Преглед на файлове

Attempt to compile refactored spells in MSVS.

DjWarmonger преди 10 години
родител
ревизия
f32849a73e
променени са 89 файла, в които са добавени 4161 реда и са изтрити 3404 реда
  1. 1 1
      AI/BattleAI/BattleAI.cpp
  2. 1 1
      AI/VCAI/AIUtility.h
  3. 7 0
      AI/VCAI/VCAI.cpp
  4. 2 1
      AI/VCAI/VCAI.h
  5. 1 1
      CCallback.cpp
  6. 1 1
      client/CMT.cpp
  7. 1 1
      client/CMusicHandler.cpp
  8. 18 1
      client/CPlayerInterface.cpp
  9. 1 0
      client/CPlayerInterface.h
  10. 1 1
      client/CPreGame.cpp
  11. 1 1
      client/Client.cpp
  12. 1 1
      client/Graphics.cpp
  13. 6 1
      client/NetPacksClient.cpp
  14. 1 1
      client/battle/CBattleAnimations.cpp
  15. 48 63
      client/battle/CBattleInterface.cpp
  16. 1 1
      client/battle/CBattleInterface.h
  17. 97 57
      client/mapHandler.cpp
  18. 14 2
      client/mapHandler.h
  19. 1 1
      client/widgets/CArtifactHolder.cpp
  20. 1 1
      client/widgets/CComponent.cpp
  21. 25 2
      client/windows/CAdvmapInterface.cpp
  22. 19 0
      client/windows/CAdvmapInterface.h
  23. 1 1
      client/windows/CCastleInterface.cpp
  24. 1 1
      client/windows/CCreatureWindow.cpp
  25. 105 119
      client/windows/CSpellWindow.cpp
  26. 0 2
      client/windows/CSpellWindow.h
  27. 1 1
      client/windows/GUIClasses.cpp
  28. 10 6
      client/windows/InfoWindows.cpp
  29. 15 0
      config/bonuses.json
  30. 116 4
      config/spells/adventure.json
  31. 1 1
      lib/BattleState.cpp
  32. 1 1
      lib/CArtHandler.cpp
  33. 1 1
      lib/CBattleCallback.cpp
  34. 4 2
      lib/CBonusTypeHandler.cpp
  35. 1 1
      lib/CCreatureSet.cpp
  36. 108 6
      lib/CGameInfoCallback.cpp
  37. 2 2
      lib/CGameInfoCallback.h
  38. 4 0
      lib/CGameInterface.h
  39. 1 1
      lib/CGameState.cpp
  40. 1 1
      lib/CGameState.h
  41. 10 3
      lib/CMakeLists.txt
  42. 1 1
      lib/CModHandler.cpp
  43. 1 1
      lib/CObstacleInstance.cpp
  44. 1 1
      lib/CTownHandler.cpp
  45. 1 1
      lib/GameConstants.cpp
  46. 2 2
      lib/GameConstants.h
  47. 1 1
      lib/HeroBonus.cpp
  48. 4 1
      lib/HeroBonus.h
  49. 1 1
      lib/IGameCallback.cpp
  50. 18 0
      lib/NetPacks.h
  51. 2 2
      lib/NetPacksLib.cpp
  52. 0 1337
      lib/SpellMechanics.cpp
  53. 1 1
      lib/VCMI_Lib.cpp
  54. 14 4
      lib/VCMI_lib.cbp
  55. 15 4
      lib/VCMI_lib.vcxproj
  56. 47 7
      lib/VCMI_lib.vcxproj.filters
  57. 1 1
      lib/mapObjects/CBank.cpp
  58. 22 1
      lib/mapObjects/CGHeroInstance.cpp
  59. 2 0
      lib/mapObjects/CGHeroInstance.h
  60. 1 1
      lib/mapObjects/CGPandoraBox.cpp
  61. 1 1
      lib/mapObjects/JsonRandom.cpp
  62. 36 2
      lib/mapObjects/MiscObjects.cpp
  63. 1 1
      lib/mapping/CMap.cpp
  64. 1 1
      lib/mapping/MapFormatH3M.cpp
  65. 1 1
      lib/registerTypes/RegisterTypes.cpp
  66. 1 0
      lib/registerTypes/RegisterTypes.h
  67. 1 1
      lib/registerTypes/TypesClientPacks1.cpp
  68. 1 1
      lib/registerTypes/TypesClientPacks2.cpp
  69. 1 1
      lib/registerTypes/TypesMapObjects1.cpp
  70. 1 1
      lib/registerTypes/TypesMapObjects2.cpp
  71. 1 1
      lib/registerTypes/TypesMapObjects3.cpp
  72. 1 1
      lib/registerTypes/TypesPregamePacks.cpp
  73. 1 1
      lib/registerTypes/TypesServerPacks.cpp
  74. 6 24
      lib/rmg/CRmgTemplateZone.cpp
  75. 272 0
      lib/spells/AdventureSpellMechanics.cpp
  76. 75 0
      lib/spells/AdventureSpellMechanics.h
  77. 417 0
      lib/spells/BattleSpellMechanics.cpp
  78. 116 0
      lib/spells/BattleSpellMechanics.h
  79. 719 0
      lib/spells/CDefaultSpellMechanics.cpp
  80. 1028 1070
      lib/spells/CSpellHandler.cpp
  81. 386 397
      lib/spells/CSpellHandler.h
  82. 95 0
      lib/spells/CreatureSpellMechanics.cpp
  83. 40 0
      lib/spells/CreatureSpellMechanics.h
  84. 87 0
      lib/spells/ISpellMechanics.cpp
  85. 16 21
      lib/spells/ISpellMechanics.h
  86. 27 0
      lib/spells/ViewSpellInt.cpp
  87. 32 0
      lib/spells/ViewSpellInt.h
  88. 29 219
      server/CGameHandler.cpp
  89. 1 1
      server/CVCMIServer.cpp

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -4,7 +4,7 @@
 #include "../../lib/BattleState.h"
 #include "../../CCallback.h"
 #include "../../lib/CCreatureHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/VCMI_Lib.h"
 
 using boost::optional;

+ 1 - 1
AI/VCAI/AIUtility.h

@@ -4,7 +4,7 @@
 #include "../../lib/CBuildingHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CTownHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/Connection.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"

+ 7 - 0
AI/VCAI/VCAI.cpp

@@ -530,6 +530,13 @@ void VCAI::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor
 	NET_EVENT_HANDLER;
 }
 
+void VCAI::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions)
+{
+	//TODO: AI support for ViewXXX spell
+	LOG_TRACE(logAi);
+	NET_EVENT_HANDLER;
+}
+
 void VCAI::init(shared_ptr<CCallback> CB)
 {
 	LOG_TRACE(logAi);

+ 2 - 1
AI/VCAI/VCAI.h

@@ -12,7 +12,7 @@
 #include "../../lib/CBuildingHandler.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CTownHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/Connection.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"
@@ -241,6 +241,7 @@ public:
 	virtual void buildChanged(const CGTownInstance *town, BuildingID buildingID, int what) override;
 	virtual void heroBonusChanged(const CGHeroInstance *hero, const Bonus &bonus, bool gain) override;
 	virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;	
 
 	virtual void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override;
 	virtual void battleEnd(const BattleResult *br) override;

+ 1 - 1
CCallback.cpp

@@ -15,7 +15,7 @@
 #include "lib/Connection.h"
 #include "lib/NetPacks.h"
 #include "client/mapHandler.h"
-#include "lib/CSpellHandler.h"
+#include "lib/spells/CSpellHandler.h"
 #include "lib/CArtHandler.h"
 #include "lib/GameConstants.h"
 #ifdef min

+ 1 - 1
client/CMT.cpp

@@ -19,7 +19,7 @@
 #include "CVideoHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CCreatureHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "CMusicHandler.h"
 #include "CVideoHandler.h"
 #include "CDefHandler.h"

+ 1 - 1
client/CMusicHandler.cpp

@@ -4,7 +4,7 @@
 #include "CMusicHandler.h"
 #include "CGameInfo.h"
 #include "../lib/CCreatureHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/JsonNode.h"
 #include "../lib/GameConstants.h"
 #include "../lib/filesystem/Filesystem.h"

+ 18 - 1
client/CPlayerInterface.cpp

@@ -23,7 +23,7 @@
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/Connection.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/mapObjects/CObjectClassesHandler.h" // For displaying correct UI when interacting with objects
 #include "../lib/BattleState.h"
@@ -2189,6 +2189,13 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 	}
 	const CSpell * spell = CGI->spellh->objects[spellID];
 
+	if(spellID == SpellID::VIEW_EARTH)
+	{
+		//TODO: implement on server side
+		int level = caster->getSpellSchoolLevel(spell);
+		adventureInt->worldViewOptions.showAllTerrain = (level>2);
+	}
+	
 	auto castSoundPath = spell->getCastSound();
 	if (!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
@@ -2703,3 +2710,13 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance* h, CGPath path)
 
 	duringMovement = false;
 }
+
+void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+	//TODO: showWorldViewEx
+	
+	std::copy(objectPositions.begin(), objectPositions.end(), std::back_inserter(adventureInt->worldViewOptions.iconPositions));
+	
+	viewWorldMap();
+}

+ 1 - 0
client/CPlayerInterface.h

@@ -196,6 +196,7 @@ public:
 	void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
 	void saveGame(COSer & h, const int version) override; //saving
 	void loadGame(CISer & h, const int version) override; //loading
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;	
 
 	//for battles
 	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero

+ 1 - 1
client/CPreGame.cpp

@@ -26,7 +26,7 @@
 #include "CPlayerInterface.h"
 #include "../CCallback.h"
 #include "CMessage.h"
-#include "../lib/CSpellHandler.h" /*for campaign bonuses*/
+#include "../lib/spells/CSpellHandler.h" /*for campaign bonuses*/
 #include "../lib/CArtHandler.h" /*for campaign bonuses*/
 #include "../lib/CBuildingHandler.h" /*for campaign bonuses*/
 #include "CBitmapHandler.h"

+ 1 - 1
client/Client.cpp

@@ -18,7 +18,7 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CBuildingHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/Connection.h"
 #ifndef VCMI_ANDROID
 #include "../lib/Interprocess.h"

+ 1 - 1
client/Graphics.cpp

@@ -15,7 +15,7 @@
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "CBitmapHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"

+ 6 - 1
client/NetPacksClient.cpp

@@ -13,7 +13,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CSoundBase.h"
 #include "../lib/StartInfo.h"
 #include "mapHandler.h"
@@ -834,6 +834,11 @@ void AdvmapSpellCast::applyCl(CClient *cl)
 	INTERFACE_CALL_IF_PRESENT(caster->getOwner(),advmapSpellCast, caster, spellID);
 }
 
+void ShowWorldViewEx::applyCl(CClient * cl)
+{
+	CALL_ONLY_THAT_INTERFACE(player, showWorldViewEx, objectPositions);
+}
+
 void OpenWindow::applyCl(CClient *cl)
 {
 	switch(window)

+ 1 - 1
client/battle/CBattleAnimations.cpp

@@ -20,7 +20,7 @@
 #include "../../lib/BattleState.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 
 /*
  * CBattleAnimations.cpp, part of VCMI engine

+ 48 - 63
client/battle/CBattleInterface.cpp

@@ -28,7 +28,7 @@
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CondSh.h"
 #include "../../lib/CRandomGenerator.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapping/CMap.h"
@@ -1228,13 +1228,18 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 {
 	const SpellID spellID(sc->id);
 	const CSpell &spell = * spellID.toSpell();
+	const std::string & spellName = spell.name;
 
 	const std::string& castSoundPath = spell.getCastSound();
-	
+
+	std::string casterName("Something");
+
+	if(sc->castedByHero)
+		casterName = curInt->cb->battleGetHeroInfo(sc->side).name;
+
 	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 
-	std::string casterCreatureName = "";
 	Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position by default
 	{
 		const auto casterStackID = sc->casterStack;
@@ -1244,8 +1249,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 			const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
 			if(casterStack != nullptr)
 			{
-				casterCreatureName = casterStack->type->namePl;
-				
+				casterName = casterStack->type->namePl;
 				srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this); 
 				srccoord.x += 250;
 				srccoord.y += 240;
@@ -1255,7 +1259,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 
 	//TODO: play custom cast animation
 	{
-				
+
 	}
 	
 	//playing projectile animation
@@ -1269,9 +1273,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 		bool Vflip = (angle < 0);
 		if(Vflip)
 			angle = -angle;
-		
+
 		std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
-		
+
 		if(!animToDisplay.empty())
 		{
 			//displaying animation
@@ -1285,7 +1289,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 			int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
 
 			delete animDef;
-			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));			
+			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
 		}
 	}	
 	waitForAnims();
@@ -1296,11 +1300,11 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	for (auto & elem : sc->affectedCres) 
 	{
 		BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
-		
+
 		if(vstd::contains(sc->resisted,elem))
 			displayEffect(78, position);
 		else
-			displaySpellEffect(spellID, position);	
+			displaySpellEffect(spellID, position);
 	}
 
 	switch(sc->id)
@@ -1319,15 +1323,26 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	bool customSpell = false;
 	if(sc->affectedCres.size() == 1)
 	{
+		const CStack * attackedStack = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false);
+
+		const std::string attackedName = attackedStack->getName();
+		const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
+		const std::string attackedNamePl = attackedStack->getCreature()->namePl;
+
 		std::string text = CGI->generaltexth->allTexts[195];
 		if(sc->castedByHero)
 		{
-			boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
-			boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name); //spell name
-			boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl ); //target
+			boost::algorithm::replace_first(text, "%s", casterName);
+			boost::algorithm::replace_first(text, "%s", spellName);
+			boost::algorithm::replace_first(text, "%s", attackedNamePl); //target
 		}
 		else
 		{
+			auto getPluralText = [attackedStack](const int baseTextID) -> std::string
+			{
+				return CGI->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID+1 : baseTextID)];
+			};
+			
 			bool plural = false; //add singular / plural form of creature text if this is true
 			int textID = 0;
 			switch(sc->id)
@@ -1345,8 +1360,8 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 				case SpellID::BIND:
 					customSpell = true;
 					text = CGI->generaltexth->allTexts[560];
-					boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
-					break;	//Roots and vines bind the %s to the ground!
+					boost::algorithm::replace_first(text, "%s", attackedNamePl);
+					break;//Roots and vines bind the %s to the ground!
 				case SpellID::DISEASE:
 					customSpell = true;
 					plural = true;
@@ -1360,25 +1375,17 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 				case SpellID::AGE:
 				{
 					customSpell = true;
-					if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
-					{
-						text = CGI->generaltexth->allTexts[552];
-						boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
-					}
-					else
-					{
-						text = CGI->generaltexth->allTexts[551];
-						boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
-					}
+					text = getPluralText(551);
+					boost::algorithm::replace_first(text, "%s", attackedName);
 					//The %s shrivel with age, and lose %d hit points."
-					TBonusListPtr bl = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getBonuses(Selector::type(Bonus::STACK_HEALTH));
+					TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
 					bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
 					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
 				}
 					break;
 				case SpellID::THUNDERBOLT:
 					text = CGI->generaltexth->allTexts[367];
-					boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
+					boost::algorithm::replace_first(text, "%s", attackedNamePl);
 					console->addText(text);
 					text = CGI->generaltexth->allTexts[343].substr(1, CGI->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
 					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay)); //no more text afterwards
@@ -1388,7 +1395,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 					break;
 				case SpellID::DISPEL_HELPFUL_SPELLS:
 					text = CGI->generaltexth->allTexts[555];
-					boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->namePl);
+					boost::algorithm::replace_first(text, "%s", attackedNamePl);
 					customSpell = true;
 					break;
 				case SpellID::DEATH_STARE:
@@ -1399,67 +1406,45 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 						{
 							text = CGI->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
 							boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
-							boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getCreature()->namePl );
+							boost::algorithm::replace_first(text, "%s", attackedNamePl);
 						}
 						else
 						{
 							text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
-							boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
+							boost::algorithm::replace_first(text, "%s", attackedNameSing);
 						}
-						boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
+						boost::algorithm::replace_first(text, "%s", casterName); //casting stack
 					}
 					else
 						text = "";
 					break;
 				default:
 					text = CGI->generaltexth->allTexts[565]; //The %s casts %s
-					if(casterCreatureName != "")
-						boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
-					else
-						boost::algorithm::replace_first(text, "%s", "@Unknown caster@"); //should not happen
+					boost::algorithm::replace_first(text, "%s", casterName); //casting stack
+
 			}
 			if (plural)
 			{
-				if (curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->count > 1)
-				{
-					text = CGI->generaltexth->allTexts[textID + 1];
-					boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
-				}
-				else
-				{
-					text = CGI->generaltexth->allTexts[textID];
-					boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->getName());
-				}
+				text = getPluralText(textID);
+				boost::algorithm::replace_first(text, "%s", attackedName);
 			}
 		}
 		if (!customSpell && !sc->dmgToDisplay)
-			boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name); //simple spell name
+			boost::algorithm::replace_first(text, "%s", spellName); //simple spell name
 		if (text.size())
 			console->addText(text);
 	}
 	else
 	{
 		std::string text = CGI->generaltexth->allTexts[196];
-		if(sc->castedByHero)
-		{
-			boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
-		}
-		if(casterCreatureName != "")
-		{
-			boost::algorithm::replace_first(text, "%s", casterCreatureName); //creature caster
-		}
-		else
-		{
-			//TODO artifacts that cast spell; scripts some day
-			boost::algorithm::replace_first(text, "%s", "Something");
-		}
-		boost::algorithm::replace_first(text, "%s", CGI->spellh->objects[sc->id]->name);
+		boost::algorithm::replace_first(text, "%s", casterName);
+		boost::algorithm::replace_first(text, "%s", spellName);
 		console->addText(text);
 	}
 	if(sc->dmgToDisplay && !customSpell)
 	{
 		std::string dmgInfo = CGI->generaltexth->allTexts[376];
-		boost::algorithm::replace_first(dmgInfo, "%s", CGI->spellh->objects[sc->id]->name); //simple spell name
+		boost::algorithm::replace_first(dmgInfo, "%s", spellName); //simple spell name
 		boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast<std::string>(sc->dmgToDisplay));
 		console->addText(dmgInfo); //todo: casualties (?)
 	}
@@ -1502,7 +1487,7 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 	}
 }
 
-void CBattleInterface::castThisSpell(int spellID)
+void CBattleInterface::castThisSpell(SpellID spellID)
 {
 	auto  ba = new BattleAction;
 	ba->actionType = Battle::HERO_SPELL;

+ 1 - 1
client/battle/CBattleInterface.h

@@ -317,7 +317,7 @@ public:
 	void displayBattleFinished(); //displays battle result
 	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(int spellID); //called when player has chosen a spell from spellbook
+	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
 	void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
 	void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
 	void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation

+ 97 - 57
client/mapHandler.cpp

@@ -528,6 +528,40 @@ CMapHandler::CMapNormalBlitter::CMapNormalBlitter(CMapHandler * parent)
 	defaultTileRect = Rect(0, 0, tileSize, tileSize);
 }
 
+SDL_Surface * CMapHandler::CMapWorldViewBlitter::objectToIcon(Obj id, si32 subId, PlayerColor owner) const
+{
+	int ownerIndex = 0;
+	if(owner < PlayerColor::PLAYER_LIMIT)
+	{
+		ownerIndex = owner.getNum() * 19;
+	}
+	else if (owner == PlayerColor::NEUTRAL)
+	{
+		ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * 19;
+	}
+	
+	switch(id)
+	{
+	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+	case Obj::MONOLITH_ONE_WAY_EXIT:
+	case Obj::MONOLITH_TWO_WAY:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::TELEPORT].bitmap;
+	case Obj::SUBTERRANEAN_GATE:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::GATE].bitmap;
+	case Obj::ARTIFACT:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::ARTIFACT].bitmap;
+	case Obj::TOWN:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::TOWN + ownerIndex].bitmap;
+	case Obj::HERO:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::HERO + ownerIndex].bitmap;
+	case Obj::MINE:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::MINE_WOOD + subId + ownerIndex].bitmap;
+	case Obj::RESOURCE:
+		return info->iconsDef->ourImages[(int)EWorldViewIcon::RES_WOOD + subId + ownerIndex].bitmap;
+	}	
+	return nullptr;	
+}
+
 void CMapHandler::CMapWorldViewBlitter::calculateWorldViewCameraPos()
 {
 	bool outsideLeft = topTile.x < 0;
@@ -609,65 +643,61 @@ void CMapHandler::CMapWorldViewBlitter::drawElement(EMapCacheType cacheType, SDL
 
 void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf, const TerrainTile2 & tile) const
 {
+	auto drawIcon = [this,targetSurf](Obj id, si32 subId, PlayerColor owner)
+	{
+		SDL_Surface * wvIcon = this->objectToIcon(id, subId, owner);
+
+		if (nullptr != wvIcon)
+		{
+			// centering icon on the object
+			Rect destRect(realPos.x + tileSize / 2 - wvIcon->w / 2, realPos.y + tileSize / 2 - wvIcon->h / 2, wvIcon->w, wvIcon->h);
+			CSDL_Ext::blitSurface(wvIcon, nullptr, targetSurf, &destRect);
+		}
+	};
+
 	auto & objects = tile.objects;
 	for(auto & object : objects)
 	{
 		const CGObjectInstance * obj = object.obj;
 
-		if (obj->pos.z != pos.z)
-			continue;
-		if (!(*info->visibilityMap)[pos.x][pos.y][pos.z])
-			continue; // TODO needs to skip this check if we have view-air-like spell cast
-		if (!obj->visitableAt(pos.x, pos.y))
-			continue;
+		const bool sameLevel = obj->pos.z == pos.z;			
+		const bool isVisible = (*info->visibilityMap)[pos.x][pos.y][pos.z];
+		const bool isVisitable = obj->visitableAt(pos.x, pos.y);
 
-		auto &ownerRaw = obj->tempOwner;
-		int ownerIndex = 0;
-		if (ownerRaw < PlayerColor::PLAYER_LIMIT)
-		{
-			ownerIndex = ownerRaw.getNum() * 19;
-		}
-		else if (ownerRaw == PlayerColor::NEUTRAL)
-		{
-			ownerIndex = PlayerColor::PLAYER_LIMIT.getNum() * 19;
-		}
+		if(sameLevel && isVisible && isVisitable)
+			drawIcon(obj->ID, obj->subID, obj->tempOwner);
+	}
+}
 
-		SDL_Surface * wvIcon = nullptr;
-		switch (obj->ID)
-		{
-		default:
+void CMapHandler::CMapWorldViewBlitter::drawOverlayEx(SDL_Surface * targetSurf)
+{
+	if(nullptr == info->additionalIcons)
+		return;
+		
+	const int3 bottomRight = pos + tileCount;
+	
+	for(const ObjectPosInfo & iconInfo : *(info->additionalIcons))
+	{
+		if(!iconInfo.pos.z == pos.z)
 			continue;
-		case Obj::MONOLITH_ONE_WAY_ENTRANCE:
-		case Obj::MONOLITH_ONE_WAY_EXIT:
-		case Obj::MONOLITH_TWO_WAY:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::TELEPORT].bitmap;
-			break;
-		case Obj::SUBTERRANEAN_GATE:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::GATE].bitmap;
-			break;
-		case Obj::ARTIFACT:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::ARTIFACT].bitmap;
-			break;
-		case Obj::TOWN:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::TOWN + ownerIndex].bitmap;
-			break;
-		case Obj::HERO:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::HERO + ownerIndex].bitmap;
-			break;
-		case Obj::MINE:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::MINE_WOOD + obj->subID + ownerIndex].bitmap;
-			break;
-		case Obj::RESOURCE:
-			wvIcon = info->iconsDef->ourImages[(int)EWorldViewIcon::RES_WOOD + obj->subID + ownerIndex].bitmap;
-			break;
-		}
-
-		if (wvIcon)
+		
+		if((iconInfo.pos.x < topTile.x) || (iconInfo.pos.y < topTile.y))
+			continue;
+		
+		if((iconInfo.pos.x > bottomRight.x) || (iconInfo.pos.y > bottomRight.y))
+			continue;		
+		
+		realPos.x = initPos.x + (iconInfo.pos.x - topTile.x) * tileSize;
+		realPos.y = initPos.x + (iconInfo.pos.y - topTile.y) * tileSize;
+		
+		SDL_Surface * wvIcon = this->objectToIcon(iconInfo.id, iconInfo.subId, iconInfo.owner);				
+		
+		if (nullptr != wvIcon)
 		{
 			// centering icon on the object
 			Rect destRect(realPos.x + tileSize / 2 - wvIcon->w / 2, realPos.y + tileSize / 2 - wvIcon->h / 2, wvIcon->w, wvIcon->h);
 			CSDL_Ext::blitSurface(wvIcon, nullptr, targetSurf, &destRect);
-		}
+		}		
 	}
 }
 
@@ -785,6 +815,11 @@ void CMapHandler::CMapBlitter::drawFrame(SDL_Surface * targetSurf) const
 	drawElement(EMapCacheType::FRAME, parent->ttiles[pos.x][pos.y][topTile.z].terbitmap, nullptr, targetSurf, &destRect);
 }
 
+void CMapHandler::CMapBlitter::drawOverlayEx(SDL_Surface * targetSurf)
+{
+//nothing to do here
+}
+
 void CMapHandler::CMapBlitter::drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const
 {
 	drawElement(EMapCacheType::HERO_FLAGS, sourceSurf, sourceRect, targetSurf, destRect, false);
@@ -904,9 +939,8 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 		{
 			if (pos.y < 0 || pos.y >= parent->sizes.y)
 				continue;
-
-			if (!canDrawCurrentTile())
-				continue;
+			
+			const bool isVisible = canDrawCurrentTile();
 
 			realTileRect.x = realPos.x;
 			realTileRect.y = realPos.y;
@@ -914,13 +948,17 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
 			const TerrainTile & tinfo = parent->map->getTile(pos);
 			const TerrainTile * tinfoUpper = pos.y > 0 ? &parent->map->getTile(int3(pos.x, pos.y - 1, pos.z)) : nullptr;
+			
+			if(isVisible || info->showAllTerrain)
+			{
+				drawTileTerrain(targetSurf, tinfo, tile);
+				if (tinfo.riverType)
+					drawRiver(targetSurf, tinfo);
+				drawRoad(targetSurf, tinfo, tinfoUpper);				
+			}
 
-			drawTileTerrain(targetSurf, tinfo, tile);
-			if (tinfo.riverType)
-				drawRiver(targetSurf, tinfo);
-			drawRoad(targetSurf, tinfo, tinfoUpper);
-
-			drawObjects(targetSurf, tile);
+			if(isVisible)
+				drawObjects(targetSurf, tile);
 		}
 	}
 
@@ -940,7 +978,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			{
 				const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
 
-				if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z])
+				if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
 					drawFow(targetSurf);
 
 				// overlay needs to be drawn over fow, because of artifacts-aura-like spells
@@ -972,6 +1010,8 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
 			}
 		}
 	}
+	
+	drawOverlayEx(targetSurf);	
 
 	// drawDebugGrid()
 	if (settings["session"]["showGrid"].Bool())

+ 14 - 2
client/mapHandler.h

@@ -2,6 +2,7 @@
 
 
 #include "../lib/int3.h"
+#include "../lib/spells/ViewSpellInt.h"
 #include "gui/Geometries.h"
 #include "SDL.h"
 
@@ -100,7 +101,11 @@ struct MapDrawingInfo
 
 	bool puzzleMode;
 	int3 grailPos; // location of grail for puzzle mode [in tiles]
-
+	
+	const std::vector<ObjectPosInfo> * additionalIcons;
+	
+	bool showAllTerrain; //for expert viewEarth
+	
 	MapDrawingInfo(int3 &topTile_, const std::vector< std::vector< std::vector<ui8> > > * visibilityMap_, SDL_Rect * drawBounds_, CDefHandler * iconsDef_ = nullptr)
 		: scaled(false),
 		  topTile(topTile_),
@@ -113,7 +118,9 @@ struct MapDrawingInfo
 		  heroAnim(0u),
 		  movement(int3()),
 		  puzzleMode(false),
-		  grailPos(int3())
+		  grailPos(int3()),
+		  additionalIcons(nullptr),
+		  showAllTerrain(false)
 	{}
 
 	ui8 getHeroAnim() const { return otherheroAnim ? anim : heroAnim; }
@@ -238,6 +245,8 @@ class CMapHandler
 		virtual void drawFow(SDL_Surface * targetSurf) const;
 		/// draws map border frame on current position
 		virtual void drawFrame(SDL_Surface * targetSurf) const;
+		/// draws additional icons (for VIEW_AIR, VIEW_EARTH spells atm)
+		virtual void drawOverlayEx(SDL_Surface * targetSurf);
 
 		// third drawing pass
 
@@ -293,6 +302,8 @@ class CMapHandler
 
 	class CMapWorldViewBlitter : public CMapBlitter
 	{
+	private:
+		SDL_Surface * objectToIcon(Obj id, si32 subId, PlayerColor owner) const;
 	protected:
 		void drawElement(EMapCacheType cacheType, SDL_Surface * sourceSurf, SDL_Rect * sourceRect,
 						 SDL_Surface * targetSurf, SDL_Rect * destRect, bool alphaBlit = false, ui8 rotationInfo = 0u) const override;
@@ -301,6 +312,7 @@ class CMapHandler
 		void drawHeroFlag(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, SDL_Rect * destRect, bool moving) const override;
 		void drawObject(SDL_Surface * targetSurf, SDL_Surface * sourceSurf, SDL_Rect * sourceRect, bool moving) const override;
 		void drawFrame(SDL_Surface * targetSurf) const override {}
+		void drawOverlayEx(SDL_Surface * targetSurf);
 		void init(const MapDrawingInfo * info) override;
 		SDL_Rect clip(SDL_Surface * targetSurf) const override;
 

+ 1 - 1
client/widgets/CArtifactHolder.cpp

@@ -16,7 +16,7 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CArtHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
 #include "../../lib/mapObjects/CGHeroInstance.h"

+ 1 - 1
client/widgets/CComponent.cpp

@@ -12,7 +12,7 @@
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/CCreatureHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/NetPacksBase.h"
 

+ 25 - 2
client/windows/CAdvmapInterface.cpp

@@ -32,7 +32,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
 #include "../../lib/CSoundBase.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/JsonNode.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -310,7 +310,7 @@ void CTerrainRect::showAll(SDL_Surface * to)
 		MapDrawingInfo info(adventureInt->position, &LOCPLINT->cb->getVisibilityMap(), &pos, adventureInt->worldViewIconsDef);
 		info.scaled = true;
 		info.scale = adventureInt->worldViewScale;
-
+		adventureInt->worldViewOptions.adjustDrawingInfo(info);
 		CGI->mh->drawTerrainRectNew(to, &info);
 	}
 }
@@ -1783,6 +1783,9 @@ void CAdvMapInt::changeMode(EAdvMapMode newMode, float newScale /* = 0.4f */)
 			townList.activate();
 			heroList.activate();
 			infoBar.activate();
+			
+			worldViewOptions.clear();
+			
 			break;
 		case EAdvMapMode::WORLD_VIEW:
 			panelMain->deactivate();
@@ -1843,3 +1846,23 @@ void CAdventureOptions::showScenarioInfo()
 		GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
 	}
 }
+
+CAdvMapInt::WorldViewOptions::WorldViewOptions()
+{
+	clear();
+}
+
+void CAdvMapInt::WorldViewOptions::clear()
+{
+	showAllTerrain = false;
+	
+	iconPositions.clear();
+}
+
+void CAdvMapInt::WorldViewOptions::adjustDrawingInfo(MapDrawingInfo& info)
+{
+	info.showAllTerrain = showAllTerrain;
+	
+	info.additionalIcons = &iconPositions;	
+}
+

+ 19 - 0
client/windows/CAdvmapInterface.h

@@ -6,6 +6,8 @@
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
 
+#include "../../lib/spells/ViewSpellInt.h"
+
 class CDefHandler;
 class CCallback;
 struct CGPath;
@@ -18,6 +20,8 @@ class IShipyard;
 enum class EMapAnimRedrawStatus;
 class CFadeAnimation;
 
+struct MapDrawingInfo;
+
 /*****************************/
 
 /*
@@ -126,6 +130,21 @@ public:
 
 	EAdvMapMode mode;
 	float worldViewScale;
+	
+	struct WorldViewOptions
+	{
+		bool showAllTerrain; //for expert viewEarth
+		
+		std::vector<ObjectPosInfo> iconPositions;
+		
+		WorldViewOptions();
+		
+		void clear();
+		
+		void adjustDrawingInfo(MapDrawingInfo & info);		
+	};
+	
+	WorldViewOptions worldViewOptions; 	
 
 	SDL_Surface * bg;
 	SDL_Surface * bgWorldView;

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -26,7 +26,7 @@
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CModHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"

+ 1 - 1
client/windows/CCreatureWindow.cpp

@@ -16,7 +16,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/CGameState.h"
 
 using namespace CSDL_Ext;

+ 105 - 119
client/windows/CSpellWindow.cpp

@@ -27,7 +27,7 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CHeroHandler.h"
-#include "../../lib/CSpellHandler.h"
+#include "../../lib/spells/CSpellHandler.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/CGameState.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
@@ -107,15 +107,11 @@ CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPl
 		Uint8 *sitesPerOurTab = s.combatSpell ? sitesPerTabBattle : sitesPerTabAdv;
 
 		++sitesPerOurTab[4];
-
-		if(s.air)
-			++sitesPerOurTab[0];
-		if(s.fire)
-			++sitesPerOurTab[1];
-		if(s.water)
-			++sitesPerOurTab[2];
-		if(s.earth)
-			++sitesPerOurTab[3];
+		
+		s.forEachSchool([&sitesPerOurTab](const SpellSchoolInfo & school, bool & stop)
+		{
+			++sitesPerOurTab[(ui8)school.id];
+		});
 	}
 	if(sitesPerTabAdv[4] % 12 == 0)
 		sitesPerTabAdv[4]/=12;
@@ -382,23 +378,17 @@ public:
 		if(A.level<B.level)
 			return true;
 		if(A.level>B.level)
-			return false;
-		if(A.air && !B.air)
-			return true;
-		if(!A.air && B.air)
-			return false;
-		if(A.fire && !B.fire)
-			return true;
-		if(!A.fire && B.fire)
-			return false;
-		if(A.water && !B.water)
-			return true;
-		if(!A.water && B.water)
-			return false;
-		if(A.earth && !B.earth)
-			return true;
-		if(!A.earth && B.earth)
-			return false;
+			return false;		
+		
+		
+		for(ui8 schoolId = 0; schoolId < 4; schoolId++)
+		{
+			if(A.school.at((ESpellSchool)schoolId) && !B.school.at((ESpellSchool)schoolId))
+				return true;
+			if(!A.school.at((ESpellSchool)schoolId) && B.school.at((ESpellSchool)schoolId))
+				return false;
+		}
+		
 		return A.name < B.name;
 	}
 } spellsorter;
@@ -406,17 +396,15 @@ public:
 void CSpellWindow::computeSpellsPerArea()
 {
 	std::vector<SpellID> spellsCurSite;
-	for(auto it = mySpells.cbegin(); it != mySpells.cend(); ++it)
-	{
-		if(CGI->spellh->objects[*it]->combatSpell ^ !battleSpellsOnly
-			&& ((CGI->spellh->objects[*it]->air && selectedTab == 0) ||
-				(CGI->spellh->objects[*it]->fire && selectedTab == 1) ||
-				(CGI->spellh->objects[*it]->water && selectedTab == 2) ||
-				(CGI->spellh->objects[*it]->earth && selectedTab == 3) ||
-				selectedTab == 4 )
+	for(const SpellID & spellID : mySpells)
+	{
+		CSpell * s = spellID.toSpell(); 
+		
+		if(s->combatSpell ^ !battleSpellsOnly
+			&& ((selectedTab == 4) || (s->school[(ESpellSchool)selectedTab]))
 			)
 		{
-			spellsCurSite.push_back(*it);
+			spellsCurSite.push_back(spellID);
 		}
 	}
 	std::sort(spellsCurSite.begin(), spellsCurSite.end(), spellsorter);
@@ -605,12 +593,6 @@ Uint8 CSpellWindow::pagesWithinCurrentTab()
 	return battleSpellsOnly ? sitesPerTabBattle[selectedTab] : sitesPerTabAdv[selectedTab];
 }
 
-void CSpellWindow::teleportTo( int town, const CGHeroInstance * hero )
-{
-	const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(town));
-	LOCPLINT->cb->castSpell(hero, SpellID::TOWN_PORTAL, dest->visitablePos());
-}
-
 CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
 {
 	this->pos = pos;
@@ -623,9 +605,9 @@ CSpellWindow::SpellArea::SpellArea(SDL_Rect pos, CSpellWindow * owner)
 
 void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 {
-	if(!down && mySpell!=-1)
+	if(!down && mySpell != SpellID::NONE)
 	{
-		const CSpell *sp = CGI->spellh->objects[mySpell];
+		const CSpell * sp = mySpell.toSpell();
 
 		int spellCost = owner->myInt->cb->getSpellCost(sp, owner->myHero);
 		if(spellCost > owner->myHero->mana) //insufficient mana
@@ -637,8 +619,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 		}
 
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
-		if((sp->combatSpell && !owner->myInt->battleInt)
-			|| (!sp->combatSpell && owner->myInt->battleInt))
+		if((sp->isCombatSpell() && !owner->myInt->battleInt)
+			|| (sp->isAdventureSpell() && owner->myInt->battleInt))
 		{
 			std::vector<CComponent*> hlp(1, new CComponent(CComponent::spell, mySpell, 0));
 			LOCPLINT->showInfoDialog(sp->getLevelInfo(schoolLevel).description, hlp);
@@ -653,9 +635,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			{
 			case ESpellCastProblem::OK:
 				{
-					int spell = mySpell;
 					owner->fexitb();
-					owner->myInt->battleInt->castThisSpell(spell);
+					owner->myInt->battleInt->castThisSpell(mySpell);
 				}
 				break;
 			case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
@@ -714,97 +695,102 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 				break;
 			}
 		}
-		else if(!sp->combatSpell && !owner->myInt->battleInt) //adventure spell
+		else if(sp->isAdventureSpell() && !owner->myInt->battleInt) //adventure spell and not in battle
 		{
-			SpellID spell = mySpell;
 			const CGHeroInstance *h = owner->myHero;
 			owner->fexitb();
+			
 
-			switch(spell)
+			if(mySpell == SpellID::TOWN_PORTAL)
 			{
-			case SpellID::SUMMON_BOAT:
+				//special case
+				//todo: move to mechanics
+				
+				std::vector <int> availableTowns;
+				std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(true);
+				if (Towns.empty())
 				{
-					int3 pos = h->bestLocation();
-					if(pos.x < 0)
-					{
-						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
-						return;
-					}
+					LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
+					return;
 				}
-				break;
-			case SpellID::SCUTTLE_BOAT:
-			case SpellID::DIMENSION_DOOR:
-				adventureInt->enterCastingMode(sp);
-				return;
-			case SpellID::VISIONS:
-			case SpellID::VIEW_EARTH:
-			case SpellID::DISGUISE:
-			case SpellID::VIEW_AIR:
-			case SpellID::FLY:
-			case SpellID::WATER_WALK:
-				break;
-			case SpellID::TOWN_PORTAL:
+
+				if (h->getSpellSchoolLevel(sp) < 2) //not advanced or expert - teleport to nearest available city
 				{
-					std::vector <int> availableTowns;
-					std::vector <const CGTownInstance*> Towns = LOCPLINT->cb->getTownsInfo(true);
-					if (Towns.empty())
-					{
-						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
-						return;
-					}
+					auto nearest = Towns.cbegin(); //nearest town's iterator
+					si32 dist = LOCPLINT->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
 
-					if (h->getSpellSchoolLevel(CGI->spellh->objects[spell]) < 2) //not advanced or expert - teleport to nearest available city
+					for (auto i = nearest + 1; i != Towns.cend(); ++i)
 					{
-						auto nearest = Towns.cbegin(); //nearest town's iterator
-						si32 dist = LOCPLINT->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
-
-						for (auto i = nearest + 1; i != Towns.cend(); ++i)
-						{
-							const CGTownInstance * dest = LOCPLINT->cb->getTown((*i)->id);
-							si32 curDist = dest->pos.dist2dSQ(h->pos);
-
-							if (curDist < dist)
-							{
-								nearest = i;
-								dist = curDist;
-							}
-						}
+						const CGTownInstance * dest = LOCPLINT->cb->getTown((*i)->id);
+						si32 curDist = dest->pos.dist2dSQ(h->pos);
 
-						if ((*nearest)->visitingHero)
-							LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[123]);
-						else
+						if (curDist < dist)
 						{
-							const CGTownInstance * town = LOCPLINT->cb->getTown((*nearest)->id);
-							LOCPLINT->cb->castSpell(h, spell, town->visitablePos());// - town->getVisitableOffset());
+							nearest = i;
+							dist = curDist;
 						}
 					}
+
+					if ((*nearest)->visitingHero)
+						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[123]);
 					else
-					{ //let the player choose
-						for(auto & Town : Towns)
+					{
+						const CGTownInstance * town = LOCPLINT->cb->getTown((*nearest)->id);
+						LOCPLINT->cb->castSpell(h, mySpell, town->visitablePos());// - town->getVisitableOffset());
+					}
+				}
+				else
+				{ //let the player choose
+					for(auto & Town : Towns)
+					{
+						const CGTownInstance *t = Town;
+						if (t->visitingHero == nullptr) //empty town and this is
 						{
-							const CGTownInstance *t = Town;
-							if (t->visitingHero == nullptr) //empty town and this is
-							{
-								availableTowns.push_back(t->id.getNum());//add to the list
-							}
+							availableTowns.push_back(t->id.getNum());//add to the list
 						}
-						if (availableTowns.empty())
-							LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
-						else
-							GH.pushInt (new CObjectListWindow(availableTowns,
-								new CAnimImage("SPELLSCR",spell),
-								CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
-								std::bind (&CSpellWindow::teleportTo, owner, _1, h)));
 					}
+					
+					auto castTownPortal = [h](int townId)
+					{
+						const CGTownInstance * dest = LOCPLINT->cb->getTown(ObjectInstanceID(townId));
+						LOCPLINT->cb->castSpell(h, SpellID::TOWN_PORTAL, dest->visitablePos());					
+					};
+					
+					if (availableTowns.empty())
+						LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[124]);
+					else
+						GH.pushInt (new CObjectListWindow(availableTowns,
+							new CAnimImage("SPELLSCR",mySpell),
+							CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
+							castTownPortal));
+				}
+				return;	
+			}
+			
+			if(mySpell == SpellID::SUMMON_BOAT)
+			{
+				//special case
+				//todo: move to mechanics				
+				int3 pos = h->bestLocation();
+				if(pos.x < 0)
+				{
+					LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
 					return;
 				}
-				break;
-			default:
-				assert(0);
 			}
-
-			//can return earlier in some cases
-			LOCPLINT->cb->castSpell(h, spell);
+			
+			if(sp->getTargetType() == CSpell::LOCATION)
+			{
+				adventureInt->enterCastingMode(sp);		
+			}
+			else if(sp->getTargetType() == CSpell::NO_TARGET)
+			{
+				LOCPLINT->cb->castSpell(h, mySpell);
+			}
+			else
+			{
+				logGlobal->error("Invalid spell target type");
+			}			
 		}
 	}
 }
@@ -816,7 +802,7 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState)
 		std::string dmgInfo;
 		const CGHeroInstance * hero = owner->myHero;
 		int causedDmg = owner->myInt->cb->estimateSpellDamage( CGI->spellh->objects[mySpell], (hero ? hero : nullptr));
-		if(causedDmg == 0 || mySpell == 57) //Titan's Lightning Bolt already has damage info included
+		if(causedDmg == 0 || mySpell == SpellID::TITANS_LIGHTNING_BOLT) //Titan's Lightning Bolt already has damage info included
 			dmgInfo = "";
 		else
 		{

+ 0 - 2
client/windows/CSpellWindow.h

@@ -112,6 +112,4 @@ public:
 	void deactivate();
 	void showAll(SDL_Surface * to);
 	void show(SDL_Surface * to);
-
-	void teleportTo(int town, const CGHeroInstance * hero);
 };

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -42,7 +42,7 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CondSh.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/GameConstants.h"

+ 10 - 6
client/windows/InfoWindows.cpp

@@ -411,7 +411,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town):
 	CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "TOWNQVBK", toScreen(position))
 {
 	InfoAboutTown iah;
-	LOCPLINT->cb->getTownInfo(town, iah);
+	LOCPLINT->cb->getTownInfo(town, iah, adventureInt->selection); //todo: should this be nearest hero?
 
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	new CTownTooltip(Point(9, 10), iah);
@@ -421,7 +421,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero):
 	CWindowObject(RCLICK_POPUP | PLAYER_COLORED, "HEROQVBK", toScreen(position))
 {
 	InfoAboutHero iah;
-	LOCPLINT->cb->getHeroInfo(hero, iah);
+	LOCPLINT->cb->getHeroInfo(hero, iah, adventureInt->selection);//todo: should this be nearest hero?
 
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	new CHeroTooltip(Point(9, 10), iah);
@@ -439,11 +439,15 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr):
 
 CIntObject * CRClickPopup::createInfoWin(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
 {
-	if(!specific)
+	if(nullptr == specific)
 		specific = adventureInt->selection;
-
-	assert(specific);
-
+	
+	if(nullptr == specific)
+	{
+		logGlobal->error("createInfoWin: no object to describe");
+		return nullptr;
+	}	
+	
 	switch(specific->ID)
 	{
 	case Obj::HERO:

+ 15 - 0
config/bonuses.json

@@ -128,6 +128,11 @@
 		}
 	},
 
+        "DISGUISED":
+        {
+        	"hidden": true
+        },
+
 	"EARTH_IMMUNITY":
 	{
 		"graphics":
@@ -328,6 +333,11 @@
 			"icon":  "zvs/Lib1.res/E_MIND"
 		}
 	},
+	
+	"NONE":
+	{
+		"hidden": true
+	},
 
 	"NO_DISTANCE_PENALTY":
 	{
@@ -515,6 +525,11 @@
 			"icon":  "zvs/Lib1.res/E_RETAIL1"
 		}
 	},
+	
+	"VISIONS":
+	{
+		"hidden": true
+	},
 	"WATER_IMMUNITY":
 	{
 		"graphics":

+ 116 - 4
config/spells/adventure.json

@@ -40,7 +40,52 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "0",
+				"effects" : {
+					"visionsMonsters" : {
+						"type" : "VISIONS",
+						"subtype" : 0,
+						"duration" : "ONE_DAY",
+						"val" : 1,
+						"valueType" : "INDEPENDENT_MAX"
+					}
+				}				
+			},
+			"advanced":{
+				"effects" : {
+					"visionsMonsters" : {
+						"val" : 2
+					},
+					"visionsHeroes" :{
+						"type" : "VISIONS",
+						"subtype" : 1,
+						"duration" : "ONE_DAY",
+						"val" : 2,
+						"valueType" : "INDEPENDENT_MAX"					
+					}
+					
+				}			
+			},
+			"expert":{
+				"effects" : {
+					"visionsMonsters" : {
+						"val" : 3
+					},
+					"visionsHeroes" :{
+						"type" : "VISIONS",
+						"subtype" : 1,
+						"duration" : "ONE_DAY",
+						"val" : 3,
+						"valueType" : "INDEPENDENT_MAX"					
+					},
+					"visionsTowns" :{
+						"type" : "VISIONS",
+						"subtype" : 2,
+						"duration" : "ONE_DAY",
+						"val" : 3,
+						"valueType" : "INDEPENDENT_MAX"					
+					}
+				}			
 			}
 		},
 		"flags" : {
@@ -72,7 +117,30 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "0",
+				"effects" : {
+					"stealth" : {
+						"type" : "DISGUISED",
+						"subtype" : 0, //required
+						"duration" : "ONE_DAY",
+						"val" : 1,
+						"valueType" : "INDEPENDENT_MAX"
+					}
+				}				
+			},
+			"advanced":{
+				"effects" : {
+					"stealth" : {
+						"val" : 2
+					}					
+				}			
+			},
+			"expert":{
+				"effects" : {
+					"stealth" : {
+						"val" : 3
+					}
+				}			
 			}
 		},
 		"flags" : {
@@ -104,7 +172,29 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "0",
+				"effects" : {
+					"fly" : {
+						"type" : "FLYING_MOVEMENT",
+						"subtype" : 2,
+						"duration" : "ONE_DAY",
+						"val" : 0 //in fact unused 
+					}
+				}				
+			},
+			"advanced":{
+				"effects" : {
+					"fly" : {
+						"subtype" : 1
+					}
+				}			
+			},
+			"expert":{
+				"effects" : {
+					"fly" : {
+						"subtype" : 1
+					}
+				}			
 			}
 		},
 		"flags" : {
@@ -120,7 +210,29 @@
 		},
 		"levels" : {
 			"base":{
-				"range" : "X"
+				"range" : "0",
+				"effects" : {
+					"waterWalk" : {
+						"type" : "WATER_WALKING",
+						"subtype" : 2,
+						"duration" : "ONE_DAY",
+						"val" : 0 //in fact unused
+					}
+				}				
+			},
+			"advanced":{
+				"effects" : {
+					"waterWalk" : {
+						"subtype" : 1
+					}
+				}			
+			},
+			"expert":{
+				"effects" : {
+					"waterWalk" : {
+						"subtype" : 1
+					}
+				}			
 			}
 		},
 		"flags" : {

+ 1 - 1
lib/BattleState.cpp

@@ -16,7 +16,7 @@
 #include "mapObjects/CObjectHandler.h"
 #include "CHeroHandler.h"
 #include "CCreatureHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "CTownHandler.h"
 #include "NetPacks.h"
 #include "JsonNode.h"

+ 1 - 1
lib/CArtHandler.cpp

@@ -15,7 +15,7 @@
 #include "CGeneralTextHandler.h"
 #include "VCMI_Lib.h"
 #include "CModHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "mapObjects/MapObjects.h"
 #include "NetPacksBase.h"
 #include "GameConstants.h"

+ 1 - 1
lib/CBattleCallback.cpp

@@ -3,7 +3,7 @@
 #include "BattleState.h"
 #include "CGameState.h"
 #include "NetPacks.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "VCMI_Lib.h"
 #include "CTownHandler.h"
 

+ 4 - 2
lib/CBonusTypeHandler.cpp

@@ -15,7 +15,7 @@
 
 #include "GameConstants.h"
 #include "CCreatureHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 
 ///MacroString
 
@@ -127,7 +127,7 @@ CBonusTypeHandler::~CBonusTypeHandler()
 }
 
 std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const
-{
+{	
 	auto getValue = [=](const std::string &name) -> std::string
 	{
 		if (name == "val")
@@ -156,6 +156,8 @@ std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBea
 	};
 	
 	const CBonusType& bt = bonusTypes[bonus->type];	
+	if(bt.hidden)
+		return "";
 	const MacroString& macro = description ? bt.description : bt.name;
 	
 	return macro.build(getValue);	

+ 1 - 1
lib/CCreatureSet.cpp

@@ -8,7 +8,7 @@
 #include "IGameCallback.h"
 #include "CGameState.h"
 #include "CGeneralTextHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "CHeroHandler.h"
 #include "IBonusTypeHandler.h"
 

+ 108 - 6
lib/CGameInfoCallback.cpp

@@ -17,7 +17,7 @@
 #include "BattleState.h" // for BattleInfo
 #include "NetPacks.h" // for InfoWindow
 #include "CModHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 
 //TODO make clean
 #define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0)
@@ -201,14 +201,22 @@ int CGameInfoCallback::howManyTowns(PlayerColor Player) const
 	return gs->players[Player].towns.size();
 }
 
-bool CGameInfoCallback::getTownInfo( const CGObjectInstance *town, InfoAboutTown &dest ) const
+bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject/* = nullptr*/) const
 {
 	ERROR_RET_VAL_IF(!isVisible(town, player), "Town is not visible!", false);  //it's not a town or it's not visible for layer
 	bool detailed = hasAccess(town->tempOwner);
 
-	//TODO vision support
 	if(town->ID == Obj::TOWN)
+	{
+		if(!detailed && nullptr != selectedObject)
+		{
+			const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);		
+			if(nullptr != selectedHero)
+				detailed = selectedHero->hasVisions(town, 1);			
+		}
+		
 		dest.initFromTown(static_cast<const CGTownInstance *>(town), detailed);
+	}		
 	else if(town->ID == Obj::GARRISON || town->ID == Obj::GARRISON2)
 		dest.initFromArmy(static_cast<const CArmedInstance *>(town), detailed);
 	else
@@ -233,15 +241,109 @@ std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (in
 	return ret;
 }
 
-bool CGameInfoCallback::getHeroInfo( const CGObjectInstance *hero, InfoAboutHero &dest ) const
+bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject/* = nullptr*/) const
 {
 	const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(hero);
 
 	ERROR_RET_VAL_IF(!h, "That's not a hero!", false);
 	ERROR_RET_VAL_IF(!isVisible(h->getPosition(false)), "That hero is not visible!", false);
 
-	//TODO vision support
-	dest.initFromHero(h, hasAccess(h->tempOwner));
+	bool accessFlag = hasAccess(h->tempOwner);
+	
+	if(!accessFlag && nullptr != selectedObject)
+	{
+		const CGHeroInstance * selectedHero = dynamic_cast<const CGHeroInstance *>(selectedObject);		
+		if(nullptr != selectedHero)
+			accessFlag = selectedHero->hasVisions(hero, 1);			
+	}
+	
+	dest.initFromHero(h, accessFlag);
+	
+	//DISGUISED bonus implementation
+	
+	if(getPlayerRelations(getLocalPlayer(), hero->tempOwner) == PlayerRelations::ENEMIES)
+	{
+		//todo: bonus cashing	
+		int disguiseLevel = h->valOfBonuses(Selector::typeSubtype(Bonus::DISGUISED, 0));
+		
+		auto doBasicDisguise = [disguiseLevel](InfoAboutHero & info)		
+		{
+			int maxAIValue = 0;
+			const CCreature * mostStrong = nullptr;
+			
+			for(auto & elem : info.army)
+			{
+				if(elem.second.type->AIValue > maxAIValue)
+				{
+					maxAIValue = elem.second.type->AIValue;
+					mostStrong = elem.second.type;
+				}
+			}
+			
+			if(nullptr == mostStrong)//just in case
+				logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Unable to select most strong stack" << disguiseLevel;
+			else
+				for(auto & elem : info.army)
+				{
+					elem.second.type = mostStrong;
+				}
+		};
+		
+		auto doAdvancedDisguise = [accessFlag, &doBasicDisguise](InfoAboutHero & info)		
+		{
+			doBasicDisguise(info);
+			
+			for(auto & elem : info.army)
+				elem.second.count = 0;
+		};
+		
+		auto doExpertDisguise = [this,h](InfoAboutHero & info)		
+		{
+			for(auto & elem : info.army)
+				elem.second.count = 0;
+			
+			const auto factionIndex = getStartInfo(false)->playerInfos.at(h->tempOwner).castle;
+			
+			int maxAIValue = 0;
+			const CCreature * mostStrong = nullptr;
+			
+			for(auto creature : VLC->creh->creatures)
+			{
+				if(creature->faction == factionIndex && creature->AIValue > maxAIValue)
+				{
+					maxAIValue = creature->AIValue;
+					mostStrong = creature;
+				}
+			}
+			
+			if(nullptr != mostStrong) //possible, faction may have no creatures at all
+				for(auto & elem : info.army)
+					elem.second.type = mostStrong;
+		};				
+		
+		
+		switch (disguiseLevel)
+		{
+		case 0:
+			//no bonus at all - do nothing
+			break;		
+		case 1:
+			doBasicDisguise(dest);
+			break;		
+		case 2:
+			doAdvancedDisguise(dest);
+			break;		
+		case 3:
+			doExpertDisguise(dest);
+			break;		
+		default:
+			//invalid value
+			logGlobal->errorStream() << "CGameInfoCallback::getHeroInfo: Invalid DISGUISED bonus value " << disguiseLevel;
+			break;
+		}
+		
+	}
+	
 	return true;
 }
 

+ 2 - 2
lib/CGameInfoCallback.h

@@ -69,7 +69,7 @@ public:
 	const CGHeroInstance* getHero(ObjectInstanceID objid) const;
 	const CGHeroInstance* getHeroWithSubid(int subid) const;
 	int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
-	bool getHeroInfo(const CGObjectInstance *hero, InfoAboutHero &dest) 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
 	const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
@@ -99,7 +99,7 @@ public:
 	std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
 	std::string getTavernGossip(const CGObjectInstance * townOrTavern) const; 
 	EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
-	virtual bool getTownInfo(const CGObjectInstance *town, InfoAboutTown &dest) const;
+	virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
 	const CTown *getNativeTown(PlayerColor color) const;
 
 	//from gs

+ 4 - 0
lib/CGameInterface.h

@@ -5,6 +5,8 @@
 #include "IGameEventsReceiver.h"
 #include "CGameStateFwd.h"
 
+#include "spells/ViewSpellInt.h"
+
 /*
  * CGameInterface.h, part of VCMI engine
  *
@@ -93,6 +95,8 @@ public:
 	// all stacks operations between these objects become allowed, interface has to call onEnd when done
 	virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) = 0;
 	virtual void finish(){}; //if for some reason we want to end
+	
+	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};
 };
 
 class DLL_LINKAGE CDynLibHandler

+ 1 - 1
lib/CGameState.cpp

@@ -7,7 +7,7 @@
 #include "CBuildingHandler.h"
 #include "CGeneralTextHandler.h"
 #include "CTownHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "CHeroHandler.h"
 #include "mapObjects/CObjectHandler.h"
 #include "CCreatureHandler.h"

+ 1 - 1
lib/CGameState.h

@@ -67,7 +67,7 @@ namespace boost
 	class shared_mutex;
 }
 
-//numbers of creatures are exact numbers if detailed else they are quantity ids (0 - a few, 1 - several and so on; additionally -1 - unknown)
+//numbers of creatures are exact numbers if detailed else they are quantity ids (1 - a few, 2 - several and so on; additionally 0 - unknown)
 struct ArmyDescriptor : public std::map<SlotID, CStackBasicDescriptor>
 {
 	bool isDetailed;

+ 10 - 3
lib/CMakeLists.txt

@@ -54,6 +54,14 @@ set(lib_SRCS
 		rmg/CZoneGraphGenerator.cpp
 		rmg/CZonePlacer.cpp
 
+		spells/CSpellHandler.cpp
+		spells/ISpellMechanics.cpp
+        spells/AdventureSpellMechanics.cpp
+        spells/BattleSpellMechanics.cpp
+        spells/CreatureSpellMechanics.cpp
+        spells/CDefaultSpellMechanics.cpp
+        spells/ViewSpellInt.cpp
+
 		BattleAction.cpp
 		BattleHex.cpp
 		BattleState.cpp
@@ -71,7 +79,7 @@ set(lib_SRCS
 		CModHandler.cpp
 		CObstacleInstance.cpp
 		CRandomGenerator.cpp
-		CSpellHandler.cpp
+
 		CThreadHelper.cpp
 		CTownHandler.cpp
 		GameConstants.cpp
@@ -83,7 +91,6 @@ set(lib_SRCS
 		VCMI_Lib.cpp
 		VCMIDirs.cpp
 		IHandlerBase.cpp
-                SpellMechanics.cpp
 
 		IGameCallback.cpp
 		CGameInfoCallback.cpp
@@ -96,7 +103,7 @@ set(lib_SRCS
 		registerTypes/TypesClientPacks2.cpp
 		registerTypes/TypesMapObjects1.cpp
 		registerTypes/TypesMapObjects2.cpp
-				registerTypes/TypesMapObjects3.cpp
+		registerTypes/TypesMapObjects3.cpp
 		registerTypes/TypesPregamePacks.cpp
 		registerTypes/TypesServerPacks.cpp
 )

+ 1 - 1
lib/CModHandler.cpp

@@ -14,7 +14,7 @@
 #include "StringConstants.h"
 #include "CStopWatch.h"
 #include "IHandlerBase.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 
 /*
  * CModHandler.cpp, part of VCMI engine

+ 1 - 1
lib/CObstacleInstance.cpp

@@ -2,7 +2,7 @@
 #include "CObstacleInstance.h"
 #include "CHeroHandler.h"
 #include "VCMI_Lib.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 
 /*
  * CObstacleInstance.cpp, part of VCMI engine

+ 1 - 1
lib/CTownHandler.cpp

@@ -9,7 +9,7 @@
 #include "CModHandler.h"
 #include "CHeroHandler.h"
 #include "CArtHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "filesystem/Filesystem.h"
 #include "mapObjects/CObjectClassesHandler.h"
 #include "mapObjects/CObjectHandler.h"

+ 1 - 1
lib/GameConstants.cpp

@@ -16,7 +16,7 @@
 #include "mapObjects/CObjectClassesHandler.h"
 #include "CArtHandler.h"
 #include "CCreatureHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 
 const SlotID SlotID::COMMANDER_SLOT_PLACEHOLDER = SlotID(-2);
 const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);

+ 2 - 2
lib/GameConstants.h

@@ -895,6 +895,8 @@ public:
 	ESpellID num;
 };
 
+ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
+
 enum class ESpellSchool: ui8
 {
 	AIR 	= 0,
@@ -903,8 +905,6 @@ enum class ESpellSchool: ui8
 	EARTH 	= 3
 };
 
-ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
-
 // Typedef declarations
 typedef ui8 TFaction;
 typedef si64 TExpType;

+ 1 - 1
lib/HeroBonus.cpp

@@ -12,7 +12,7 @@
 #include "HeroBonus.h"
 
 #include "VCMI_Lib.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "CCreatureHandler.h"
 #include "CCreatureSet.h"
 #include "CHeroHandler.h"

+ 4 - 1
lib/HeroBonus.h

@@ -215,7 +215,10 @@ public:
 	BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
 	BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\
 	BONUS_NAME(SPOILS_OF_WAR) /*val * 10^-6 * gained exp resources of subtype will be given to hero after battle*/\
-	BONUS_NAME(BLOCK)
+	BONUS_NAME(BLOCK)\
+	BONUS_NAME(DISGUISED) /* subtype - spell level */\
+	BONUS_NAME(VISIONS) /* subtype - spell level */
+	
 
 #define BONUS_SOURCE_LIST \
 	BONUS_SOURCE(ARTIFACT)\

+ 1 - 1
lib/IGameCallback.cpp

@@ -12,7 +12,7 @@
 #include "IGameCallback.h"
 
 #include "CHeroHandler.h" // for CHeroHandler
-#include "CSpellHandler.h" // for CSpell
+#include "spells/CSpellHandler.h"// for CSpell
 #include "NetPacks.h"
 #include "CBonusTypeHandler.h"
 #include "CModHandler.h"

+ 18 - 0
lib/NetPacks.h

@@ -16,6 +16,8 @@
 #include "mapping/CMap.h"
 #include "CObstacleInstance.h"
 
+#include "spells/ViewSpellInt.h"
+
 /*
  * NetPacks.h, part of VCMI engine
  *
@@ -1685,6 +1687,22 @@ struct AdvmapSpellCast : public CPackForClient //108
 	}
 };
 
+struct ShowWorldViewEx : public CPackForClient //4000
+{
+	PlayerColor player;
+	
+	std::vector<ObjectPosInfo> objectPositions;
+	
+	ShowWorldViewEx(){type = 4000;}
+	
+	void applyCl(CClient *cl);
+	
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & player & objectPositions;
+	}	
+};
+
 /***********************************************************************************************************/
 
 struct CommitPackage : public CPackForServer

+ 2 - 2
lib/NetPacksLib.cpp

@@ -9,7 +9,7 @@
 #include "CModHandler.h"
 #include "VCMI_Lib.h"
 #include "mapping/CMap.h"
-#include "CSpellHandler.h"
+#include "spells/SpellMechanics.h"
 #include "CCreatureHandler.h"
 #include "CGameState.h"
 #include "BattleState.h"
@@ -1339,7 +1339,7 @@ DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
 
 	const CSpell * spell = SpellID(id).toSpell();
 	
-	spell->afterCast(gs->curB, this);
+	spell->applyBattle(gs->curB, this);
 }
 
 void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)

+ 0 - 1337
lib/SpellMechanics.cpp

@@ -1,1337 +0,0 @@
-/*
- * SpellMechanics.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 "SpellMechanics.h"
-
-#include "CObstacleInstance.h"
-#include "mapObjects/CGHeroInstance.h"
-#include "BattleState.h"
-#include "CRandomGenerator.h"
-
-#include "NetPacks.h"
-
-namespace SRSLPraserHelpers
-{
-	static int XYToHex(int x, int y)
-	{
-		return x + GameConstants::BFIELD_WIDTH * y;
-	}
-
-	static int XYToHex(std::pair<int, int> xy)
-	{
-		return XYToHex(xy.first, xy.second);
-	}
-
-	static int hexToY(int battleFieldPosition)
-	{
-		return battleFieldPosition/GameConstants::BFIELD_WIDTH;
-	}
-
-	static int hexToX(int battleFieldPosition)
-	{
-		int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
-		return pos;
-	}
-
-	static std::pair<int, int> hexToPair(int battleFieldPosition)
-	{
-		return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
-	}
-
-	//moves hex by one hex in given direction
-	//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
-	static std::pair<int, int> gotoDir(int x, int y, int direction)
-	{
-		switch(direction)
-		{
-		case 0: //top left
-			return std::make_pair((y%2) ? x-1 : x, y-1);
-		case 1: //top right
-			return std::make_pair((y%2) ? x : x+1, y-1);
-		case 2:  //right
-			return std::make_pair(x+1, y);
-		case 3: //right bottom
-			return std::make_pair((y%2) ? x : x+1, y+1);
-		case 4: //left bottom
-			return std::make_pair((y%2) ? x-1 : x, y+1);
-		case 5: //left
-			return std::make_pair(x-1, y);
-		default:
-			throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
-		}
-	}
-
-	static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
-	{
-		return gotoDir(xy.first, xy.second, direction);
-	}
-
-	static bool isGoodHex(std::pair<int, int> xy)
-	{
-		return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
-	}
-
-	//helper function for rangeInHexes
-	static std::set<ui16> getInRange(unsigned int center, int low, int high)
-	{
-		std::set<ui16> ret;
-		if(low == 0)
-		{
-			ret.insert(center);
-		}
-
-		std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
-		for(auto & elem : mainPointForLayer)
-			elem = hexToPair(center);
-
-		for(int it=1; it<=high; ++it) //it - distance to the center
-		{
-			for(int b=0; b<6; ++b)
-				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
-
-			if(it>=low)
-			{
-				std::pair<int, int> curHex;
-
-				//adding lines (A-b, B-c, C-d, etc)
-				for(int v=0; v<6; ++v)
-				{
-					curHex = mainPointForLayer[v];
-					for(int h=0; h<it; ++h)
-					{
-						if(isGoodHex(curHex))
-							ret.insert(XYToHex(curHex));
-						curHex = gotoDir(curHex, (v+2)%6);
-					}
-				}
-
-			} //if(it>=low)
-		}
-
-		return ret;
-	}
-}
-
-struct SpellCastContext
-{
-	SpellCastContext(std::vector<const CStack*> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
-		attackedCres(attackedCres), sc(sc), si(si){}; 
-	std::vector<const CStack*> & attackedCres;
-	BattleSpellCast & sc;
-	StacksInjured & si;
-};
-
-class DefaultSpellMechanics: public ISpellMechanics
-{
-public:
-	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
-	
-	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
-	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
-	
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
-	
-	//bool adventureCast(const SpellCastContext & context) const override; 
-	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
-	
-	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;
-protected:
-	
-	virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
-	
-	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
-	
-};
-
-class AcidBreathDamageMechnics: public DefaultSpellMechanics
-{
-public:
-	AcidBreathDamageMechnics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-class ChainLightningMechanics: public DefaultSpellMechanics
-{
-public:
-	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
-};
-
-class CloneMechanics: public DefaultSpellMechanics
-{
-public:
-	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
-};
-
-class CureMechanics: public DefaultSpellMechanics
-{
-public:
-	CureMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-	
-	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;	
-};
-
-class DeathStareMechnics: public DefaultSpellMechanics
-{
-public:
-	DeathStareMechnics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-class DispellHelpfulMechanics: public DefaultSpellMechanics
-{
-public:
-	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	
-	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;
-	
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
-};
-
-class DispellMechanics: public DefaultSpellMechanics
-{
-public:
-	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	
-	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;	
-};
-
-class HypnotizeMechanics: public DefaultSpellMechanics
-{
-public:
-	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
-}; 
-
-class ObstacleMechanics: public DefaultSpellMechanics
-{
-public:
-	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};		
-
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
-};
-
-class WallMechanics: public ObstacleMechanics
-{
-public:
-	WallMechanics(CSpell * s): ObstacleMechanics(s){};	
-	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;		
-};
-
-class RemoveObstacleMechanics: public DefaultSpellMechanics
-{
-public:
-	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-///all rising spells
-class RisingSpellMechanics: public DefaultSpellMechanics
-{
-public:
-	RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};		
-	
-};
-
-class SacrificeMechanics: public RisingSpellMechanics
-{
-public:
-	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};	
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-///all rising spells but SACRIFICE
-class SpecialRisingSpellMechanics: public RisingSpellMechanics
-{
-public:
-	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;						
-};
-
-class SummonMechanics: public DefaultSpellMechanics
-{
-public:
-	SummonMechanics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-class TeleportMechanics: public DefaultSpellMechanics
-{
-public:
-	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
-};
-
-///ISpellMechanics
-ISpellMechanics::ISpellMechanics(CSpell * s):
-	owner(s)
-{
-	
-}
-
-ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
-{
-	switch (s->id)
-	{
-	case SpellID::ACID_BREATH_DAMAGE:
-		return new AcidBreathDamageMechnics(s);
-	case SpellID::CHAIN_LIGHTNING:
-		return new ChainLightningMechanics(s);		
-	case SpellID::CLONE:
-		return new CloneMechanics(s);
-	case SpellID::CURE:
-		return new CureMechanics(s);
-	case SpellID::DEATH_STARE:
-		return new DeathStareMechnics(s);			
-	case SpellID::DISPEL:
-		return new DispellMechanics(s);	
-	case SpellID::DISPEL_HELPFUL_SPELLS:
-		return new DispellHelpfulMechanics(s);
-	case SpellID::FIRE_WALL:
-	case SpellID::FORCE_FIELD:
-		return new WallMechanics(s);		
-	case SpellID::HYPNOTIZE:
-		return new HypnotizeMechanics(s);
-	case SpellID::LAND_MINE:
-	case SpellID::QUICKSAND:
-		return new ObstacleMechanics(s);
-	case SpellID::REMOVE_OBSTACLE:
-		return new RemoveObstacleMechanics(s);
-	case SpellID::SACRIFICE:
-		return new SacrificeMechanics(s);
-	case SpellID::SUMMON_FIRE_ELEMENTAL:
-	case SpellID::SUMMON_EARTH_ELEMENTAL:
-	case SpellID::SUMMON_WATER_ELEMENTAL:
-	case SpellID::SUMMON_AIR_ELEMENTAL:
-		return new SummonMechanics(s);
-	case SpellID::TELEPORT:
-		return new TeleportMechanics(s);
-	default:		
-		if(s->isRisingSpell())
-			return new SpecialRisingSpellMechanics(s);
-		else	
-			return new DefaultSpellMechanics(s);		
-	}	
-}
-
-
-///DefaultSpellMechanics
-void DefaultSpellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	if (packet->castedByHero)
-	{
-		if (packet->side < 2)
-		{
-			battle->sides[packet->side].castSpellsCount++;
-		}
-	}
-	
-	//handle countering spells
-	for(auto stackID : packet->affectedCres)
-	{
-		if(vstd::contains(packet->resisted, stackID))
-			continue;
-
-		CStack * s = battle->getStack(stackID);
-		s->popBonuses([&](const Bonus * b) -> bool
-		{
-			//check for each bonus if it should be removed
-			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
-			const int spellID = isSpellEffect ? b->sid : -1;
-
-			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
-		});
-	}	
-}
-
-
-void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
-{
-	BattleSpellCast sc;
-	sc.side = parameters.casterSide;
-	sc.id = owner->id;
-	sc.skill = parameters.spellLvl;
-	sc.tile = parameters.destination;
-	sc.dmgToDisplay = 0;
-	sc.castedByHero = nullptr != parameters.caster;
-	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
-	sc.manaGained = 0;
-	
-	int spellCost = 0;	
-	
-	//calculate spell cost
-	if(parameters.caster) 
-	{
-		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
-
-		if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
-		{
-			int manaChannel = 0;
-			for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
-			{
-				if(stack->owner == parameters.secHero->tempOwner)
-				{
-					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
-				}
-			}
-			sc.manaGained = (manaChannel * spellCost) / 100;
-		}
-	}	
-	
-	
-	//calculating affected creatures for all spells
-	//must be vector, as in Chain Lightning order matters
-	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
-
-	auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
-	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
-	
-	for (auto cre : attackedCres)
-	{
-		sc.affectedCres.insert(cre->ID);
-	}
-	
-	//checking if creatures resist
-	//resistance is applied only to negative spells
-	if(owner->isNegative())
-	{
-		for(auto s : attackedCres)
-		{
-			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
-			
-			if(env->getRandomGenerator().nextInt(99) < prob)
-			{
-				sc.resisted.push_back(s->ID);
-			}
-		}
-	}
-	
-	StacksInjured si;	
-	SpellCastContext ctx(attackedCres, sc, si);
-	
-	applyBattleEffects(env, parameters, ctx);
-	
-	env->sendAndApply(&sc);
-	
-
-	//spend mana
-	if(parameters.caster) 
-	{
-		SetMana sm;
-		sm.absolute = false;
-		
-		sm.hid = parameters.caster->id;
-		sm.val = -spellCost;
-		
-		env->sendAndApply(&sm);
-		
-		if(sc.manaGained > 0)
-		{
-			assert(parameters.secHero);
-			
-			sm.hid = parameters.secHero->id;
-			sm.val = sc.manaGained;
-			env->sendAndApply(&sm);
-		}		
-	}
-	
-	if(!si.stacks.empty()) //after spellcast info shows
-		env->sendAndApply(&si);
-	
-	//reduce number of casts remaining
-	//TODO: this should be part of BattleSpellCast apply
-	if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING) 
-	{
-		assert(parameters.casterStack);
-		
-		BattleSetStackProperty ssp;
-		ssp.stackID = parameters.casterStack->ID;
-		ssp.which = BattleSetStackProperty::CASTS;
-		ssp.val = -1;
-		ssp.absolute = false;
-		env->sendAndApply(&ssp);
-	}
-
-	//Magic Mirror effect
-	if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
-	{
-		for(auto & attackedCre : attackedCres)
-		{
-			int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
-			if(mirrorChance > env->getRandomGenerator().nextInt(99))
-			{
-				std::vector<const CStack *> mirrorTargets;
-				auto battleStacks = parameters.cb->battleGetAllStacks(true);
-				for(auto & battleStack : battleStacks)
-				{
-					if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
-					{
-						if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
-							mirrorTargets.push_back(battleStack);
-					}
-				}
-				if(!mirrorTargets.empty())
-				{
-					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
-					
-					BattleSpellCastParameters mirrorParameters = parameters;
-					mirrorParameters.spellLvl = 0;
-					mirrorParameters.casterSide = 1-parameters.casterSide;
-					mirrorParameters.casterColor = (attackedCre)->owner;
-					mirrorParameters.caster = nullptr;
-					mirrorParameters.destination = targetHex;
-					mirrorParameters.secHero = parameters.caster;
-					mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
-					mirrorParameters.casterStack = (attackedCre);
-					mirrorParameters.selectedStack = nullptr;
-					
-					battleCast(env, mirrorParameters);					
-				}
-			}
-		}
-	}	
-}
-
-int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
-{
-	if(!caster)
-	{
-		if (!usedSpellPower)
-			return 3; //default duration of all creature spells
-		else
-			return usedSpellPower; //use creature spell power
-	}
-	switch(owner->id)
-	{
-	case SpellID::FRENZY:
-		return 1;
-	default: //other spells
-		return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
-	}	
-}
-
-void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	//applying effects
-	if(owner->isOffensiveSpell())
-	{
-		int spellDamage = 0;
-		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
-		{
-			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
-			if(unitSpellPower)
-				ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
-			else //Faerie Dragon
-			{
-				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
-				ctx.sc.dmgToDisplay = 0;
-			}
-		}
-		int chainLightningModifier = 0;
-		for(auto & attackedCre : ctx.attackedCres)
-		{
-			if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
-				continue;
-
-			BattleStackAttacked bsa;
-			if(spellDamage)
-				bsa.damageAmount = spellDamage >> chainLightningModifier;
-			else
-				bsa.damageAmount =  owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
-
-			ctx.sc.dmgToDisplay += bsa.damageAmount;
-
-			bsa.stackAttacked = (attackedCre)->ID;
-			if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
-				bsa.attackerID = parameters.casterStack->ID;
-			else
-				bsa.attackerID = -1;
-			(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
-			ctx.si.stacks.push_back(bsa);
-
-			if(owner->id == SpellID::CHAIN_LIGHTNING)
-				++chainLightningModifier;
-		}
-	}
-	
-	if(owner->hasEffects())
-	{
-		int stackSpellPower = 0;
-		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
-		{
-			stackSpellPower =  parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
-		}
-		SetStackEffect sse;
-		Bonus pseudoBonus;
-		pseudoBonus.sid = owner->id;
-		pseudoBonus.val = parameters.spellLvl;
-		pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
-		CStack::stackEffectToFeature(sse.effect, pseudoBonus);
-		if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
-		{
-			sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
-		}
-		if(owner->id == SpellID::BIND &&  parameters.casterStack)//bind
-		{
-			sse.effect.back().additionalInfo =  parameters.casterStack->ID; //we need to know who casted Bind
-		}
-		const Bonus * bonus = nullptr;
-		if(parameters.caster)
-			bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
-		//TODO does hero specialty should affects his stack casting spells?
-
-		si32 power = 0;
-		for(const CStack * affected : ctx.attackedCres)
-		{
-			if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
-				continue;
-			sse.stacks.push_back(affected->ID);
-
-			//Apply hero specials - peculiar enchants
-			const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
-			if(bonus)
-			{
-				switch(bonus->additionalInfo)
-				{
-					case 0: //normal
-					{
-						switch(tier)
-						{
-							case 1: case 2:
-								power = 3;
-							break;
-							case 3: case 4:
-								power = 2;
-							break;
-							case 5: case 6:
-								power = 1;
-							break;
-						}
-						Bonus specialBonus(sse.effect.back());
-						specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
-						sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
-					}
-					break;
-					case 1: //only Coronius as yet
-					{
-						power = std::max(5 - tier, 0);
-						Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
-						specialBonus.sid = owner->id;
-						sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
-					}
-					break;
-				}
-			}
-			if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
-			{
-				int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
-				Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
-				specialBonus.valType = Bonus::PERCENT_TO_ALL;
-				specialBonus.sid = owner->id;
-				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
-			}
-		}
-
-		if(!sse.stacks.empty())
-			env->sendAndApply(&sse);
-
-	}
-	
-	if(owner->isHealingSpell())
-	{
-		int hpGained = 0;
-		if(parameters.casterStack)
-		{
-			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
-			if(unitSpellPower)
-				hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
-			else //Faerie Dragon-like effect - unused so far
-				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
-		}
-		StacksHealedOrResurrected shr;
-		shr.lifeDrain = false;
-		shr.tentHealing = false;
-		for(auto & attackedCre : ctx.attackedCres)
-		{
-			StacksHealedOrResurrected::HealInfo hi;
-			hi.stackID = (attackedCre)->ID;
-			if (parameters.casterStack) //casted by creature
-			{
-				const bool resurrect = owner->isRisingSpell();
-				if (hpGained)
-				{
-					//archangel
-					hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
-				}
-				else
-				{
-					//any typical spell (commander's cure or animate dead)
-					int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl);
-					hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
-				}					
-			}
-			else
-				hi.healedHP = owner->calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
-			hi.lowLevelResurrection = parameters.spellLvl <= 1;
-			shr.healedStacks.push_back(hi);
-		}
-		if(!shr.healedStacks.empty())
-			env->sendAndApply(&shr);
-	}		
-}
-
-
-std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
-{
-	using namespace SRSLPraserHelpers;
-	
-	std::vector<BattleHex> ret;
-	std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
-
-	if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
-	{
-		std::string number1, number2;
-		int beg, end;
-		bool readingFirst = true;
-		for(auto & elem : rng)
-		{
-			if(std::isdigit(elem) ) //reading number
-			{
-				if(readingFirst)
-					number1 += elem;
-				else
-					number2 += elem;
-			}
-			else if(elem == ',') //comma
-			{
-				//calculating variables
-				if(readingFirst)
-				{
-					beg = atoi(number1.c_str());
-					number1 = "";
-				}
-				else
-				{
-					end = atoi(number2.c_str());
-					number2 = "";
-				}
-				//obtaining new hexes
-				std::set<ui16> curLayer;
-				if(readingFirst)
-				{
-					curLayer = getInRange(centralHex, beg, beg);
-				}
-				else
-				{
-					curLayer = getInRange(centralHex, beg, end);
-					readingFirst = true;
-				}
-				//adding abtained hexes
-				for(auto & curLayer_it : curLayer)
-				{
-					ret.push_back(curLayer_it);
-				}
-
-			}
-			else if(elem == '-') //dash
-			{
-				beg = atoi(number1.c_str());
-				number1 = "";
-				readingFirst = false;
-			}
-		}
-	}
-
-	//remove duplicates (TODO check if actually needed)
-	range::unique(ret);
-	return ret;		
-}
-
-
-std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
-{
-	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
-	
-	const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
-	const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
-
-	const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
-	
-	//TODO: more generic solution for mass spells
-	if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
-	{
-		for(BattleHex hex : attackedHexes)
-		{
-			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
-			{
-				attackedCres.insert(st);
-			}
-		}
-	}
-	else if(ti.type == CSpell::CREATURE)
-	{
-		auto predicate = [=](const CStack * s){
-			const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
-			const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
-			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
-	
-			//for single target spells select stacks covering destination tile
-			const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
-			//handle smart targeting
-			const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
-			
-			return rangeCovers && positivenessFlag && validTarget;		
-		};
-		
-		TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
-		
-		if(ti.massive)
-		{
-			//for massive spells add all targets
-			for (auto stack : stacks)
-				attackedCres.insert(stack);
-
-		}
-		else
-		{
-			//for single target spells we must select one target. Alive stack is preferred (issue #1763)
-			for(auto stack : stacks)
-			{
-				if(stack->alive())
-				{
-					attackedCres.insert(stack);
-					break;
-				}				
-			}	
-			
-			if(attackedCres.empty() && !stacks.empty())
-			{
-				attackedCres.insert(stacks.front());
-			}						
-		}
-	}
-	else //custom range from attackedHexes
-	{
-		for(BattleHex hex : attackedHexes)
-		{
-			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
-				attackedCres.insert(st);
-		}
-	}	
-	
-	return attackedCres;
-}
-
-
-ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
-{
-	//by default use general algorithm
-	return owner->isImmuneBy(obj);
-}
-
-///AcidBreathDamageMechnics
-void AcidBreathDamageMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
-	
-	for(auto & attackedCre : ctx.attackedCres) //no immunities
-	{
-		BattleStackAttacked bsa;
-		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
-		bsa.spellID = owner->id;
-		bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
-		bsa.stackAttacked = (attackedCre)->ID;
-		bsa.attackerID = -1;
-		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
-		ctx.si.stacks.push_back(bsa);
-	}	
-}
-
-///ChainLightningMechanics
-std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
-{
-	std::set<const CStack* > attackedCres;
-	
-	std::set<BattleHex> possibleHexes;
-	for(auto stack : ctx.cb->battleGetAllStacks())
-	{
-		if(stack->isValidTarget())
-		{
-			for(auto hex : stack->getHexes())
-			{
-				possibleHexes.insert (hex);
-			}
-		}
-	}
-	int targetsOnLevel[4] = {4, 4, 5, 5};
-
-	BattleHex lightningHex = ctx.destination;
-	for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
-	{
-		auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
-		if(!stack)
-			break;
-		attackedCres.insert (stack);
-		for(auto hex : stack->getHexes())
-		{
-			possibleHexes.erase(hex); //can't hit same place twice
-		}
-		if(possibleHexes.empty()) //not enough targets
-			break;
-		lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
-	}	
-		
-	return attackedCres;
-}
-
-///CloneMechanics
-void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	const CStack * clonedStack = nullptr;
-	if(ctx.attackedCres.size())
-		clonedStack = *ctx.attackedCres.begin();
-	if(!clonedStack)
-	{
-		env->complain ("No target stack to clone!");
-		return;
-	}
-	const int attacker = !(bool)parameters.casterSide; 
-
-	BattleStackAdded bsa;
-	bsa.creID = clonedStack->type->idNumber;
-	bsa.attacker = attacker;
-	bsa.summoned = true;
-	bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it
-	bsa.amount = clonedStack->count;
-	env->sendAndApply(&bsa);
-
-	BattleSetStackProperty ssp;
-	ssp.stackID = bsa.newStackID;//we know stack ID after apply
-	ssp.which = BattleSetStackProperty::CLONED;
-	ssp.val = 0;
-	ssp.absolute = 1;
-	env->sendAndApply(&ssp);	
-}
-
-ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
-{
-	//can't clone already cloned creature
-	if(vstd::contains(obj->state, EBattleStackState::CLONED))
-		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	//TODO: how about stacks casting Clone?
-	//currently Clone casted by stack is assumed Expert level
-	ui8 schoolLevel;
-	if(caster)
-	{
-		schoolLevel = caster->getSpellSchoolLevel(owner);
-	}
-	else
-	{
-		schoolLevel = 3;
-	}
-
-	if(schoolLevel < 3)
-	{
-		int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
-		int creLevel = obj->getCreature()->level;
-		if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-	//use default algorithm only if there is no mechanics-related problem		
-	return DefaultSpellMechanics::isImmuneByStack(caster, obj);	
-}
-
-///CureMechanics
-void CureMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::afterCast(battle, packet);
-	
-	for(auto stackID : packet->affectedCres)
-	{
-		if(vstd::contains(packet->resisted, stackID))
-		{
-			logGlobal->errorStream() << "Resistance to positive spell CURE";
-			continue;
-		}			
-
-		CStack *s = battle->getStack(stackID);
-		s->popBonuses([&](const Bonus *b) -> bool
-		{
-			if(b->source == Bonus::SPELL_EFFECT)
-			{
-				CSpell * sp = SpellID(b->sid).toSpell();
-				return sp->isNegative();
-			}
-			return false; //not a spell effect
-		});
-	}		
-}
-
-
-///DeathStareMechnics
-void DeathStareMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
-	
-	if(!ctx.attackedCres.empty())
-		vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
-	
-	for(auto & attackedCre : ctx.attackedCres)
-	{
-		BattleStackAttacked bsa;
-		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
-		bsa.spellID = owner->id;
-		bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
-		bsa.stackAttacked = (attackedCre)->ID;
-		bsa.attackerID = -1;
-		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
-		ctx.si.stacks.push_back(bsa);
-	}	
-}
-
-
-///DispellHelpfulMechanics
-void DispellHelpfulMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::afterCast(battle, packet);
-	
-	for(auto stackID : packet->affectedCres)
-	{
-		if(vstd::contains(packet->resisted, stackID))
-			continue;
-
-		CStack *s = battle->getStack(stackID);
-		s->popBonuses([&](const Bonus *b) -> bool
-		{
-			return Selector::positiveSpellEffects(b);
-		});
-	}	
-}
-
-
-ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster,  const CStack * obj) const
-{
-	TBonusListPtr spellBon = obj->getSpellBonuses();
-	bool hasPositiveSpell = false;
-	for(const Bonus * b : *spellBon)
-	{
-		if(SpellID(b->sid).toSpell()->isPositive())
-		{
-			hasPositiveSpell = true;
-			break;
-		}
-	}
-	if(!hasPositiveSpell)
-	{
-		return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
-	}
-	
-	//use default algorithm only if there is no mechanics-related problem		
-	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
-}
-
-///DispellMechanics
-void DispellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::afterCast(battle, packet);
-	
-	for(auto stackID : packet->affectedCres)
-	{
-		if(vstd::contains(packet->resisted, stackID))
-			continue;
-
-		CStack *s = battle->getStack(stackID);
-		s->popBonuses([&](const Bonus *b) -> bool
-		{
-			return Selector::sourceType(Bonus::SPELL_EFFECT)(b);
-		});
-	}	
-}
-
-
-///HypnotizeMechanics
-ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
-{
-	if(nullptr != caster) //do not resist hypnotize casted after attack, for example
-	{
-		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
-		ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
-		//apply 'damage' bonus for hypnotize, including hero specialty
-		ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
-			* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
-		if (subjectHealth > maxHealth)
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}			
-	return DefaultSpellMechanics::isImmuneByStack(caster, obj);
-}
-
-///ObstacleMechanics
-void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	auto placeObstacle = [&, this](BattleHex pos)
-	{
-		static int obstacleIdToGive =  parameters.cb->obstacles.size()
-									? (parameters.cb->obstacles.back()->uniqueID+1)
-									: 0;
-
-		auto obstacle = make_shared<SpellCreatedObstacle>();
-		switch(owner->id) // :/
-		{
-		case SpellID::QUICKSAND:
-			obstacle->obstacleType = CObstacleInstance::QUICKSAND;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::LAND_MINE:
-			obstacle->obstacleType = CObstacleInstance::LAND_MINE;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::FIRE_WALL:
-			obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		case SpellID::FORCE_FIELD:
-			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		default:
-			//this function cannot be used with spells that do not create obstacles
-			assert(0);
-		}
-
-		obstacle->pos = pos;
-		obstacle->casterSide = parameters.casterSide;
-		obstacle->ID = owner->id;
-		obstacle->spellLevel = parameters.spellLvl;
-		obstacle->casterSpellPower = parameters.usedSpellPower;
-		obstacle->uniqueID = obstacleIdToGive++;
-
-		BattleObstaclePlaced bop;
-		bop.obstacle = obstacle;
-		env->sendAndApply(&bop);
-	};	
-	
-	switch(owner->id)
-	{
-	case SpellID::QUICKSAND:
-	case SpellID::LAND_MINE:
-		{
-			std::vector<BattleHex> availableTiles;
-			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
-			{
-				BattleHex hex = i;
-				if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
-					availableTiles.push_back(hex);
-			}
-			boost::range::random_shuffle(availableTiles);
-
-			const int patchesForSkill[] = {4, 4, 6, 8};
-			const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
-
-			//land mines or quicksand patches are handled as spell created obstacles
-			for (int i = 0; i < patchesToPut; i++)
-				placeObstacle(availableTiles.at(i));
-		}
-
-		break;
-	case SpellID::FORCE_FIELD:
-		placeObstacle(parameters.destination);
-		break;
-	case SpellID::FIRE_WALL:
-		{
-			//fire wall is build from multiple obstacles - one fire piece for each affected hex
-			auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
-			for(BattleHex hex : affectedHexes)
-				placeObstacle(hex);
-		}
-		break;
-	default:		
-		assert(0);
-	}			
-}
-
-
-///WallMechanics
-std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
-{
-	using namespace SRSLPraserHelpers;
-
-	std::vector<BattleHex> ret;	
-	
-	//Special case - shape of obstacle depends on caster's side
-	//TODO make it possible through spell_info config
-
-	BattleHex::EDir firstStep, secondStep;
-	if(side)
-	{
-		firstStep = BattleHex::TOP_LEFT;
-		secondStep = BattleHex::TOP_RIGHT;
-	}
-	else
-	{
-		firstStep = BattleHex::TOP_RIGHT;
-		secondStep = BattleHex::TOP_LEFT;
-	}
-
-	//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
-	auto addIfValid = [&](BattleHex hex)
-	{
-		if(hex.isValid())
-			ret.push_back(hex);
-		else if(outDroppedHexes)
-			*outDroppedHexes = true;
-	};
-
-	ret.push_back(centralHex);
-	addIfValid(centralHex.moveInDir(firstStep, false));
-	if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
-		addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
-
-	return ret;	
-}
-
-///RemoveObstacleMechanics
-void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
-	{
-		ObstaclesRemoved obr;
-		obr.obstacles.insert(obstacleToRemove->uniqueID);
-		env->sendAndApply(&obr);
-	}
-	else
-		env->complain("There's no obstacle to remove!");	
-}
-
-///SpecialRisingSpellMechanics
-void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
-
-	if(parameters.selectedStack == parameters.cb->battleActiveStack())
-	//set another active stack than the one removed, or bad things will happen
-	//TODO: make that part of BattleStacksRemoved? what about client update?
-	{
-		//makeStackDoNothing(gs->curB->getStack (selectedStack));
-
-		BattleSetActiveStack sas;
-
-		//std::vector<const CStack *> hlp;
-		//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
-
-		//if(hlp.size())
-		//{
-		//	sas.stack = hlp[0]->ID;
-		//}
-		//else
-		//	complain ("No new stack to activate!");
-		sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
-		env->sendAndApply(&sas);
-
-	}
-	BattleStacksRemoved bsr;
-	bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
-	env->sendAndApply(&bsr);
-		
-}
-
-
-///SpecialRisingSpellMechanics
-ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
-{
-	// following does apply to resurrect and animate dead(?) only
-	// for sacrifice health calculation and health limit check don't matter
-
-	if(obj->count >= obj->baseAmount)
-		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	
-	if(caster) //FIXME: Archangels can cast immune stack
-	{
-		auto maxHealth = owner->calculateHealedHP(caster, obj);
-		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}	
-	
-	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
-}
-
-///SummonMechanics
-void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	//todo: make configurable
-	CreatureID creID = CreatureID::NONE;
-	switch(owner->id)
-	{
-		case SpellID::SUMMON_FIRE_ELEMENTAL:
-			creID = CreatureID::FIRE_ELEMENTAL;
-			break;
-		case SpellID::SUMMON_EARTH_ELEMENTAL:
-			creID = CreatureID::EARTH_ELEMENTAL;
-			break;
-		case SpellID::SUMMON_WATER_ELEMENTAL:
-			creID = CreatureID::WATER_ELEMENTAL;
-			break;
-		case SpellID::SUMMON_AIR_ELEMENTAL:
-			creID = CreatureID::AIR_ELEMENTAL;
-			break;
-		default:
-			env->complain("Unable to determine summoned creature");
-			return;
-	}
-
-	BattleStackAdded bsa;
-	bsa.creID = creID;
-	bsa.attacker = !(bool)parameters.casterSide;
-	bsa.summoned = true;
-	bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it
-
-	//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
-	int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
-
-	bsa.amount = parameters.usedSpellPower
-		* owner->getPower(parameters.spellLvl)
-		* (100 + percentBonus) / 100.0; //new feature - percentage bonus
-	if(bsa.amount)
-		env->sendAndApply(&bsa);
-	else
-		env->complain("Summoning didn't summon any!");	
-}
-
-
-///TeleportMechanics
-void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
-{
-	BattleStackMoved bsm;
-	bsm.distance = -1;
-	bsm.stack = parameters.selectedStack->ID;
-	std::vector<BattleHex> tiles;
-	tiles.push_back(parameters.destination);
-	bsm.tilesToMove = tiles;
-	bsm.teleporting = true;
-	env->sendAndApply(&bsm);	
-}
-	
-

+ 1 - 1
lib/VCMI_Lib.cpp

@@ -19,7 +19,7 @@
 #include "mapObjects/CObjectHandler.h"
 #include "CTownHandler.h"
 #include "CBuildingHandler.h"
-#include "CSpellHandler.h"
+#include "spells/CSpellHandler.h"
 #include "CGeneralTextHandler.h"
 #include "CModHandler.h"
 #include "IGameEventsReceiver.h"

+ 14 - 4
lib/VCMI_lib.cbp

@@ -150,8 +150,6 @@
 		<Unit filename="CRandomGenerator.h" />
 		<Unit filename="CScriptingModule.h" />
 		<Unit filename="CSoundBase.h" />
-		<Unit filename="CSpellHandler.cpp" />
-		<Unit filename="CSpellHandler.h" />
 		<Unit filename="CStopWatch.h" />
 		<Unit filename="CThreadHelper.cpp" />
 		<Unit filename="CThreadHelper.h" />
@@ -185,8 +183,6 @@
 		<Unit filename="ResourceSet.cpp" />
 		<Unit filename="ResourceSet.h" />
 		<Unit filename="ScopeGuard.h" />
-		<Unit filename="SpellMechanics.cpp" />
-		<Unit filename="SpellMechanics.h" />
 		<Unit filename="StartInfo.h" />
 		<Unit filename="StdInc.h">
 			<Option weight="0" />
@@ -294,6 +290,20 @@
 		<Unit filename="rmg/CZoneGraphGenerator.h" />
 		<Unit filename="rmg/CZonePlacer.cpp" />
 		<Unit filename="rmg/CZonePlacer.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/ViewSpellInt.cpp" />
+		<Unit filename="spells/ViewSpellInt.h" />
 		<Unit filename="vcmi_endian.h" />
 		<Extensions>
 			<code_completion />

+ 15 - 4
lib/VCMI_lib.vcxproj

@@ -184,11 +184,16 @@
     <ClCompile Include="CModHandler.cpp" />
     <ClCompile Include="CObstacleInstance.cpp" />
     <ClCompile Include="Connection.cpp" />
-    <ClCompile Include="CSpellHandler.cpp" />
-    <ClCompile Include="SpellMechanics.cpp" />
     <ClCompile Include="CThreadHelper.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="CRandomGenerator.cpp" />
+    <ClCompile Include="spells\CSpellHandler.cpp" />
+    <ClCompile Include="spells\ISpellMechanics.cpp" />
+    <ClCompile Include="spells\AdventureSpellMechanics.cpp" />
+    <ClCompile Include="spells\BattleSpellMechanics.cpp" />
+    <ClCompile Include="spells\CreatureSpellMechanics.cpp" />
+    <ClCompile Include="spells\CDefaultSpellMechanics.cpp" />
+    <ClCompile Include="spells\ViewSpellInt.cpp" />
     <ClCompile Include="filesystem\AdapterLoaders.cpp" />
     <ClCompile Include="filesystem\CArchiveLoader.cpp" />
     <ClCompile Include="filesystem\CBinaryReader.cpp" />
@@ -287,7 +292,6 @@
     <ClInclude Include="ConstTransitivePtr.h" />
     <ClInclude Include="CRandomGenerator.h" />
     <ClInclude Include="CScriptingModule.h" />
-    <ClInclude Include="CSpellHandler.h" />
     <ClInclude Include="CStopWatch.h" />
     <ClInclude Include="CThreadHelper.h" />
     <ClInclude Include="CTownHandler.h" />
@@ -353,7 +357,14 @@
     <ClInclude Include="rmg\CZoneGraphGenerator.h" />
     <ClInclude Include="rmg\CZonePlacer.h" />
     <ClInclude Include="rmg\float3.h" />
-    <ClInclude Include="SpellMechanics.h" />
+    <ClInclude Include="spells\AdventureSpellMechanics.h" />
+    <ClInclude Include="spells\BattleSpellMechanics.h" />
+    <ClInclude Include="spells\CDefaultSpellMechanics.h" />
+    <ClInclude Include="spells\CreatureSpellMechanics.h" />
+    <ClInclude Include="spells\CSpellHandler.h" />
+    <ClInclude Include="spells\ISpellMechanics.h" />
+    <ClInclude Include="spells\SpellMechanics.h" />
+    <ClInclude Include="spells\ViewSpellInt.h" />
     <ClInclude Include="StartInfo.h" />
     <ClInclude Include="StdInc.h" />
     <ClInclude Include="UnlockGuard.h" />

+ 47 - 7
lib/VCMI_lib.vcxproj.filters

@@ -26,6 +26,9 @@
     <Filter Include="mapObjects">
       <UniqueIdentifier>{ee24c7f7-f4e2-4d35-b994-94a6e29ea92f}</UniqueIdentifier>
     </Filter>
+    <Filter Include="spells">
+      <UniqueIdentifier>{bda963b1-00e1-412a-9b44-f5cd3f8e9e33}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="BattleAction.cpp" />
@@ -35,7 +38,6 @@
     <ClCompile Include="CCreatureHandler.cpp" />
     <ClCompile Include="CGeneralTextHandler.cpp" />
     <ClCompile Include="CHeroHandler.cpp" />
-    <ClCompile Include="CSpellHandler.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="CCreatureSet.cpp" />
     <ClCompile Include="CGameState.cpp" />
@@ -205,7 +207,27 @@
     </ClCompile>
     <ClCompile Include="registerTypes\TypesMapObjects3.cpp" />
     <ClCompile Include="IHandlerBase.cpp" />
-    <ClCompile Include="SpellMechanics.cpp" />
+    <ClCompile Include="spells\AdventureSpellMechanics.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\CDefaultSpellMechanics.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\CSpellHandler.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\ViewSpellInt.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\ISpellMechanics.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\BattleSpellMechanics.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
+    <ClCompile Include="spells\CreatureSpellMechanics.cpp">
+      <Filter>spells</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="CCreatureSet.h">
@@ -259,9 +281,6 @@
     <ClInclude Include="ConstTransitivePtr.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="CSpellHandler.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="CTownHandler.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -493,8 +512,29 @@
     <ClInclude Include="CGameStateFwd.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="SpellMechanics.h">
-      <Filter>Header Files</Filter>
+    <ClInclude Include="spells\AdventureSpellMechanics.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\BattleSpellMechanics.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\CreatureSpellMechanics.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\CSpellHandler.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\ISpellMechanics.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\ViewSpellInt.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\CDefaultSpellMechanics.h">
+      <Filter>spells</Filter>
+    </ClInclude>
+    <ClInclude Include="spells\SpellMechanics.h">
+      <Filter>spells</Filter>
     </ClInclude>
   </ItemGroup>
 </Project>

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -15,7 +15,7 @@
 #include "../CGeneralTextHandler.h"
 #include "../CSoundBase.h"
 #include "CommonConstructors.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
 

+ 22 - 1
lib/mapObjects/CGHeroInstance.cpp

@@ -16,7 +16,7 @@
 #include "../CHeroHandler.h"
 #include "../CModHandler.h"
 #include "../CSoundBase.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "CObjectClassesHandler.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
@@ -1343,3 +1343,24 @@ void CGHeroInstance::levelUpAutomatically()
 		levelUp(proposedSecondarySkills);
 	}
 }
+
+bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subtype) const
+{
+	//VISIONS spell support
+	
+	const std::string cached = boost::to_string((boost::format("type_%d__subtype_%d") % Bonus::VISIONS % subtype)); 
+	
+	const int visionsMultiplier = valOfBonuses(Selector::typeSubtype(Bonus::VISIONS,subtype), cached);
+	
+	int visionsRange =  visionsMultiplier * getPrimSkillLevel(PrimarySkill::SPELL_POWER);
+		
+	if (visionsMultiplier > 0) 	
+		vstd::amax(visionsRange, 3); //minimum range is 3 tiles, but only if VISIONS bonus present
+	
+	const int distance = target->pos.dist2d(getPosition(false));
+	
+	logGlobal->debug(boost::to_string(boost::format("Visions: dist %d, mult %d, range %d") % distance % visionsMultiplier % visionsRange));
+	
+	return (distance < visionsRange) && (target->pos.z == pos.z);	
+}
+

+ 2 - 0
lib/mapObjects/CGHeroInstance.h

@@ -193,6 +193,8 @@ public:
 	void Updatespecialty();
 	void recreateSecondarySkillsBonuses();
 	void updateSkill(SecondarySkill which, int val);
+	
+	bool hasVisions(const CGObjectInstance * target, const int subtype) const;
 
 	CGHeroInstance();
 	virtual ~CGHeroInstance();

+ 1 - 1
lib/mapObjects/CGPandoraBox.cpp

@@ -14,7 +14,7 @@
 #include "../NetPacks.h"
 #include "../CSoundBase.h"
 
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../StartInfo.h"
 #include "../IGameCallback.h"
 

+ 1 - 1
lib/mapObjects/JsonRandom.cpp

@@ -20,7 +20,7 @@
 #include "../CArtHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CCreatureSet.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 
 namespace JsonRandom
 {

+ 36 - 2
lib/mapObjects/MiscObjects.cpp

@@ -17,7 +17,7 @@
 #include "../CModHandler.h"
 
 #include "CObjectClassesHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
 
@@ -101,7 +101,41 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 
 std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 {
-	std::string hoverName = getHoverText(hero->tempOwner);
+	std::string hoverName;
+	if(hero->hasVisions(this, 0))
+	{		
+		MetaString ms;
+		ms << stacks.begin()->second->count;
+		ms << " " ;
+		ms.addTxt(MetaString::CRE_PL_NAMES,subID);
+		
+		ms << "\n";
+		
+		int decision = takenAction(hero, true);
+		
+		switch (decision)
+		{
+		case FIGHT:
+			ms.addTxt(MetaString::GENERAL_TXT,246);
+			break;
+		case FLEE:
+			ms.addTxt(MetaString::GENERAL_TXT,245);
+			break;
+		case JOIN_FOR_FREE:
+			ms.addTxt(MetaString::GENERAL_TXT,243);
+			break;					
+		default: //decision = cost in gold
+			VLC->generaltexth->allTexts[244];
+			ms << boost::to_string(boost::format(VLC->generaltexth->allTexts[244]) % decision);			
+			break;
+		}		
+
+		ms.toString(hoverName);		
+	}
+	else
+	{
+		hoverName = getHoverText(hero->tempOwner);	
+	}	
 
 	const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"];
 

+ 1 - 1
lib/mapping/CMap.cpp

@@ -9,7 +9,7 @@
 #include "../mapObjects/CObjectClassesHandler.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../CGeneralTextHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "CMapEditManager.h"
 
 SHeroName::SHeroName() : heroId(-1)

+ 1 - 1
lib/mapping/MapFormatH3M.cpp

@@ -16,7 +16,7 @@
 
 #include "../CStopWatch.h"
 #include "../filesystem/Filesystem.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CCreatureHandler.h"
 #include "../CGeneralTextHandler.h"
 #include "../CHeroHandler.h"

+ 1 - 1
lib/registerTypes/RegisterTypes.cpp

@@ -13,7 +13,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -248,6 +248,7 @@ void registerTypesClientPacks1(Serializer &s)
 	s.template registerType<CPackForClient, HeroVisit>();
 	s.template registerType<CPackForClient, SetCommanderProperty>();
 	s.template registerType<CPackForClient, ChangeObjectVisitors>();
+	s.template registerType<CPackForClient, ShowWorldViewEx>();
 }
 
 template<typename Serializer>

+ 1 - 1
lib/registerTypes/TypesClientPacks1.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesClientPacks2.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesMapObjects1.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesMapObjects2.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesMapObjects3.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesPregamePacks.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 1 - 1
lib/registerTypes/TypesServerPacks.cpp

@@ -12,7 +12,7 @@
 #include "../VCMI_Lib.h"
 #include "../CArtHandler.h"
 #include "../CHeroHandler.h"
-#include "../CSpellHandler.h"
+#include "../spells/CSpellHandler.h"
 #include "../CTownHandler.h"
 #include "../mapping/CCampaignHandler.h"
 #include "../NetPacks.h"

+ 6 - 24
lib/rmg/CRmgTemplateZone.cpp

@@ -17,7 +17,7 @@
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
 #include "../CCreatureHandler.h"
-#include "../CSpellHandler.h" //for choosing random spells
+#include "../spells/CSpellHandler.h" //for choosing random spells
 
 #include "../mapObjects/CommonConstructors.h"
 #include "../mapObjects/MapObjects.h" //needed to resolve templates for CommonConstructors.h
@@ -2187,9 +2187,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	}
 
 	//Pandora with 15 spells of certain school
-	for (int i = 1; i <= 4; i++)
+	for (int i = 0; i < 4; i++)
 	{
-		oi.generateObject = [i, gen]() -> CGObjectInstance *
+		oi.generateObject = [i,gen]() -> CGObjectInstance *
 		{
 			auto obj = new CGPandoraBox();
 			obj->ID = Obj::PANDORAS_BOX;
@@ -2198,27 +2198,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 			std::vector <CSpell *> spells;
 			for (auto spell : VLC->spellh->objects)
 			{
-				if (!spell->isSpecialSpell())
-				{
-					bool school = false; //TODO: we could have better interface for iterating schools
-					switch (i)
-					{
-					case 1:
-						school = spell->air;
-						break;
-					case 2:
-						school = spell->earth;
-						break;
-					case 3:
-						school = spell->fire;
-						break;
-					case 4:
-						school = spell->water;
-						break;
-					}
-					if (school)
-						spells.push_back(spell);
-				}
+
+				if (!spell->isSpecialSpell() && spell->school[(ESpellSchool)i])
+					spells.push_back(spell);
 			}
 
 			RandomGeneratorUtil::randomShuffle(spells, gen->rand);

+ 272 - 0
lib/spells/AdventureSpellMechanics.cpp

@@ -0,0 +1,272 @@
+/*
+ * AdventureSpellMechanics.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 "AdventureSpellMechanics.h"
+
+#include "../CRandomGenerator.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../NetPacks.h"
+#include "../BattleState.h"
+#include "../CGameState.h"
+#include "../CGameInfoCallback.h"
+
+///SummonBoatMechanics
+bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	//try to find unoccupied boat to summon
+	const CGBoat * nearest = nullptr;
+	double dist = 0;
+	int3 summonPos = parameters.caster->bestLocation();
+	if(summonPos.x < 0)
+	{
+		env->complain("There is no water tile available!");
+		return false;
+	}
+
+	for(const CGObjectInstance * obj : env->getMap()->objects)
+	{
+		if(obj && obj->ID == Obj::BOAT)
+		{
+			const CGBoat *b = static_cast<const CGBoat*>(obj);
+			if(b->hero)
+				continue; //we're looking for unoccupied boat
+
+			double nDist = b->pos.dist2d(parameters.caster->getPosition());
+			if(!nearest || nDist < dist) //it's first boat or closer than previous
+			{
+				nearest = b;
+				dist = nDist;
+			}
+		}
+	}
+
+	if(nullptr != nearest) //we found boat to summon
+	{
+		ChangeObjPos cop;
+		cop.objid = nearest->id;
+		cop.nPos = summonPos + int3(1,0,0);;
+		cop.flags = 1;
+		env->sendAndApply(&cop);
+	}
+	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
+		env->sendAndApply(&iw);
+	}
+	else //create boat
+	{
+		NewObject no;
+		no.ID = Obj::BOAT;
+		no.subID = parameters.caster->getBoatType();
+		no.pos = summonPos + int3(1,0,0);;
+		env->sendAndApply(&no);
+	}
+	return true;
+}
+
+///ScuttleBoatMechanics
+bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Invalid dst tile for scuttle!");
+		return false;
+	}
+
+	//TODO: test range, visibility
+	const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
+	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
+	{
+		env->complain("There is no boat to scuttle!");
+		return false;
+	}
+
+	RemoveObject ro;
+	ro.id = t->visitableObjects.back()->id;
+	env->sendAndApply(&ro);
+	return true;
+}
+
+///DimensionDoorMechanics
+bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination is out of map!");
+		return false;
+	}
+
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
+
+	if(nullptr == dest)
+	{
+		env->complain("Destination tile doesn't exist!");
+		return false;
+	}
+
+	if(nullptr == curr)
+	{
+		env->complain("Source tile doesn't exist!");
+		return false;
+	}
+
+	if(parameters.caster->movement <= 0)
+	{
+		env->complain("Hero needs movement points to cast Dimension Door!");
+		return false;
+	}
+
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	GiveBonus gb;
+	gb.id = parameters.caster->id.getNum();
+	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
+	env->sendAndApply(&gb);
+
+	if(!dest->isClear(curr)) //wrong dest tile
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
+		env->sendAndApply(&iw);
+	}
+	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
+	{
+		SetMovePoints smp;
+		smp.hid = parameters.caster->id;
+		smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
+		env->sendAndApply(&smp);
+	}
+	return true;
+}
+
+///TownPortalMechanics
+bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
+{
+	if (!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination tile not present!");
+		return false;
+	}
+
+	TerrainTile tile = env->getMap()->getTile(parameters.pos);
+	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
+	{
+		env->complain("Town not found for Town Portal!");
+		return false;
+	}
+
+	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
+	if (town->tempOwner != parameters.caster->tempOwner)
+	{
+		env->complain("Can't teleport to another player!");
+		return false;
+	}
+
+	if (town->visitingHero)
+	{
+		env->complain("Can't teleport to occupied town!");
+		return false;
+	}
+
+	if (parameters.caster->getSpellSchoolLevel(owner) < 2)
+	{
+		si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
+		ObjectInstanceID nearest = town->id; //nearest town's ID
+		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
+		{
+			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
+			if (currDist < dist)
+			{
+				nearest = currTown->id;
+				dist = currDist;
+			}
+		}
+		if (town->id != nearest)
+		{
+			env->complain("This hero can only teleport to nearest town!");
+			return false;
+		}
+
+	}
+	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);
+	return true;
+}
+
+bool ViewMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	ShowWorldViewEx pack;
+
+	pack.player = parameters.caster->tempOwner;
+
+	const int spellLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+	for(const CGObjectInstance * obj : env->getMap()->objects)
+	{
+		//todo:we need to send only not visible objects
+
+		if(filterObject(obj, spellLevel))
+			pack.objectPositions.push_back(ObjectPosInfo(obj));
+	}
+
+	env->sendAndApply(&pack);
+
+	return true;
+}
+
+bool ViewAirMechanics::filterObject(const CGObjectInstance * obj, const int spellLevel) const
+{
+	return (obj->ID == Obj::ARTIFACT) || (spellLevel>1 && obj->ID == Obj::HERO) || (spellLevel>2 && obj->ID == Obj::TOWN);
+}
+
+bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int spellLevel) const
+{
+	return (obj->ID == Obj::RESOURCE) || (spellLevel>1 && obj->ID == Obj::MINE);
+}
+

+ 75 - 0
lib/spells/AdventureSpellMechanics.h

@@ -0,0 +1,75 @@
+/*
+ * AdventureSpellMechanics.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 "CDefaultSpellMechanics.h"
+
+class ISpellMechanics;
+class DefaultSpellMechanics;
+
+class DLL_LINKAGE SummonBoatMechanics : public DefaultSpellMechanics
+{
+public:
+	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+};
+
+class DLL_LINKAGE ScuttleBoatMechanics : public DefaultSpellMechanics
+{
+public:
+	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+};
+
+class DLL_LINKAGE DimensionDoorMechanics : public DefaultSpellMechanics
+{
+public:
+	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+};
+
+class DLL_LINKAGE TownPortalMechanics : public DefaultSpellMechanics
+{
+public:
+	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+};
+
+class DLL_LINKAGE ViewMechanics : public DefaultSpellMechanics
+{
+public:
+	ViewMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	virtual bool filterObject(const CGObjectInstance * obj, const int spellLevel) const = 0;
+};
+
+class DLL_LINKAGE ViewAirMechanics : public ViewMechanics
+{
+public:
+	ViewAirMechanics(CSpell * s): ViewMechanics(s){};
+protected:
+	bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
+};
+
+class DLL_LINKAGE ViewEarthMechanics : public ViewMechanics
+{
+public:
+	ViewEarthMechanics(CSpell * s): ViewMechanics(s){};
+protected:
+	bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
+};
+
+

+ 417 - 0
lib/spells/BattleSpellMechanics.cpp

@@ -0,0 +1,417 @@
+/*
+ * BattleSpellMechanics.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 "BattleSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.h"
+
+///ChainLightningMechanics
+std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+{
+	std::set<const CStack* > attackedCres;
+
+	std::set<BattleHex> possibleHexes;
+	for(auto stack : ctx.cb->battleGetAllStacks())
+	{
+		if(stack->isValidTarget())
+		{
+			for(auto hex : stack->getHexes())
+			{
+				possibleHexes.insert (hex);
+			}
+		}
+	}
+	int targetsOnLevel[4] = {4, 4, 5, 5};
+
+	BattleHex lightningHex = ctx.destination;
+	for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
+	{
+		auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
+		if(!stack)
+			break;
+		attackedCres.insert (stack);
+		for(auto hex : stack->getHexes())
+		{
+			possibleHexes.erase(hex); //can't hit same place twice
+		}
+		if(possibleHexes.empty()) //not enough targets
+			break;
+		lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
+	}
+
+	return attackedCres;
+}
+
+///CloneMechanics
+void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	const CStack * clonedStack = nullptr;
+	if(ctx.attackedCres.size())
+		clonedStack = *ctx.attackedCres.begin();
+	if(!clonedStack)
+	{
+		env->complain ("No target stack to clone!");
+		return;
+	}
+	const int attacker = !(bool)parameters.casterSide;
+
+	BattleStackAdded bsa;
+	bsa.creID = clonedStack->type->idNumber;
+	bsa.attacker = attacker;
+	bsa.summoned = true;
+	bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it
+	bsa.amount = clonedStack->count;
+	env->sendAndApply(&bsa);
+
+	BattleSetStackProperty ssp;
+	ssp.stackID = bsa.newStackID;//we know stack ID after apply
+	ssp.which = BattleSetStackProperty::CLONED;
+	ssp.val = 0;
+	ssp.absolute = 1;
+	env->sendAndApply(&ssp);
+}
+
+ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	//can't clone already cloned creature
+	if(vstd::contains(obj->state, EBattleStackState::CLONED))
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	//TODO: how about stacks casting Clone?
+	//currently Clone casted by stack is assumed Expert level
+	ui8 schoolLevel;
+	if(caster)
+	{
+		schoolLevel = caster->getSpellSchoolLevel(owner);
+	}
+	else
+	{
+		schoolLevel = 3;
+	}
+
+	if(schoolLevel < 3)
+	{
+		int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
+		int creLevel = obj->getCreature()->level;
+		if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+	//use default algorithm only if there is no mechanics-related problem
+	return DefaultSpellMechanics::isImmuneByStack(caster, obj);
+}
+
+///CureMechanics
+void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(battle, packet);
+
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+		{
+			logGlobal->errorStream() << "Resistance to positive spell CURE";
+			continue;
+		}
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			if(b->source == Bonus::SPELL_EFFECT)
+			{
+				CSpell * sp = SpellID(b->sid).toSpell();
+				return sp->isNegative();
+			}
+			return false; //not a spell effect
+		});
+	}
+}
+
+///DispellMechanics
+void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(battle, packet);
+
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			return Selector::sourceType(Bonus::SPELL_EFFECT)(b);
+		});
+	}
+}
+
+
+///HypnotizeMechanics
+ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	if(nullptr != caster) //do not resist hypnotize casted after attack, for example
+	{
+		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
+		ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
+		//apply 'damage' bonus for hypnotize, including hero specialty
+		ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
+			* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
+		if (subjectHealth > maxHealth)
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+	return DefaultSpellMechanics::isImmuneByStack(caster, obj);
+}
+
+///ObstacleMechanics
+void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	auto placeObstacle = [&, this](BattleHex pos)
+	{
+		static int obstacleIdToGive =  parameters.cb->obstacles.size()
+									? (parameters.cb->obstacles.back()->uniqueID+1)
+									: 0;
+
+		auto obstacle = make_shared<SpellCreatedObstacle>();
+		switch(owner->id) // :/
+		{
+		case SpellID::QUICKSAND:
+			obstacle->obstacleType = CObstacleInstance::QUICKSAND;
+			obstacle->turnsRemaining = -1;
+			obstacle->visibleForAnotherSide = false;
+			break;
+		case SpellID::LAND_MINE:
+			obstacle->obstacleType = CObstacleInstance::LAND_MINE;
+			obstacle->turnsRemaining = -1;
+			obstacle->visibleForAnotherSide = false;
+			break;
+		case SpellID::FIRE_WALL:
+			obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
+			obstacle->turnsRemaining = 2;
+			obstacle->visibleForAnotherSide = true;
+			break;
+		case SpellID::FORCE_FIELD:
+			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
+			obstacle->turnsRemaining = 2;
+			obstacle->visibleForAnotherSide = true;
+			break;
+		default:
+			//this function cannot be used with spells that do not create obstacles
+			assert(0);
+		}
+
+		obstacle->pos = pos;
+		obstacle->casterSide = parameters.casterSide;
+		obstacle->ID = owner->id;
+		obstacle->spellLevel = parameters.spellLvl;
+		obstacle->casterSpellPower = parameters.usedSpellPower;
+		obstacle->uniqueID = obstacleIdToGive++;
+
+		BattleObstaclePlaced bop;
+		bop.obstacle = obstacle;
+		env->sendAndApply(&bop);
+	};
+
+	switch(owner->id)
+	{
+	case SpellID::QUICKSAND:
+	case SpellID::LAND_MINE:
+		{
+			std::vector<BattleHex> availableTiles;
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
+			{
+				BattleHex hex = i;
+				if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
+					availableTiles.push_back(hex);
+			}
+			boost::range::random_shuffle(availableTiles);
+
+			const int patchesForSkill[] = {4, 4, 6, 8};
+			const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
+
+			//land mines or quicksand patches are handled as spell created obstacles
+			for (int i = 0; i < patchesToPut; i++)
+				placeObstacle(availableTiles.at(i));
+		}
+
+		break;
+	case SpellID::FORCE_FIELD:
+		placeObstacle(parameters.destination);
+		break;
+	case SpellID::FIRE_WALL:
+		{
+			//fire wall is build from multiple obstacles - one fire piece for each affected hex
+			auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
+			for(BattleHex hex : affectedHexes)
+				placeObstacle(hex);
+		}
+		break;
+	default:
+		assert(0);
+	}
+}
+
+
+///WallMechanics
+std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
+{
+	std::vector<BattleHex> ret;
+
+	//Special case - shape of obstacle depends on caster's side
+	//TODO make it possible through spell config
+
+	BattleHex::EDir firstStep, secondStep;
+	if(side)
+	{
+		firstStep = BattleHex::TOP_LEFT;
+		secondStep = BattleHex::TOP_RIGHT;
+	}
+	else
+	{
+		firstStep = BattleHex::TOP_RIGHT;
+		secondStep = BattleHex::TOP_LEFT;
+	}
+
+	//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
+	auto addIfValid = [&](BattleHex hex)
+	{
+		if(hex.isValid())
+			ret.push_back(hex);
+		else if(outDroppedHexes)
+			*outDroppedHexes = true;
+	};
+
+	ret.push_back(centralHex);
+	addIfValid(centralHex.moveInDir(firstStep, false));
+	if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
+		addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
+
+	return ret;
+}
+
+///RemoveObstacleMechanics
+void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
+	{
+		ObstaclesRemoved obr;
+		obr.obstacles.insert(obstacleToRemove->uniqueID);
+		env->sendAndApply(&obr);
+	}
+	else
+		env->complain("There's no obstacle to remove!");
+}
+
+///SpecialRisingSpellMechanics
+void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
+
+	if(parameters.selectedStack == parameters.cb->battleActiveStack())
+	//set another active stack than the one removed, or bad things will happen
+	//TODO: make that part of BattleStacksRemoved? what about client update?
+	{
+		//makeStackDoNothing(gs->curB->getStack (selectedStack));
+
+		BattleSetActiveStack sas;
+
+		//std::vector<const CStack *> hlp;
+		//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
+
+		//if(hlp.size())
+		//{
+		//	sas.stack = hlp[0]->ID;
+		//}
+		//else
+		//	complain ("No new stack to activate!");
+		sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
+		env->sendAndApply(&sas);
+
+	}
+	BattleStacksRemoved bsr;
+	bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
+	env->sendAndApply(&bsr);
+}
+
+
+///SpecialRisingSpellMechanics
+ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	// following does apply to resurrect and animate dead(?) only
+	// for sacrifice health calculation and health limit check don't matter
+
+	if(obj->count >= obj->baseAmount)
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+
+	if(caster) //FIXME: Archangels can cast immune stack
+	{
+		auto maxHealth = calculateHealedHP(caster, obj, nullptr);
+		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);
+}
+
+///SummonMechanics
+void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//todo: make configurable
+	CreatureID creID = CreatureID::NONE;
+	switch(owner->id)
+	{
+		case SpellID::SUMMON_FIRE_ELEMENTAL:
+			creID = CreatureID::FIRE_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_EARTH_ELEMENTAL:
+			creID = CreatureID::EARTH_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_WATER_ELEMENTAL:
+			creID = CreatureID::WATER_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_AIR_ELEMENTAL:
+			creID = CreatureID::AIR_ELEMENTAL;
+			break;
+		default:
+			env->complain("Unable to determine summoned creature");
+			return;
+	}
+
+	BattleStackAdded bsa;
+	bsa.creID = creID;
+	bsa.attacker = !(bool)parameters.casterSide;
+	bsa.summoned = true;
+	bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it
+
+	//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
+	int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
+
+	bsa.amount = parameters.usedSpellPower
+		* owner->getPower(parameters.spellLvl)
+		* (100 + percentBonus) / 100.0; //new feature - percentage bonus
+	if(bsa.amount)
+		env->sendAndApply(&bsa);
+	else
+		env->complain("Summoning didn't summon any!");
+}
+
+
+///TeleportMechanics
+void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	BattleStackMoved bsm;
+	bsm.distance = -1;
+	bsm.stack = parameters.selectedStack->ID;
+	std::vector<BattleHex> tiles;
+	tiles.push_back(parameters.destination);
+	bsm.tilesToMove = tiles;
+	bsm.teleporting = true;
+	env->sendAndApply(&bsm);
+}
+

+ 116 - 0
lib/spells/BattleSpellMechanics.h

@@ -0,0 +1,116 @@
+/*
+ * BattleSpellMechanics.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 "CDefaultSpellMechanics.h"
+
+class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
+{
+public:
+	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+};
+
+class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
+{
+public:
+	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+class DLL_LINKAGE CureMechanics : public DefaultSpellMechanics
+{
+public:
+	CureMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
+};
+
+class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
+{
+public:
+	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
+};
+
+class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
+{
+public:
+	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+};
+
+class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
+{
+public:
+	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+class DLL_LINKAGE WallMechanics : public ObstacleMechanics
+{
+public:
+	WallMechanics(CSpell * s): ObstacleMechanics(s){};
+	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
+};
+
+class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
+{
+public:
+	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+///all rising spells
+class DLL_LINKAGE RisingSpellMechanics : public DefaultSpellMechanics
+{
+public:
+	RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+};
+
+class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
+{
+public:
+	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+///all rising spells but SACRIFICE
+class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
+{
+public:
+	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+};
+
+class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
+{
+public:
+	SummonMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
+{
+public:
+	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};

+ 719 - 0
lib/spells/CDefaultSpellMechanics.cpp

@@ -0,0 +1,719 @@
+/*
+ * CDefaultSpellMechanics.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 "CDefaultSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.h"
+
+namespace SRSLPraserHelpers
+{
+	static int XYToHex(int x, int y)
+	{
+		return x + GameConstants::BFIELD_WIDTH * y;
+	}
+
+	static int XYToHex(std::pair<int, int> xy)
+	{
+		return XYToHex(xy.first, xy.second);
+	}
+
+	static int hexToY(int battleFieldPosition)
+	{
+		return battleFieldPosition/GameConstants::BFIELD_WIDTH;
+	}
+
+	static int hexToX(int battleFieldPosition)
+	{
+		int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
+		return pos;
+	}
+
+	static std::pair<int, int> hexToPair(int battleFieldPosition)
+	{
+		return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
+	}
+
+	//moves hex by one hex in given direction
+	//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
+	static std::pair<int, int> gotoDir(int x, int y, int direction)
+	{
+		switch(direction)
+		{
+		case 0: //top left
+			return std::make_pair((y%2) ? x-1 : x, y-1);
+		case 1: //top right
+			return std::make_pair((y%2) ? x : x+1, y-1);
+		case 2:  //right
+			return std::make_pair(x+1, y);
+		case 3: //right bottom
+			return std::make_pair((y%2) ? x : x+1, y+1);
+		case 4: //left bottom
+			return std::make_pair((y%2) ? x-1 : x, y+1);
+		case 5: //left
+			return std::make_pair(x-1, y);
+		default:
+			throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
+		}
+	}
+
+	static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
+	{
+		return gotoDir(xy.first, xy.second, direction);
+	}
+
+	static bool isGoodHex(std::pair<int, int> xy)
+	{
+		return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
+	}
+
+	//helper function for rangeInHexes
+	static std::set<ui16> getInRange(unsigned int center, int low, int high)
+	{
+		std::set<ui16> ret;
+		if(low == 0)
+		{
+			ret.insert(center);
+		}
+
+		std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
+		for(auto & elem : mainPointForLayer)
+			elem = hexToPair(center);
+
+		for(int it=1; it<=high; ++it) //it - distance to the center
+		{
+			for(int b=0; b<6; ++b)
+				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
+
+			if(it>=low)
+			{
+				std::pair<int, int> curHex;
+
+				//adding lines (A-b, B-c, C-d, etc)
+				for(int v=0; v<6; ++v)
+				{
+					curHex = mainPointForLayer[v];
+					for(int h=0; h<it; ++h)
+					{
+						if(isGoodHex(curHex))
+							ret.insert(XYToHex(curHex));
+						curHex = gotoDir(curHex, (v+2)%6);
+					}
+				}
+
+			} //if(it>=low)
+		}
+
+		return ret;
+	}
+}
+
+///DefaultSpellMechanics
+void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	if (packet->castedByHero)
+	{
+		if (packet->side < 2)
+		{
+			battle->sides[packet->side].castSpellsCount++;
+		}
+	}
+
+	//handle countering spells
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack * s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus * b) -> bool
+		{
+			//check for each bonus if it should be removed
+			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
+			const int spellID = isSpellEffect ? b->sid : -1;
+
+			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
+		});
+	}
+}
+
+bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(!owner->isAdventureSpell())
+	{
+		env->complain("Attempt to cast non adventure spell in adventure mode");
+		return false;
+	}
+
+	const CGHeroInstance * caster = parameters.caster;
+	const int cost = caster->getSpellCost(owner);
+
+	if(!caster->canCastThisSpell(owner))
+	{
+		env->complain("Hero cannot cast this spell!");
+		return false;
+	}
+
+	if(caster->mana < cost)
+	{
+		env->complain("Hero doesn't have enough spell points to cast this spell!");
+		return false;
+	}
+
+	{
+		AdvmapSpellCast asc;
+		asc.caster = caster;
+		asc.spellID = owner->id;
+		env->sendAndApply(&asc);
+	}
+
+	if(applyAdventureEffects(env, parameters))
+	{
+		SetMana sm;
+		sm.hid = caster->id;
+		sm.absolute = false;
+		sm.val = -cost;
+		env->sendAndApply(&sm);
+		return true;
+	}
+	return false;
+}
+
+bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(owner->hasEffects())
+	{
+		const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+		std::vector<Bonus> bonuses;
+
+		owner->getEffects(bonuses, schoolLevel);
+
+		for(Bonus b : bonuses)
+		{
+			GiveBonus gb;
+			gb.id = parameters.caster->id.getNum();
+			gb.bonus = b;
+			env->sendAndApply(&gb);
+		}
+
+		return true;
+	}
+	else
+	{
+		//There is no generic algorithm of adventure cast
+		env->complain("Unimplemented adventure spell");
+		return false;
+	}
+}
+
+
+void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
+{
+	BattleSpellCast sc;
+	sc.side = parameters.casterSide;
+	sc.id = owner->id;
+	sc.skill = parameters.spellLvl;
+	sc.tile = parameters.destination;
+	sc.dmgToDisplay = 0;
+	sc.castedByHero = nullptr != parameters.caster;
+	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
+	sc.manaGained = 0;
+
+	int spellCost = 0;
+
+	//calculate spell cost
+	if(parameters.caster)
+	{
+		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
+
+		if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
+		{
+			int manaChannel = 0;
+			for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
+			{
+				if(stack->owner == parameters.secHero->tempOwner)
+				{
+					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
+				}
+			}
+			sc.manaGained = (manaChannel * spellCost) / 100;
+		}
+	}
+
+	//calculating affected creatures for all spells
+	//must be vector, as in Chain Lightning order matters
+	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
+
+	auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
+	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
+
+	for (auto cre : attackedCres)
+	{
+		sc.affectedCres.insert(cre->ID);
+	}
+
+	//checking if creatures resist
+	//resistance is applied only to negative spells
+	if(owner->isNegative())
+	{
+		for(auto s : attackedCres)
+		{
+			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
+
+			if(env->getRandomGenerator().nextInt(99) < prob)
+			{
+				sc.resisted.push_back(s->ID);
+			}
+		}
+	}
+
+	StacksInjured si;
+	SpellCastContext ctx(attackedCres, sc, si);
+
+	applyBattleEffects(env, parameters, ctx);
+
+	env->sendAndApply(&sc);
+
+	//spend mana
+	if(parameters.caster)
+	{
+		SetMana sm;
+		sm.absolute = false;
+
+		sm.hid = parameters.caster->id;
+		sm.val = -spellCost;
+
+		env->sendAndApply(&sm);
+
+		if(sc.manaGained > 0)
+		{
+			assert(parameters.secHero);
+
+			sm.hid = parameters.secHero->id;
+			sm.val = sc.manaGained;
+			env->sendAndApply(&sm);
+		}
+	}
+
+	if(!si.stacks.empty()) //after spellcast info shows
+		env->sendAndApply(&si);
+
+	//reduce number of casts remaining
+	//TODO: this should be part of BattleSpellCast apply
+	if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
+	{
+		assert(parameters.casterStack);
+
+		BattleSetStackProperty ssp;
+		ssp.stackID = parameters.casterStack->ID;
+		ssp.which = BattleSetStackProperty::CASTS;
+		ssp.val = -1;
+		ssp.absolute = false;
+		env->sendAndApply(&ssp);
+	}
+
+	//Magic Mirror effect
+	if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
+	{
+		for(auto & attackedCre : attackedCres)
+		{
+			int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
+			if(mirrorChance > env->getRandomGenerator().nextInt(99))
+			{
+				std::vector<const CStack *> mirrorTargets;
+				auto battleStacks = parameters.cb->battleGetAllStacks(true);
+				for(auto & battleStack : battleStacks)
+				{
+					if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
+					{
+						if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
+							mirrorTargets.push_back(battleStack);
+					}
+				}
+				if(!mirrorTargets.empty())
+				{
+					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
+
+					BattleSpellCastParameters mirrorParameters = parameters;
+					mirrorParameters.spellLvl = 0;
+					mirrorParameters.casterSide = 1-parameters.casterSide;
+					mirrorParameters.casterColor = (attackedCre)->owner;
+					mirrorParameters.caster = nullptr;
+					mirrorParameters.destination = targetHex;
+					mirrorParameters.secHero = parameters.caster;
+					mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
+					mirrorParameters.casterStack = (attackedCre);
+					mirrorParameters.selectedStack = nullptr;
+
+					battleCast(env, mirrorParameters);
+				}
+			}
+		}
+	}
+}
+
+int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
+{
+	if(!caster)
+	{
+		if (!usedSpellPower)
+			return 3; //default duration of all creature spells
+		else
+			return usedSpellPower; //use creature spell power
+	}
+	switch(owner->id)
+	{
+	case SpellID::FRENZY:
+		return 1;
+	default: //other spells
+		return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
+	}
+}
+
+ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
+{
+	int healedHealth;
+
+	if(!owner->isHealingSpell())
+	{
+		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
+		return 0;
+	}
+
+	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
+	const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
+
+	if (owner->id == SpellID::SACRIFICE && sacrificedStack)
+		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
+	else
+		healedHealth = spellPowerSkill * owner->power + levelPower; //???
+	healedHealth = owner->calculateBonus(healedHealth, caster, stack);
+	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));
+}
+
+
+void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//applying effects
+	if(owner->isOffensiveSpell())
+	{
+		int spellDamage = 0;
+		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
+		{
+			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
+			if(unitSpellPower)
+				ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
+			else //Faerie Dragon
+			{
+				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
+				ctx.sc.dmgToDisplay = 0;
+			}
+		}
+		int chainLightningModifier = 0;
+		for(auto & attackedCre : ctx.attackedCres)
+		{
+			if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
+				continue;
+
+			BattleStackAttacked bsa;
+			if(spellDamage)
+				bsa.damageAmount = spellDamage >> chainLightningModifier;
+			else
+				bsa.damageAmount =  owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
+
+			ctx.sc.dmgToDisplay += bsa.damageAmount;
+
+			bsa.stackAttacked = (attackedCre)->ID;
+			if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
+				bsa.attackerID = parameters.casterStack->ID;
+			else
+				bsa.attackerID = -1;
+			(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+			ctx.si.stacks.push_back(bsa);
+
+			if(owner->id == SpellID::CHAIN_LIGHTNING)
+				++chainLightningModifier;
+		}
+	}
+
+	if(owner->hasEffects())
+	{
+		int stackSpellPower = 0;
+		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
+		{
+			stackSpellPower =  parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
+		}
+		SetStackEffect sse;
+		Bonus pseudoBonus;
+		pseudoBonus.sid = owner->id;
+		pseudoBonus.val = parameters.spellLvl;
+		pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
+		CStack::stackEffectToFeature(sse.effect, pseudoBonus);
+		if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
+		{
+			sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
+		}
+		if(owner->id == SpellID::BIND &&  parameters.casterStack)//bind
+		{
+			sse.effect.back().additionalInfo =  parameters.casterStack->ID; //we need to know who casted Bind
+		}
+		const Bonus * bonus = nullptr;
+		if(parameters.caster)
+			bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
+		//TODO does hero specialty should affects his stack casting spells?
+
+		si32 power = 0;
+		for(const CStack * affected : ctx.attackedCres)
+		{
+			if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
+				continue;
+			sse.stacks.push_back(affected->ID);
+
+			//Apply hero specials - peculiar enchants
+			const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
+			if(bonus)
+			{
+				switch(bonus->additionalInfo)
+				{
+					case 0: //normal
+					{
+						switch(tier)
+						{
+							case 1: case 2:
+								power = 3;
+							break;
+							case 3: case 4:
+								power = 2;
+							break;
+							case 5: case 6:
+								power = 1;
+							break;
+						}
+						Bonus specialBonus(sse.effect.back());
+						specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
+						sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
+					}
+					break;
+					case 1: //only Coronius as yet
+					{
+						power = std::max(5 - tier, 0);
+						Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
+						specialBonus.sid = owner->id;
+						sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
+					}
+					break;
+				}
+			}
+			if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
+			{
+				int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
+				Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
+				specialBonus.valType = Bonus::PERCENT_TO_ALL;
+				specialBonus.sid = owner->id;
+				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
+			}
+		}
+
+		if(!sse.stacks.empty())
+			env->sendAndApply(&sse);
+
+	}
+
+	if(owner->isHealingSpell())
+	{
+		int hpGained = 0;
+		if(parameters.casterStack)
+		{
+			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
+			if(unitSpellPower)
+				hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
+			else //Faerie Dragon-like effect - unused so far
+				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
+		}
+		StacksHealedOrResurrected shr;
+		shr.lifeDrain = false;
+		shr.tentHealing = false;
+		for(auto & attackedCre : ctx.attackedCres)
+		{
+			StacksHealedOrResurrected::HealInfo hi;
+			hi.stackID = (attackedCre)->ID;
+			if (parameters.casterStack) //casted by creature
+			{
+				const bool resurrect = owner->isRisingSpell();
+				if (hpGained)
+				{
+					//archangel
+					hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
+				}
+				else
+				{
+					//any typical spell (commander's cure or animate dead)
+					int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl);
+					hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
+				}
+			}
+			else
+				hi.healedHP = calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
+			hi.lowLevelResurrection = parameters.spellLvl <= 1;
+			shr.healedStacks.push_back(hi);
+		}
+		if(!shr.healedStacks.empty())
+			env->sendAndApply(&shr);
+	}
+}
+
+std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
+{
+	using namespace SRSLPraserHelpers;
+
+	std::vector<BattleHex> ret;
+	std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
+
+	if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
+	{
+		std::string number1, number2;
+		int beg, end;
+		bool readingFirst = true;
+		for(auto & elem : rng)
+		{
+			if(std::isdigit(elem) ) //reading number
+			{
+				if(readingFirst)
+					number1 += elem;
+				else
+					number2 += elem;
+			}
+			else if(elem == ',') //comma
+			{
+				//calculating variables
+				if(readingFirst)
+				{
+					beg = atoi(number1.c_str());
+					number1 = "";
+				}
+				else
+				{
+					end = atoi(number2.c_str());
+					number2 = "";
+				}
+				//obtaining new hexes
+				std::set<ui16> curLayer;
+				if(readingFirst)
+				{
+					curLayer = getInRange(centralHex, beg, beg);
+				}
+				else
+				{
+					curLayer = getInRange(centralHex, beg, end);
+					readingFirst = true;
+				}
+				//adding abtained hexes
+				for(auto & curLayer_it : curLayer)
+				{
+					ret.push_back(curLayer_it);
+				}
+
+			}
+			else if(elem == '-') //dash
+			{
+				beg = atoi(number1.c_str());
+				number1 = "";
+				readingFirst = false;
+			}
+		}
+	}
+
+	//remove duplicates (TODO check if actually needed)
+	range::unique(ret);
+	return ret;
+}
+
+std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+{
+	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
+
+	const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
+	const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
+
+	const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
+
+	//TODO: more generic solution for mass spells
+	if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
+	{
+		for(BattleHex hex : attackedHexes)
+		{
+			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
+			{
+				attackedCres.insert(st);
+			}
+		}
+	}
+	else if(ti.type == CSpell::CREATURE)
+	{
+		auto predicate = [=](const CStack * s){
+			const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
+			const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
+			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
+
+			//for single target spells select stacks covering destination tile
+			const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
+			//handle smart targeting
+			const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
+
+			return rangeCovers && positivenessFlag && validTarget;
+		};
+
+		TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
+
+		if(ti.massive)
+		{
+			//for massive spells add all targets
+			for (auto stack : stacks)
+				attackedCres.insert(stack);
+
+		}
+		else
+		{
+			//for single target spells we must select one target. Alive stack is preferred (issue #1763)
+			for(auto stack : stacks)
+			{
+				if(stack->alive())
+				{
+					attackedCres.insert(stack);
+					break;
+				}
+			}
+
+			if(attackedCres.empty() && !stacks.empty())
+			{
+				attackedCres.insert(stacks.front());
+			}
+		}
+	}
+	else //custom range from attackedHexes
+	{
+		for(BattleHex hex : attackedHexes)
+		{
+			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
+				attackedCres.insert(st);
+		}
+	}
+
+	return attackedCres;
+}
+
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	//by default use general algorithm
+	return owner->isImmuneBy(obj);
+}

+ 1028 - 1070
lib/CSpellHandler.cpp → lib/spells/CSpellHandler.cpp

@@ -1,1070 +1,1028 @@
-#include "StdInc.h"
-#include "CSpellHandler.h"
-
-#include "CGeneralTextHandler.h"
-#include "filesystem/Filesystem.h"
-
-#include "JsonNode.h"
-#include <cctype>
-#include "BattleHex.h"
-#include "CModHandler.h"
-#include "StringConstants.h"
-
-#include "mapObjects/CGHeroInstance.h"
-#include "BattleState.h"
-#include "CBattleCallback.h"
-
-#include "SpellMechanics.h"
-
-
-
-/*
- * CSpellHandler.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
- *
- */
-
-namespace SpellConfig
-{
-	static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
-	
-	static const SpellSchoolInfo SCHOOL[4] = 
-	{
-		{
-			ESpellSchool::AIR,
-			Bonus::AIR_SPELL_DMG_PREMY,
-			Bonus::AIR_IMMUNITY,
-			"air",
-			SecondarySkill::AIR_MAGIC,
-			Bonus::AIR_SPELLS
-		},
-		{
-			ESpellSchool::FIRE,
-			Bonus::FIRE_SPELL_DMG_PREMY,
-			Bonus::FIRE_IMMUNITY,
-			"fire",
-			SecondarySkill::FIRE_MAGIC,
-			Bonus::FIRE_SPELLS
-		},
-		{
-			ESpellSchool::WATER,
-			Bonus::WATER_SPELL_DMG_PREMY,
-			Bonus::WATER_IMMUNITY,
-			"water",
-			SecondarySkill::WATER_MAGIC,
-			Bonus::WATER_SPELLS
-		},
-		{
-			ESpellSchool::EARTH,
-			Bonus::EARTH_SPELL_DMG_PREMY,
-			Bonus::EARTH_IMMUNITY,
-			"earth",
-			SecondarySkill::EARTH_MAGIC,
-			Bonus::EARTH_SPELLS
-		}
-	};	
-}
-
-BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
-	: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
-	usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
-{
-	
-}
-
-
-///CSpell::LevelInfo
-CSpell::LevelInfo::LevelInfo()
-	:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
-{
-
-}
-
-CSpell::LevelInfo::~LevelInfo()
-{
-
-}
-
-///CSpell
-CSpell::CSpell():
-	id(SpellID::NONE), level(0),
-	earth(false), water(false), fire(false), air(false),
-	combatSpell(false), creatureAbility(false),
-	positiveness(ESpellPositiveness::NEUTRAL),
-	defaultProbability(0),
-	isRising(false), isDamage(false), isOffensive(false),
-	targetType(ETargetType::NO_TARGET),
-	mechanics(nullptr)
-{
-	levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
-}
-
-CSpell::~CSpell()
-{
-	delete mechanics;
-}
-
-void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	mechanics->afterCast(battle, packet);
-}
-
-
-void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
-{
-	assert(env);
-	
-	mechanics->battleCast(env, parameters);
-}
-
-bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
-{
-	if(!hasSpellBook)
-		return false;
-	
-	const bool inSpellBook = vstd::contains(spellBook, id);
-	const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
-	
-	bool inTome = false;
-	
-	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
-	{
-		if(caster->hasBonusOfType(cnf.knoledgeBonus))
-		{
-			inTome = stop = true;
-		}				
-	});	
-
-    if (isSpecialSpell())
-    {
-        if (inSpellBook)
-        {//hero has this spell in spellbook
-            logGlobal->errorStream() << "Special spell in spellbook "<<name;
-        }
-        return isBonus;
-    }
-    else
-    {
-       return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
-    }	
-}
-
-const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
-{
-	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
-	{
-		logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
-		throw new std::runtime_error("Invalid school level");
-	}
-
-	return levels.at(level);
-}
-
-ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const CStack* affectedCreature) const
-{
-	ui32 ret = baseDamage;
-	//applying sorcery secondary skill
-	if(caster)
-	{
-		ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
-		ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
-		
-		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
-		{
-			ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
-			stop = true; //only bonus from one school is used
-		});		
-
-		if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
-			ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
-	}
-	return ret;	
-}
-
-ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
-{
-	ui32 ret = 0; //value to return
-
-	//check if spell really does damage - if not, return 0
-	if(!isDamageSpell())
-		return 0;
-
-	ret = usedSpellPower * power;
-	ret += getPower(spellSchoolLevel);
-
-	//affected creature-specific part
-	if(nullptr != affectedCreature)
-	{
-		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
-		
-		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
-		{
-			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
-			{
-				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
-				ret /= 100;
-				stop = true;//only bonus from one school is used	
-			}				
-		});
-
-		//general spell dmg reduction
-		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
-			ret /= 100;
-		}
-		//dmg increasing
-		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
-		{
-			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
-			ret /= 100;
-		}
-	}
-	ret = calculateBonus(ret, caster, affectedCreature);
-	return ret;	
-}
-
-
-ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
-{
-//todo: use Mechanics class
-	int healedHealth;
-	
-	if(!isHealingSpell())
-	{
-		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< name;
-		return 0;
-	}		
-	
-	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-	const int levelPower = getPower(caster->getSpellSchoolLevel(this));
-	
-	if (id == SpellID::SACRIFICE && sacrificedStack)
-		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
-	else
-		healedHealth = spellPowerSkill * power + levelPower; //???
-	healedHealth = calculateBonus(healedHealth, caster, stack);
-	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));	
-}
-
-
-std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
-{
-	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
-}
-
-std::set<const CStack* > CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
-{
-	ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
-
-	std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
-	
-	//now handle immunities		
-	auto predicate = [&, this](const CStack * s)->bool
-	{
-		bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(destination);
-		bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
-		
-		return !(hitDirectly || notImmune);  
-	};	
-	vstd::erase_if(attackedCres, predicate);
-	
-	return attackedCres;
-}
-
-
-CSpell::ETargetType CSpell::getTargetType() const
-{
-	return targetType;
-}
-
-CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
-{
-	TargetInfo info(this, level);
-	return info;
-}
-
-void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
-{
-	bool stop = false;
-	for(const SpellSchoolInfo & cnf : SpellConfig::SCHOOL)
-	{
-		if(school.at(cnf.id))
-		{
-			cb(cnf, stop);
-			
-			if(stop)
-				break;
-		}				
-	}	
-}
-
-
-bool CSpell::isCombatSpell() const
-{
-	return combatSpell;
-}
-
-bool CSpell::isAdventureSpell() const
-{
-	return !combatSpell;
-}
-
-bool CSpell::isCreatureAbility() const
-{
-	return creatureAbility;
-}
-
-bool CSpell::isPositive() const
-{
-	return positiveness == POSITIVE;
-}
-
-bool CSpell::isNegative() const
-{
-	return positiveness == NEGATIVE;
-}
-
-bool CSpell::isNeutral() const
-{
-	return positiveness == NEUTRAL;
-}
-
-bool CSpell::isHealingSpell() const
-{
-	return isRisingSpell() || (id == SpellID::CURE);
-}
-
-bool CSpell::isRisingSpell() const
-{
-	return isRising;
-}
-
-bool CSpell::isDamageSpell() const
-{
-	return isDamage;
-}
-
-bool CSpell::isOffensiveSpell() const
-{
-	return isOffensive;
-}
-
-bool CSpell::isSpecialSpell() const
-{
-	return isSpecial;
-}
-
-bool CSpell::hasEffects() const
-{
-	return !levels[0].effects.empty();
-}
-
-const std::string& CSpell::getIconImmune() const
-{
-	return iconImmune;
-}
-
-const std::string& CSpell::getCastSound() const
-{
-	return castSound;
-}
-
-
-
-si32 CSpell::getCost(const int skillLevel) const
-{
-	return getLevelInfo(skillLevel).cost;
-}
-
-si32 CSpell::getPower(const int skillLevel) const
-{
-	return getLevelInfo(skillLevel).power;
-}
-
-//si32 CSpell::calculatePower(const int skillLevel) const
-//{
-//    return power + getPower(skillLevel);
-//}
-
-si32 CSpell::getProbability(const TFaction factionId) const
-{
-	if(!vstd::contains(probabilities,factionId))
-	{
-		return defaultProbability;
-	}
-	return probabilities.at(factionId);
-}
-
-
-void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
-{
-	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
-	{
-		logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
-		return;
-	}
-
-	const std::vector<Bonus> & effects = levels[level].effects;
-
-	if(effects.empty())
-	{
-		logGlobal->errorStream() << __FUNCTION__ << " This spell ("  + name + ") has no effects for level " << level;
-		return;
-	}
-
-	lst.reserve(lst.size() + effects.size());
-
-	for(const Bonus & b : effects)
-	{
-		lst.push_back(Bonus(b));
-	}
-}
-
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
-{
-	// Get all stacks at destination hex. only alive if not rising spell
-	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
-		return s->coversPos(destination) && (isRisingSpell() || s->alive());
-	});
-	
-	if(!stacks.empty())
-	{
-		bool allImmune = true;
-		
-		ESpellCastProblem::ESpellCastProblem problem;		
-		
-		for(auto s : stacks)
-		{
-			ESpellCastProblem::ESpellCastProblem res = isImmuneByStack(caster,s);
-			
-			if(res == ESpellCastProblem::OK)
-			{
-				allImmune = false;
-			}
-			else
-			{
-				problem = res;
-			}
-		}
-		
-		if(allImmune)
-			return problem;
-	}
-	else //no target stack on this tile
-	{
-		if(getTargetType() == CSpell::CREATURE)
-		{
-			if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
-			{
-				const CSpell::TargetInfo ti(this, caster->getSpellSchoolLevel(this), mode);
-				
-				if(!ti.massive)
-					return ESpellCastProblem::WRONG_SPELL_TARGET;					
-			}
-			else
-			{
- 				return ESpellCastProblem::WRONG_SPELL_TARGET;
-			}			
-		}
-	}
-
-	return ESpellCastProblem::OK;	
-}
-
-
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
-{	
-	//todo: use new bonus API
-	//1. Check absolute limiters
-	for(auto b : absoluteLimiters)
-	{
-		if (!obj->hasBonusOfType(b))
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-
-	//2. Check absolute immunities
-	for(auto b : absoluteImmunities)
-	{
-		if (obj->hasBonusOfType(b))
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-	
-	//check receptivity
-	if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
-		return ESpellCastProblem::OK;	
-
-	//3. Check negation
-	//FIXME: Orb of vulnerability mechanics is not such trivial
-	if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
-		return ESpellCastProblem::NOT_DECIDED;
-		
-	//4. Check negatable limit
-	for(auto b : limiters)
-	{
-		if (!obj->hasBonusOfType(b))
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-
-
-	//5. Check negatable immunities
-	for(auto b : immunities)
-	{
-		if (obj->hasBonusOfType(b))
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-
-	//6. Check elemental immunities
-	
-	ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;
-	
-	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
-	{
-		auto element = cnf.immunityBonus;
-		
-		if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether
-		{
-			tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			stop = true;
-		}				
-		else if(!isPositive()) //negative or indifferent
-		{
-			if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
-			{
-				tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-				stop = true;
-			}			
-		}	
-	});
-	
-	if(tmp != ESpellCastProblem::NOT_DECIDED)
-		return tmp;
-	
-	TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
-
-	if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
-		|| ( levelImmunities->size() > 0  &&  levelImmunities->totalValue() >= level  &&  level))
-	{
-		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}
-
-	return ESpellCastProblem::NOT_DECIDED;
-}
-
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
-{
-	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
-	
-	if (ESpellCastProblem::NOT_DECIDED != immuneResult) 
-		return immuneResult;
-	return ESpellCastProblem::OK;	
-}
-
-
-void CSpell::setIsOffensive(const bool val)
-{
-	isOffensive = val;
-
-	if(val)
-	{
-		positiveness = CSpell::NEGATIVE;
-		isDamage = true;
-	}
-}
-
-void CSpell::setIsRising(const bool val)
-{
-	isRising = val;
-
-	if(val)
-	{
-		positiveness = CSpell::POSITIVE;
-	}
-}
-
-void CSpell::setup()
-{
-	setupMechanics();
-	
-	air = school[ESpellSchool::AIR];
-	fire = school[ESpellSchool::FIRE];
-	water = school[ESpellSchool::WATER];
-	earth = school[ESpellSchool::EARTH];	
-}
-
-
-void CSpell::setupMechanics()
-{
-	if(nullptr != mechanics)
-	{
-		logGlobal->errorStream() << "Spell " << this->name << " mechanics already set";
-		delete mechanics;
-	}
-	
-	mechanics = ISpellMechanics::createMechanics(this);	
-}
-
-///CSpell::AnimationInfo
-CSpell::AnimationInfo::AnimationInfo()
-{
-	
-}
-
-CSpell::AnimationInfo::~AnimationInfo()
-{
-	
-}
-
-std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
-{	
-	std::string res;	
-	double maximum = 0.0;
-	
-	for(const auto & info : projectile)
-	{
-		if(info.minimumAngle < angle && info.minimumAngle > maximum)
-		{
-			maximum = info.minimumAngle;
-			res = info.resourceName;
-		}
-	}
-	
-	return std::move(res);	
-}
-
-
-///CSpell::TargetInfo
-CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level)
-{
-	init(spell, level);
-}
-
-CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode)
-{
-	init(spell, level);
-	if(mode == ECastingMode::ENCHANTER_CASTING)
-	{
-		smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
-		massive = true;
-	}
-	else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
-	{
-		alwaysHitDirectly = true;
-	}	
-}
-
-void CSpell::TargetInfo::init(const CSpell * spell, const int level)
-{
-	auto & levelInfo = spell->getLevelInfo(level);
-
-	type = spell->getTargetType();
-	smart = levelInfo.smartTarget;
-	massive = levelInfo.range == "X";
-	onlyAlive = !spell->isRisingSpell();
-	alwaysHitDirectly = false;
-	clearAffected = levelInfo.clearAffected;
-	clearTarget = levelInfo.clearTarget;
-}
-
-
-bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
-{
-	int3 diff = pos - center;
-	if(diff.x >= -9  &&  diff.x <= 9  &&  diff.y >= -8  &&  diff.y <= 8)
-		return true;
-	else
-		return false;
-}
-
-///CSpellHandler
-CSpellHandler::CSpellHandler()
-{
-
-}
-
-std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
-{
-	using namespace SpellConfig;
-	std::vector<JsonNode> legacyData;
-
-	CLegacyConfigParser parser("DATA/SPTRAITS.TXT");
-
-	auto readSchool = [&](JsonMap& schools, const std::string& name)
-	{
-		if (parser.readString() == "x")
-		{
-			schools[name].Bool() = true;
-		}
-	};
-
-	auto read = [&,this](bool combat, bool ability)
-	{
-		do
-		{
-			JsonNode lineNode(JsonNode::DATA_STRUCT);
-
-			const si32 id = legacyData.size();
-
-			lineNode["index"].Float() = id;
-			lineNode["type"].String() = ability ? "ability" : (combat ? "combat" : "adventure");
-
-			lineNode["name"].String() = parser.readString();
-
-			parser.readString(); //ignored unused abbreviated name
-			lineNode["level"].Float()      = parser.readNumber();
-
-			auto& schools = lineNode["school"].Struct();
-
-			readSchool(schools, "earth");
-			readSchool(schools, "water");
-			readSchool(schools, "fire");
-			readSchool(schools, "air");
-
-			auto& levels = lineNode["levels"].Struct();
-
-			auto getLevel = [&](const size_t idx)->JsonMap&
-			{
-				assert(idx < GameConstants::SPELL_SCHOOL_LEVELS);
-				return levels[LEVEL_NAMES[idx]].Struct();
-			};
-
-			auto costs = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
-			lineNode["power"].Float() = parser.readNumber();
-			auto powers = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
-
-			auto& chances = lineNode["gainChance"].Struct();
-
-			for(size_t i = 0; i < GameConstants::F_NUMBER ; i++){
-				chances[ETownType::names[i]].Float() = parser.readNumber();
-			}
-
-			auto AIVals = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
-
-			std::vector<std::string> descriptions;
-			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS ; i++)
-				descriptions.push_back(parser.readString());
-
-			parser.readString(); //ignore attributes. All data present in JSON
-
-			//save parsed level specific data
-			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
-			{
-				auto& level = getLevel(i);
-				level["description"].String() = descriptions[i];
-				level["cost"].Float() = costs[i];
-				level["power"].Float() = powers[i];
-				level["aiValue"].Float() = AIVals[i];
-			}
-
-			legacyData.push_back(lineNode);
-
-
-		}
-		while (parser.endLine() && !parser.isNextEntryEmpty());
-	};
-
-	auto skip = [&](int cnt)
-	{
-		for(int i=0; i<cnt; i++)
-			parser.endLine();
-	};
-
-	skip(5);// header
-	read(false,false); //read adventure map spells
-	skip(3);
-	read(true,false); //read battle spells
-	skip(3);
-	read(true,true);//read creature abilities
-
-    //TODO: maybe move to config
-	//clone Acid Breath attributes for Acid Breath damage effect
-	JsonNode temp = legacyData[SpellID::ACID_BREATH_DEFENSE];
-	temp["index"].Float() = SpellID::ACID_BREATH_DAMAGE;
-	legacyData.push_back(temp);
-
-	objects.resize(legacyData.size());
-
-	return legacyData;
-}
-
-const std::string CSpellHandler::getTypeName() const
-{
-	return "spell";
-}
-
-CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
-{
-	using namespace SpellConfig;
-
-	CSpell * spell = new CSpell();
-
-	const auto type = json["type"].String();
-
-	if(type == "ability")
-	{
-		spell->creatureAbility = true;
-		spell->combatSpell = true;
-	}
-	else
-	{
-		spell->creatureAbility = false;
-		spell->combatSpell = type == "combat";
-	}
-
-	spell->name = json["name"].String();
-
-	logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;
-
-	const auto schoolNames = json["school"];
-	
-	for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)
-	{
-		spell->school[info.id] = schoolNames[info.jsonName].Bool();
-	}
-
-	spell->level = json["level"].Float();
-	spell->power = json["power"].Float();
-
-	spell->defaultProbability = json["defaultGainChance"].Float();
-
-	for(const auto & node : json["gainChance"].Struct())
-	{
-		const int chance = node.second.Float();
-
-		VLC->modh->identifiers.requestIdentifier(node.second.meta, "faction",node.first, [=](si32 factionID)
-		{
-			spell->probabilities[factionID] = chance;
-		});
-	}
-
-	auto targetType = json["targetType"].String();
-
-	if(targetType == "NO_TARGET")
-		spell->targetType = CSpell::NO_TARGET;
-	else if(targetType == "CREATURE")
-		spell->targetType = CSpell::CREATURE;
-	else if(targetType == "OBSTACLE")
-		spell->targetType = CSpell::OBSTACLE;
-	else if(targetType == "LOCATION")
-		spell->targetType = CSpell::LOCATION;
-	else 
-		logGlobal->warnStream() << "Spell " << spell->name << ". Target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ". Assumed NO_TARGET.";
-
-	for(const auto & counteredSpell: json["counters"].Struct())
-		if (counteredSpell.second.Bool())
-		{
-			VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
-			{
-				spell->counteredSpells.push_back(SpellID(id));
-			});
-		}
-
-	//TODO: more error checking - f.e. conflicting flags
-	const auto flags = json["flags"];
-
-	//by default all flags are set to false in constructor
-
-	spell->isDamage = flags["damage"].Bool(); //do this before "offensive"
-
-	if(flags["offensive"].Bool())
-	{
-		spell->setIsOffensive(true);
-	}
-
-	if(flags["rising"].Bool())
-	{
-		spell->setIsRising(true);
-	}
-
-	const bool implicitPositiveness = spell->isOffensive || spell->isRising; //(!) "damage" does not mean NEGATIVE  --AVS
-
-	if(flags["indifferent"].Bool())
-	{
-		spell->positiveness = CSpell::NEUTRAL;
-	}
-	else if(flags["negative"].Bool())
-	{
-		spell->positiveness = CSpell::NEGATIVE;
-	}
-	else if(flags["positive"].Bool())
-	{
-		spell->positiveness = CSpell::POSITIVE;
-	}
-	else if(!implicitPositiveness)
-	{
-		spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case
-		logGlobal->errorStream() << "No positiveness specified, assumed NEUTRAL";
-	}
-
-	spell->isSpecial = flags["special"].Bool();
-
-	auto findBonus = [&](std::string name, std::vector<Bonus::BonusType> &vec)
-	{
-		auto it = bonusNameMap.find(name);
-		if(it == bonusNameMap.end())
-		{
-			logGlobal->errorStream() << spell->name << ": invalid bonus name" << name;
-		}
-		else
-		{
-			vec.push_back((Bonus::BonusType)it->second);
-		}
-	};
-
-	auto readBonusStruct = [&](std::string name, std::vector<Bonus::BonusType> &vec)
-	{
-		for(auto bonusData: json[name].Struct())
-		{
-			const std::string bonusId = bonusData.first;
-			const bool flag = bonusData.second.Bool();
-
-			if(flag)
-				findBonus(bonusId, vec);
-		}
-	};
-
-	readBonusStruct("immunity", spell->immunities);
-	readBonusStruct("absoluteImmunity", spell->absoluteImmunities);
-	readBonusStruct("limit", spell->limiters);	
-	readBonusStruct("absoluteLimit", spell->absoluteLimiters);
-
-
-	const JsonNode & graphicsNode = json["graphics"];
-
-	spell->iconImmune = graphicsNode["iconImmune"].String();
-	spell->iconBook = graphicsNode["iconBook"].String();
-	spell->iconEffect = graphicsNode["iconEffect"].String();
-	spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
-	spell->iconScroll = graphicsNode["iconScroll"].String();
-
-	const JsonNode & animationNode = json["animation"];
-	
-	auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)	
-	{
-		auto queueNode = animationNode[jsonName].Vector();		
-		for(const JsonNode & item : queueNode)
-		{	
-			CSpell::TAnimation newItem;
-			newItem.verticalPosition = VerticalPosition::TOP;
-			
-			if(item.getType() == JsonNode::DATA_STRING)
-				newItem.resourceName = item.String();
-			else if(item.getType() == JsonNode::DATA_STRUCT)
-			{
-				newItem.resourceName = item["defName"].String();
-				
-				auto vPosStr = item["verticalPosition"].String();
-				if("bottom" == vPosStr)
-					newItem.verticalPosition = VerticalPosition::BOTTOM;
-			}	
-			q.push_back(newItem);		
-		}
-	};
-	
-	loadAnimationQueue("affect", spell->animationInfo.affect);
-	loadAnimationQueue("cast", spell->animationInfo.cast);
-	loadAnimationQueue("hit", spell->animationInfo.hit);	
-	
-	const JsonVector & projectile = animationNode["projectile"].Vector();
-	
-	for(const JsonNode & item : projectile)
-	{
-		CSpell::ProjectileInfo info;
-		info.resourceName = item["defName"].String();
-		info.minimumAngle = item["minimumAngle"].Float();
-		
-		spell->animationInfo.projectile.push_back(info);
-	}
-
-	const JsonNode & soundsNode = json["sounds"];
-	spell->castSound = soundsNode["cast"].String();
-
-
-	//load level attributes
-
-	const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;
-
-	for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)
-	{
-		const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];
-		
-		CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
-
-		const si32 levelPower     = levelObject.power = levelNode["power"].Float();		
-
-		levelObject.description   = levelNode["description"].String();
-		levelObject.cost          = levelNode["cost"].Float();
-		levelObject.AIValue       = levelNode["aiValue"].Float();
-		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();
-		levelObject.clearTarget   = levelNode["targetModifier"]["clearTarget"].Bool();
-		levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();			
-		levelObject.range         = levelNode["range"].String();
-		
-		for(const auto & elem : levelNode["effects"].Struct())
-		{
-			const JsonNode & bonusNode = elem.second;
-			Bonus * b = JsonUtils::parseBonus(bonusNode);
-			const bool usePowerAsValue = bonusNode["val"].isNull();
-
-			//TODO: make this work. see CSpellHandler::afterLoadFinalization()
-			//b->sid = spell->id; //for all
-
-			b->source = Bonus::SPELL_EFFECT;//for all
-
-			if(usePowerAsValue)
-				b->val = levelPower;
-
-			levelObject.effects.push_back(*b);
-		}
-		
-	}
-
-	return spell;
-}
-
-void CSpellHandler::afterLoadFinalization()
-{
-	//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
-	for(auto spell: objects)
-	{
-		for(auto & level: spell->levels)
-			for(auto & bonus: level.effects)
-				bonus.sid = spell->id;
-		spell->setup();
-	}
-}
-
-void CSpellHandler::beforeValidate(JsonNode & object)
-{
-	//handle "base" level info
-	
-	JsonNode& levels = object["levels"];
-	JsonNode& base = levels["base"];
-	
-	auto inheritNode = [&](const std::string & name){
-		JsonUtils::inherit(levels[name],base);
-	};
-	
-	inheritNode("none");
-	inheritNode("basic");
-	inheritNode("advanced");
-	inheritNode("expert");
-
-}
-
-
-CSpellHandler::~CSpellHandler()
-{
-
-}
-
-std::vector<bool> CSpellHandler::getDefaultAllowed() const
-{
-	std::vector<bool> allowedSpells;
-	allowedSpells.resize(GameConstants::SPELLS_QUANTITY, true);
-	return allowedSpells;
-}
+/*
+ * CSpellHandler.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 <cctype>
+
+#include "CSpellHandler.h"
+
+#include "../CGeneralTextHandler.h"
+#include "../filesystem/Filesystem.h"
+
+#include "../JsonNode.h"
+
+#include "../BattleHex.h"
+#include "../CModHandler.h"
+#include "../StringConstants.h"
+
+#include "../mapObjects/CGHeroInstance.h"
+#include "../BattleState.h"
+#include "../CBattleCallback.h"
+
+#include "SpellMechanics.h"
+
+namespace SpellConfig
+{
+	static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
+
+	static const SpellSchoolInfo SCHOOL[4] =
+	{
+		{
+			ESpellSchool::AIR,
+			Bonus::AIR_SPELL_DMG_PREMY,
+			Bonus::AIR_IMMUNITY,
+			"air",
+			SecondarySkill::AIR_MAGIC,
+			Bonus::AIR_SPELLS
+		},
+		{
+			ESpellSchool::FIRE,
+			Bonus::FIRE_SPELL_DMG_PREMY,
+			Bonus::FIRE_IMMUNITY,
+			"fire",
+			SecondarySkill::FIRE_MAGIC,
+			Bonus::FIRE_SPELLS
+		},
+		{
+			ESpellSchool::WATER,
+			Bonus::WATER_SPELL_DMG_PREMY,
+			Bonus::WATER_IMMUNITY,
+			"water",
+			SecondarySkill::WATER_MAGIC,
+			Bonus::WATER_SPELLS
+		},
+		{
+			ESpellSchool::EARTH,
+			Bonus::EARTH_SPELL_DMG_PREMY,
+			Bonus::EARTH_IMMUNITY,
+			"earth",
+			SecondarySkill::EARTH_MAGIC,
+			Bonus::EARTH_SPELLS
+		}
+	};
+}
+
+BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
+	: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
+	usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
+{
+
+}
+
+///CSpell::LevelInfo
+CSpell::LevelInfo::LevelInfo()
+	:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
+{
+
+}
+
+CSpell::LevelInfo::~LevelInfo()
+{
+
+}
+
+///CSpell
+CSpell::CSpell():
+	id(SpellID::NONE), level(0),
+	combatSpell(false), creatureAbility(false),
+	positiveness(ESpellPositiveness::NEUTRAL),
+	defaultProbability(0),
+	isRising(false), isDamage(false), isOffensive(false),
+	targetType(ETargetType::NO_TARGET),
+	mechanics(nullptr)
+{
+	levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
+}
+
+CSpell::~CSpell()
+{
+	delete mechanics;
+}
+
+void CSpell::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	mechanics->applyBattle(battle, packet);
+}
+
+bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	assert(env);
+
+	return mechanics->adventureCast(env, parameters);
+}
+
+void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
+{
+	assert(env);
+
+	mechanics->battleCast(env, parameters);
+}
+
+bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
+{
+	if(!hasSpellBook)
+		return false;
+
+	const bool inSpellBook = vstd::contains(spellBook, id);
+	const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
+
+	bool inTome = false;
+
+	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+	{
+		if(caster->hasBonusOfType(cnf.knoledgeBonus))
+		{
+			inTome = stop = true;
+		}
+	});
+
+    if (isSpecialSpell())
+    {
+        if (inSpellBook)
+        {//hero has this spell in spellbook
+            logGlobal->errorStream() << "Special spell in spellbook "<<name;
+        }
+        return isBonus;
+    }
+    else
+    {
+       return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
+    }
+}
+
+const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
+{
+	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
+	{
+		logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
+		throw new std::runtime_error("Invalid school level");
+	}
+
+	return levels.at(level);
+}
+
+ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const
+{
+	ui32 ret = baseDamage;
+	//applying sorcery secondary skill
+	if(caster)
+	{
+		ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
+		ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
+
+		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+		{
+			ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
+			stop = true; //only bonus from one school is used
+		});
+
+		if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
+			ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
+	}
+	return ret;
+}
+
+ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
+{
+	ui32 ret = 0; //value to return
+
+	//check if spell really does damage - if not, return 0
+	if(!isDamageSpell())
+		return 0;
+
+	ret = usedSpellPower * power;
+	ret += getPower(spellSchoolLevel);
+
+	//affected creature-specific part
+	if(nullptr != affectedCreature)
+	{
+		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
+
+		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+		{
+			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
+			{
+				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
+				ret /= 100;
+				stop = true;//only bonus from one school is used
+			}
+		});
+
+		//general spell dmg reduction
+		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
+		{
+			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
+			ret /= 100;
+		}
+		//dmg increasing
+		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
+		{
+			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
+			ret /= 100;
+		}
+	}
+	ret = calculateBonus(ret, caster, affectedCreature);
+	return ret;
+}
+
+std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
+{
+	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
+}
+
+std::set<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
+{
+	ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
+
+	std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
+
+	//now handle immunities
+	auto predicate = [&, this](const CStack * s)->bool
+	{
+		bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(destination);
+		bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
+
+		return !(hitDirectly || notImmune);
+	};
+	vstd::erase_if(attackedCres, predicate);
+
+	return attackedCres;
+}
+
+CSpell::ETargetType CSpell::getTargetType() const
+{
+	return targetType;
+}
+
+CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
+{
+	TargetInfo info(this, level);
+	return info;
+}
+
+void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
+{
+	bool stop = false;
+	for(const SpellSchoolInfo & cnf : SpellConfig::SCHOOL)
+	{
+		if(school.at(cnf.id))
+		{
+			cb(cnf, stop);
+
+			if(stop)
+				break;
+		}
+	}
+}
+
+bool CSpell::isCombatSpell() const
+{
+	return combatSpell;
+}
+
+bool CSpell::isAdventureSpell() const
+{
+	return !combatSpell;
+}
+
+bool CSpell::isCreatureAbility() const
+{
+	return creatureAbility;
+}
+
+bool CSpell::isPositive() const
+{
+	return positiveness == POSITIVE;
+}
+
+bool CSpell::isNegative() const
+{
+	return positiveness == NEGATIVE;
+}
+
+bool CSpell::isNeutral() const
+{
+	return positiveness == NEUTRAL;
+}
+
+bool CSpell::isHealingSpell() const
+{
+	return isRisingSpell() || (id == SpellID::CURE);
+}
+
+bool CSpell::isRisingSpell() const
+{
+	return isRising;
+}
+
+bool CSpell::isDamageSpell() const
+{
+	return isDamage;
+}
+
+bool CSpell::isOffensiveSpell() const
+{
+	return isOffensive;
+}
+
+bool CSpell::isSpecialSpell() const
+{
+	return isSpecial;
+}
+
+bool CSpell::hasEffects() const
+{
+	return !levels[0].effects.empty();
+}
+
+const std::string & CSpell::getIconImmune() const
+{
+	return iconImmune;
+}
+
+const std::string & CSpell::getCastSound() const
+{
+	return castSound;
+}
+
+si32 CSpell::getCost(const int skillLevel) const
+{
+	return getLevelInfo(skillLevel).cost;
+}
+
+si32 CSpell::getPower(const int skillLevel) const
+{
+	return getLevelInfo(skillLevel).power;
+}
+
+si32 CSpell::getProbability(const TFaction factionId) const
+{
+	if(!vstd::contains(probabilities,factionId))
+	{
+		return defaultProbability;
+	}
+	return probabilities.at(factionId);
+}
+
+void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
+{
+	if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
+	{
+		logGlobal->errorStream() << __FUNCTION__ << " invalid school level " << level;
+		return;
+	}
+
+	const std::vector<Bonus> & effects = levels[level].effects;
+
+	if(effects.empty())
+	{
+		logGlobal->errorStream() << __FUNCTION__ << " This spell ("  + name + ") has no effects for level " << level;
+		return;
+	}
+
+	lst.reserve(lst.size() + effects.size());
+
+	for(const Bonus & b : effects)
+	{
+		lst.push_back(Bonus(b));
+	}
+}
+
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
+{
+	// Get all stacks at destination hex. only alive if not rising spell
+	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
+		return s->coversPos(destination) && (isRisingSpell() || s->alive());
+	});
+
+	if(!stacks.empty())
+	{
+		bool allImmune = true;
+
+		ESpellCastProblem::ESpellCastProblem problem;
+
+		for(auto s : stacks)
+		{
+			ESpellCastProblem::ESpellCastProblem res = isImmuneByStack(caster,s);
+
+			if(res == ESpellCastProblem::OK)
+			{
+				allImmune = false;
+			}
+			else
+			{
+				problem = res;
+			}
+		}
+
+		if(allImmune)
+			return problem;
+	}
+	else //no target stack on this tile
+	{
+		if(getTargetType() == CSpell::CREATURE)
+		{
+			if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
+			{
+				const CSpell::TargetInfo ti(this, caster->getSpellSchoolLevel(this), mode);
+
+				if(!ti.massive)
+					return ESpellCastProblem::WRONG_SPELL_TARGET;
+			}
+			else
+			{
+ 				return ESpellCastProblem::WRONG_SPELL_TARGET;
+			}
+		}
+	}
+
+	return ESpellCastProblem::OK;
+}
+
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
+{
+	//todo: use new bonus API
+	//1. Check absolute limiters
+	for(auto b : absoluteLimiters)
+	{
+		if (!obj->hasBonusOfType(b))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+	//2. Check absolute immunities
+	for(auto b : absoluteImmunities)
+	{
+		if (obj->hasBonusOfType(b))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+	//check receptivity
+	if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
+		return ESpellCastProblem::OK;
+
+	//3. Check negation
+	//FIXME: Orb of vulnerability mechanics is not such trivial
+	if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
+		return ESpellCastProblem::NOT_DECIDED;
+
+	//4. Check negatable limit
+	for(auto b : limiters)
+	{
+		if (!obj->hasBonusOfType(b))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+
+	//5. Check negatable immunities
+	for(auto b : immunities)
+	{
+		if (obj->hasBonusOfType(b))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+	//6. Check elemental immunities
+
+	ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;
+
+	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+	{
+		auto element = cnf.immunityBonus;
+
+		if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether
+		{
+			tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			stop = true;
+		}
+		else if(!isPositive()) //negative or indifferent
+		{
+			if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
+			{
+				tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+				stop = true;
+			}
+		}
+	});
+
+	if(tmp != ESpellCastProblem::NOT_DECIDED)
+		return tmp;
+
+	TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
+
+	if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
+		|| ( levelImmunities->size() > 0  &&  levelImmunities->totalValue() >= level  &&  level))
+	{
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+
+	return ESpellCastProblem::NOT_DECIDED;
+}
+
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
+
+	if (ESpellCastProblem::NOT_DECIDED != immuneResult)
+		return immuneResult;
+	return ESpellCastProblem::OK;
+}
+
+void CSpell::setIsOffensive(const bool val)
+{
+	isOffensive = val;
+
+	if(val)
+	{
+		positiveness = CSpell::NEGATIVE;
+		isDamage = true;
+	}
+}
+
+void CSpell::setIsRising(const bool val)
+{
+	isRising = val;
+
+	if(val)
+	{
+		positiveness = CSpell::POSITIVE;
+	}
+}
+
+void CSpell::setup()
+{
+	setupMechanics();
+}
+
+void CSpell::setupMechanics()
+{
+	if(nullptr != mechanics)
+	{
+		logGlobal->errorStream() << "Spell " << this->name << " mechanics already set";
+		delete mechanics;
+	}
+
+	mechanics = ISpellMechanics::createMechanics(this);
+}
+
+///CSpell::AnimationInfo
+CSpell::AnimationInfo::AnimationInfo()
+{
+
+}
+
+CSpell::AnimationInfo::~AnimationInfo()
+{
+
+}
+
+std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
+{
+	std::string res;
+	double maximum = 0.0;
+
+	for(const auto & info : projectile)
+	{
+		if(info.minimumAngle < angle && info.minimumAngle > maximum)
+		{
+			maximum = info.minimumAngle;
+			res = info.resourceName;
+		}
+	}
+
+	return std::move(res);
+}
+
+///CSpell::TargetInfo
+CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level)
+{
+	init(spell, level);
+}
+
+CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode)
+{
+	init(spell, level);
+	if(mode == ECastingMode::ENCHANTER_CASTING)
+	{
+		smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
+		massive = true;
+	}
+	else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
+	{
+		alwaysHitDirectly = true;
+	}
+}
+
+void CSpell::TargetInfo::init(const CSpell * spell, const int level)
+{
+	auto & levelInfo = spell->getLevelInfo(level);
+
+	type = spell->getTargetType();
+	smart = levelInfo.smartTarget;
+	massive = levelInfo.range == "X";
+	onlyAlive = !spell->isRisingSpell();
+	alwaysHitDirectly = false;
+	clearAffected = levelInfo.clearAffected;
+	clearTarget = levelInfo.clearTarget;
+}
+
+bool DLL_LINKAGE isInScreenRange(const int3 & center, const int3 & pos)
+{
+	int3 diff = pos - center;
+	if(diff.x >= -9  &&  diff.x <= 9  &&  diff.y >= -8  &&  diff.y <= 8)
+		return true;
+	else
+		return false;
+}
+
+///CSpellHandler
+CSpellHandler::CSpellHandler()
+{
+
+}
+
+std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
+{
+	using namespace SpellConfig;
+	std::vector<JsonNode> legacyData;
+
+	CLegacyConfigParser parser("DATA/SPTRAITS.TXT");
+
+	auto readSchool = [&](JsonMap & schools, const std::string & name)
+	{
+		if (parser.readString() == "x")
+		{
+			schools[name].Bool() = true;
+		}
+	};
+
+	auto read = [&,this](bool combat, bool ability)
+	{
+		do
+		{
+			JsonNode lineNode(JsonNode::DATA_STRUCT);
+
+			const si32 id = legacyData.size();
+
+			lineNode["index"].Float() = id;
+			lineNode["type"].String() = ability ? "ability" : (combat ? "combat" : "adventure");
+
+			lineNode["name"].String() = parser.readString();
+
+			parser.readString(); //ignored unused abbreviated name
+			lineNode["level"].Float()      = parser.readNumber();
+
+			auto& schools = lineNode["school"].Struct();
+
+			readSchool(schools, "earth");
+			readSchool(schools, "water");
+			readSchool(schools, "fire");
+			readSchool(schools, "air");
+
+			auto& levels = lineNode["levels"].Struct();
+
+			auto getLevel = [&](const size_t idx)->JsonMap&
+			{
+				assert(idx < GameConstants::SPELL_SCHOOL_LEVELS);
+				return levels[LEVEL_NAMES[idx]].Struct();
+			};
+
+			auto costs = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
+			lineNode["power"].Float() = parser.readNumber();
+			auto powers = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
+
+			auto& chances = lineNode["gainChance"].Struct();
+
+			for(size_t i = 0; i < GameConstants::F_NUMBER; i++){
+				chances[ETownType::names[i]].Float() = parser.readNumber();
+			}
+
+			auto AIVals = parser.readNumArray<si32>(GameConstants::SPELL_SCHOOL_LEVELS);
+
+			std::vector<std::string> descriptions;
+			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
+				descriptions.push_back(parser.readString());
+
+			parser.readString(); //ignore attributes. All data present in JSON
+
+			//save parsed level specific data
+			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
+			{
+				auto& level = getLevel(i);
+				level["description"].String() = descriptions[i];
+				level["cost"].Float() = costs[i];
+				level["power"].Float() = powers[i];
+				level["aiValue"].Float() = AIVals[i];
+			}
+
+			legacyData.push_back(lineNode);
+		}
+		while (parser.endLine() && !parser.isNextEntryEmpty());
+	};
+
+	auto skip = [&](int cnt)
+	{
+		for(int i=0; i<cnt; i++)
+			parser.endLine();
+	};
+
+	skip(5);// header
+	read(false,false); //read adventure map spells
+	skip(3);
+	read(true,false); //read battle spells
+	skip(3);
+	read(true,true);//read creature abilities
+
+    //TODO: maybe move to config
+	//clone Acid Breath attributes for Acid Breath damage effect
+	JsonNode temp = legacyData[SpellID::ACID_BREATH_DEFENSE];
+	temp["index"].Float() = SpellID::ACID_BREATH_DAMAGE;
+	legacyData.push_back(temp);
+
+	objects.resize(legacyData.size());
+
+	return legacyData;
+}
+
+const std::string CSpellHandler::getTypeName() const
+{
+	return "spell";
+}
+
+CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
+{
+	using namespace SpellConfig;
+
+	CSpell * spell = new CSpell();
+
+	const auto type = json["type"].String();
+
+	if(type == "ability")
+	{
+		spell->creatureAbility = true;
+		spell->combatSpell = true;
+	}
+	else
+	{
+		spell->creatureAbility = false;
+		spell->combatSpell = type == "combat";
+	}
+
+	spell->name = json["name"].String();
+
+	logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;
+
+	const auto schoolNames = json["school"];
+
+	for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)
+	{
+		spell->school[info.id] = schoolNames[info.jsonName].Bool();
+	}
+
+	spell->level = json["level"].Float();
+	spell->power = json["power"].Float();
+
+	spell->defaultProbability = json["defaultGainChance"].Float();
+
+	for(const auto & node : json["gainChance"].Struct())
+	{
+		const int chance = node.second.Float();
+
+		VLC->modh->identifiers.requestIdentifier(node.second.meta, "faction",node.first, [=](si32 factionID)
+		{
+			spell->probabilities[factionID] = chance;
+		});
+	}
+
+	auto targetType = json["targetType"].String();
+
+	if(targetType == "NO_TARGET")
+		spell->targetType = CSpell::NO_TARGET;
+	else if(targetType == "CREATURE")
+		spell->targetType = CSpell::CREATURE;
+	else if(targetType == "OBSTACLE")
+		spell->targetType = CSpell::OBSTACLE;
+	else if(targetType == "LOCATION")
+		spell->targetType = CSpell::LOCATION;
+	else
+		logGlobal->warnStream() << "Spell " << spell->name << ". Target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ". Assumed NO_TARGET.";
+
+	for(const auto & counteredSpell: json["counters"].Struct())
+		if (counteredSpell.second.Bool())
+		{
+			VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
+			{
+				spell->counteredSpells.push_back(SpellID(id));
+			});
+		}
+
+	//TODO: more error checking - f.e. conflicting flags
+	const auto flags = json["flags"];
+
+	//by default all flags are set to false in constructor
+
+	spell->isDamage = flags["damage"].Bool(); //do this before "offensive"
+
+	if(flags["offensive"].Bool())
+	{
+		spell->setIsOffensive(true);
+	}
+
+	if(flags["rising"].Bool())
+	{
+		spell->setIsRising(true);
+	}
+
+	const bool implicitPositiveness = spell->isOffensive || spell->isRising; //(!) "damage" does not mean NEGATIVE  --AVS
+
+	if(flags["indifferent"].Bool())
+	{
+		spell->positiveness = CSpell::NEUTRAL;
+	}
+	else if(flags["negative"].Bool())
+	{
+		spell->positiveness = CSpell::NEGATIVE;
+	}
+	else if(flags["positive"].Bool())
+	{
+		spell->positiveness = CSpell::POSITIVE;
+	}
+	else if(!implicitPositiveness)
+	{
+		spell->positiveness = CSpell::NEUTRAL; //duplicates constructor but, just in case
+		logGlobal->errorStream() << "No positiveness specified, assumed NEUTRAL";
+	}
+
+	spell->isSpecial = flags["special"].Bool();
+
+	auto findBonus = [&](std::string name, std::vector<Bonus::BonusType> & vec)
+	{
+		auto it = bonusNameMap.find(name);
+		if(it == bonusNameMap.end())
+		{
+			logGlobal->errorStream() << spell->name << ": invalid bonus name" << name;
+		}
+		else
+		{
+			vec.push_back((Bonus::BonusType)it->second);
+		}
+	};
+
+	auto readBonusStruct = [&](std::string name, std::vector<Bonus::BonusType> & vec)
+	{
+		for(auto bonusData: json[name].Struct())
+		{
+			const std::string bonusId = bonusData.first;
+			const bool flag = bonusData.second.Bool();
+
+			if(flag)
+				findBonus(bonusId, vec);
+		}
+	};
+
+	readBonusStruct("immunity", spell->immunities);
+	readBonusStruct("absoluteImmunity", spell->absoluteImmunities);
+	readBonusStruct("limit", spell->limiters);
+	readBonusStruct("absoluteLimit", spell->absoluteLimiters);
+
+
+	const JsonNode & graphicsNode = json["graphics"];
+
+	spell->iconImmune = graphicsNode["iconImmune"].String();
+	spell->iconBook = graphicsNode["iconBook"].String();
+	spell->iconEffect = graphicsNode["iconEffect"].String();
+	spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
+	spell->iconScroll = graphicsNode["iconScroll"].String();
+
+	const JsonNode & animationNode = json["animation"];
+
+	auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)
+	{
+		auto queueNode = animationNode[jsonName].Vector();
+		for(const JsonNode & item : queueNode)
+		{
+			CSpell::TAnimation newItem;
+			newItem.verticalPosition = VerticalPosition::TOP;
+
+			if(item.getType() == JsonNode::DATA_STRING)
+				newItem.resourceName = item.String();
+			else if(item.getType() == JsonNode::DATA_STRUCT)
+			{
+				newItem.resourceName = item["defName"].String();
+
+				auto vPosStr = item["verticalPosition"].String();
+				if("bottom" == vPosStr)
+					newItem.verticalPosition = VerticalPosition::BOTTOM;
+			}
+			q.push_back(newItem);
+		}
+	};
+
+	loadAnimationQueue("affect", spell->animationInfo.affect);
+	loadAnimationQueue("cast", spell->animationInfo.cast);
+	loadAnimationQueue("hit", spell->animationInfo.hit);
+
+	const JsonVector & projectile = animationNode["projectile"].Vector();
+
+	for(const JsonNode & item : projectile)
+	{
+		CSpell::ProjectileInfo info;
+		info.resourceName = item["defName"].String();
+		info.minimumAngle = item["minimumAngle"].Float();
+
+		spell->animationInfo.projectile.push_back(info);
+	}
+
+	const JsonNode & soundsNode = json["sounds"];
+	spell->castSound = soundsNode["cast"].String();
+
+
+	//load level attributes
+
+	const int levelsCount = GameConstants::SPELL_SCHOOL_LEVELS;
+
+	for(int levelIndex = 0; levelIndex < levelsCount; levelIndex++)
+	{
+		const JsonNode & levelNode = json["levels"][LEVEL_NAMES[levelIndex]];
+
+		CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
+
+		const si32 levelPower     = levelObject.power = levelNode["power"].Float();
+
+		levelObject.description   = levelNode["description"].String();
+		levelObject.cost          = levelNode["cost"].Float();
+		levelObject.AIValue       = levelNode["aiValue"].Float();
+		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();
+		levelObject.clearTarget   = levelNode["targetModifier"]["clearTarget"].Bool();
+		levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();
+		levelObject.range         = levelNode["range"].String();
+
+		for(const auto & elem : levelNode["effects"].Struct())
+		{
+			const JsonNode & bonusNode = elem.second;
+			Bonus * b = JsonUtils::parseBonus(bonusNode);
+			const bool usePowerAsValue = bonusNode["val"].isNull();
+
+			//TODO: make this work. see CSpellHandler::afterLoadFinalization()
+			//b->sid = spell->id; //for all
+
+			b->source = Bonus::SPELL_EFFECT;//for all
+
+			if(usePowerAsValue)
+				b->val = levelPower;
+
+			levelObject.effects.push_back(*b);
+		}
+
+	}
+
+	return spell;
+}
+
+void CSpellHandler::afterLoadFinalization()
+{
+	//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
+	for(auto spell: objects)
+	{
+		for(auto & level: spell->levels)
+			for(auto & bonus: level.effects)
+				bonus.sid = spell->id;
+		spell->setup();
+	}
+}
+
+void CSpellHandler::beforeValidate(JsonNode & object)
+{
+	//handle "base" level info
+
+	JsonNode & levels = object["levels"];
+	JsonNode & base = levels["base"];
+
+	auto inheritNode = [&](const std::string & name){
+		JsonUtils::inherit(levels[name],base);
+	};
+
+	inheritNode("none");
+	inheritNode("basic");
+	inheritNode("advanced");
+	inheritNode("expert");
+}
+
+
+CSpellHandler::~CSpellHandler()
+{
+
+}
+
+std::vector<bool> CSpellHandler::getDefaultAllowed() const
+{
+	std::vector<bool> allowedSpells;
+	allowedSpells.resize(GameConstants::SPELLS_QUANTITY, true);
+	return allowedSpells;
+}

+ 386 - 397
lib/CSpellHandler.h → lib/spells/CSpellHandler.h

@@ -1,397 +1,386 @@
-#pragma once
-
-#include "IHandlerBase.h"
-#include "../lib/ConstTransitivePtr.h"
-#include "int3.h"
-#include "GameConstants.h"
-#include "BattleHex.h"
-#include "HeroBonus.h"
-
-
-/*
- * CSpellHandler.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
- *
- */
-
-class CSpell;
-class ISpellMechanics;
-
-class CLegacyConfigParser;
-
-class CGHeroInstance;
-class CStack;
-
-class CBattleInfoCallback;
-class BattleInfo;
-
-struct CPackForClient;
-struct BattleSpellCast;
-
-class CRandomGenerator;
-
-struct SpellSchoolInfo
-{
-	ESpellSchool id; //backlink
-	Bonus::BonusType damagePremyBonus;
-	Bonus::BonusType immunityBonus;	
-	std::string jsonName;
-	SecondarySkill::ESecondarySkill skill;
-	Bonus::BonusType knoledgeBonus;			
-};
-
-///callback to be provided by server
-class DLL_LINKAGE SpellCastEnvironment
-{
-public:
-	virtual ~SpellCastEnvironment(){};
-	virtual void sendAndApply(CPackForClient * info) const = 0;
-	
-	virtual CRandomGenerator & getRandomGenerator() const = 0;
-	virtual void complain(const std::string & problem) const = 0;
-};
-
-///helper struct
-struct DLL_LINKAGE BattleSpellCastParameters
-{
-public:
-	BattleSpellCastParameters(const BattleInfo * cb);
-	int spellLvl;
-	BattleHex destination;
-	ui8 casterSide;
-	PlayerColor casterColor;
-	const CGHeroInstance * caster;
-	const CGHeroInstance * secHero;
-	int usedSpellPower;
-	ECastingMode::ECastingMode mode;
-	const CStack * casterStack;
-	const CStack * selectedStack;	
-	const BattleInfo * cb;		
-};
-
-enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
-
-class DLL_LINKAGE CSpell
-{
-public:
-	
-	struct ProjectileInfo
-	{
-		///in radians. Only positive value. Negative angle is handled by vertical flip
-		double minimumAngle; 
-		
-		///resource name
-		std::string resourceName;
-		 
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & minimumAngle & resourceName; 
-		}		
-	};
-	
-	struct AnimationItem
-	{
-		std::string resourceName;
-		VerticalPosition verticalPosition;
-		
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & resourceName & verticalPosition; 
-		}		
-	};
-	
-	typedef AnimationItem TAnimation;
-	typedef std::vector<TAnimation> TAnimationQueue; 
-	
-	struct DLL_LINKAGE AnimationInfo
-	{
-		AnimationInfo();
-		~AnimationInfo();
-
-		///displayed on all affected targets. 
-		TAnimationQueue affect;
-
-		///displayed on caster.
-		TAnimationQueue cast;
-
-		///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
-		TAnimationQueue hit;
-
-		///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
-		///use selectProjectile to access
-		std::vector<ProjectileInfo> projectile;
-
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & projectile & hit & cast;
-		}
-
-		std::string selectProjectile(const double angle) const;
-	} animationInfo;
-	
-public:
-	struct LevelInfo
-	{
-		std::string description; //descriptions of spell for skill level
-		si32 cost;
-		si32 power;
-		si32 AIValue;
-
-		bool smartTarget;
-		bool clearTarget;
-		bool clearAffected;
-		std::string range;
-
-		std::vector<Bonus> effects;
-
-		LevelInfo();
-		~LevelInfo();
-
-		template <typename Handler> void serialize(Handler &h, const int version)
-		{
-			h & description & cost & power & AIValue & smartTarget & range & effects;
-			h & clearTarget & clearAffected;
-		}
-	};
-
-	/** \brief Low level accessor. Don`t use it if absolutely necessary
-	 *
-	 * \param level. spell school level
-	 * \return Spell level info structure
-	 *
-	 */
-	const CSpell::LevelInfo& getLevelInfo(const int level) const;
-public:
-	enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
-	enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
-
-	struct TargetInfo
-	{
-		ETargetType type;
-		bool smart;
-		bool massive;
-		bool onlyAlive;
-		///no immunity on primary target (mostly spell-like attack)
-		bool alwaysHitDirectly;
-		
-		bool clearTarget;
-		bool clearAffected;
-		
-		TargetInfo(const CSpell * spell, const int level);
-		TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
-		
-	private:
-		void init(const CSpell * spell, const int level);
-	};
-
-	SpellID id;
-	std::string identifier; //???
-	std::string name;
-
-	si32 level;
-	bool earth; //deprecated
-	bool water; //deprecated
-	bool fire;  //deprecated
-	bool air;   //deprecated
-	
-	std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
-	
-	si32 power; //spell's power
-
-	std::map<TFaction, si32> probabilities; //% chance to gain for castles
-
-	bool combatSpell; //is this spell combat (true) or adventure (false)
-	bool creatureAbility; //if true, only creatures can use this spell
-	si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
-
-	std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
-
-	CSpell();
-	~CSpell();
-	
-	bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
-	
-
-	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
-	ETargetType getTargetType() const; //deprecated
-
-	CSpell::TargetInfo getTargetInfo(const int level) const;
-
-
-	bool isCombatSpell() const;
-	bool isAdventureSpell() const;
-	bool isCreatureAbility() const;
-
-	bool isPositive() const;
-	bool isNegative() const;
-	bool isNeutral() const;
-
-	bool isDamageSpell() const;
-	bool isHealingSpell() const;
-	bool isRisingSpell() const;	
-	bool isOffensiveSpell() const;
-
-	bool isSpecialSpell() const;
-
-	bool hasEffects() const;
-	void getEffects(std::vector<Bonus> &lst, const int level) const;
-	
-	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
-	
-	//internal, for use only by Mechanics classes
-	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
-	
-	//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
-	
-	//internal, for use only by Mechanics classes. applying secondary skills
-	ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
-	
-	///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
-	ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
-	
-	///calculate healed HP for all spells casted by hero
-	ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const;
-	
-	///selects from allStacks actually affected stacks
-	std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
-
-	si32 getCost(const int skillLevel) const;
-
-	/**
-	 * Returns spell level power, base power ignored
-	 */
-	si32 getPower(const int skillLevel) const;
-
-//	/**
-//	* Returns spell power, taking base power into account
-//	*/
-//	si32 calculatePower(const int skillLevel) const;
-
-
-	si32 getProbability(const TFaction factionId) const;
-
-	/**
-	 * Calls cb for each school this spell belongs to
-	 *
-	 * Set stop to true to abort looping
-	 */	
-	void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
-
-	/**
-	 * Returns resource name of icon for SPELL_IMMUNITY bonus
-	 */
-	const std::string& getIconImmune() const;
-
-	const std::string& getCastSound() const;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & identifier & id & name & level & power
-		  & probabilities  & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
-		h & isRising & isDamage & isOffensive;
-		h & targetType;
-		h & immunities & limiters & absoluteImmunities & absoluteLimiters;
-		h & iconImmune;
-		h & defaultProbability;
-		h & isSpecial;
-		h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
-		h & levels;		
-		h & school;		
-		h & animationInfo;
-
-		if(!h.saving)
-			setup();
-	}
-	friend class CSpellHandler;
-	friend class Graphics;
-public:
-	///Server logic. Has write access to GameState via packets.
-	///May be executed on client side by (future) non-cheat-proof scripts.
-	
-	//void adventureCast() const; 
-	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const; 	
-		
-public:	
-	///Client-server logic. Has direct write access to GameState.
-	///Shall be called (only) when applying packets on BOTH SIDES
-	
-	///implementation of BattleSpellCast applying
-	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const;
-		
-private:
-	void setIsOffensive(const bool val);
-	void setIsRising(const bool val);
-	
-	//call this after load or deserialization. cant be done in constructor.
-	void setup();
-	void setupMechanics();
-private:
-	si32 defaultProbability;
-
-	bool isRising;
-	bool isDamage;
-	bool isOffensive;
-	bool isSpecial;
-
-	std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
-
-	ETargetType targetType;
-
-	std::vector<Bonus::BonusType> immunities; //any of these grants immunity
-	std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, can't be negated
-	std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
-	std::vector<Bonus::BonusType> absoluteLimiters; //all of them are required to be affected, can't be negated
-
-	///graphics related stuff
-
-	std::string iconImmune;
-
-	std::string iconBook;
-	std::string iconEffect;
-	std::string iconScenarioBonus;
-	std::string iconScroll;
-
-	///sound related stuff
-	std::string castSound;
-
-	std::vector<LevelInfo> levels;
-	
-	ISpellMechanics * mechanics;//(!) do not serialize
-};
-
-bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
-
-class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
-{
-public:
-	CSpellHandler();
-	virtual ~CSpellHandler();
-
-	///IHandler base
-	std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
-	void afterLoadFinalization() override;
-	void beforeValidate(JsonNode & object) override;
-
-	/**
-	 * Gets a list of default allowed spells. OH3 spells are all allowed by default.
-	 *
-	 * @return a list of allowed spells, the index is the spell id and the value either 0 for not allowed or 1 for allowed
-	 */
-	std::vector<bool> getDefaultAllowed() const override;
-
-	const std::string getTypeName() const override;
-
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & objects ;
-	}
-		
-protected:
-	CSpell * loadFromJson(const JsonNode & json) override;
-};
+/*
+ * CSpellHandler.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 "../IHandlerBase.h"
+#include "../ConstTransitivePtr.h"
+#include "../int3.h"
+#include "../GameConstants.h"
+#include "../BattleHex.h"
+#include "../HeroBonus.h"
+
+class CGObjectInstance;
+class CSpell;
+class ISpellMechanics;
+class CLegacyConfigParser;
+class CGHeroInstance;
+class CStack;
+class CBattleInfoCallback;
+class BattleInfo;
+struct CPackForClient;
+struct BattleSpellCast;
+class CGameInfoCallback;
+class CRandomGenerator;
+class CMap;
+
+struct SpellSchoolInfo
+{
+	ESpellSchool id; //backlink
+	Bonus::BonusType damagePremyBonus;
+	Bonus::BonusType immunityBonus;
+	std::string jsonName;
+	SecondarySkill::ESecondarySkill skill;
+	Bonus::BonusType knoledgeBonus;
+};
+
+///callback to be provided by server
+class DLL_LINKAGE SpellCastEnvironment
+{
+public:
+	virtual ~SpellCastEnvironment(){};
+	virtual void sendAndApply(CPackForClient * info) const = 0;
+
+	virtual CRandomGenerator & getRandomGenerator() const = 0;
+	virtual void complain(const std::string & problem) const = 0;
+
+	virtual const CMap * getMap() const = 0;
+	virtual const CGameInfoCallback * getCb() const = 0;
+
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0;	//TODO: remove
+};
+
+///helper struct
+struct DLL_LINKAGE BattleSpellCastParameters
+{
+public:
+	BattleSpellCastParameters(const BattleInfo * cb);
+	int spellLvl;
+	BattleHex destination;
+	ui8 casterSide;
+	PlayerColor casterColor;
+	const CGHeroInstance * caster;
+	const CGHeroInstance * secHero;
+	int usedSpellPower;
+	ECastingMode::ECastingMode mode;
+	const CStack * casterStack;
+	const CStack * selectedStack;
+	const BattleInfo * cb;
+};
+
+struct DLL_LINKAGE AdventureSpellCastParameters
+{
+	const CGHeroInstance * caster;
+	int3 pos;
+};
+
+enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
+
+class DLL_LINKAGE CSpell
+{
+public:
+	struct ProjectileInfo
+	{
+		///in radians. Only positive value. Negative angle is handled by vertical flip
+		double minimumAngle;
+
+		///resource name
+		std::string resourceName;
+
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & minimumAngle & resourceName;
+		}
+	};
+
+	struct AnimationItem
+	{
+		std::string resourceName;
+		VerticalPosition verticalPosition;
+
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & resourceName & verticalPosition;
+		}
+	};
+
+	typedef AnimationItem TAnimation;
+	typedef std::vector<TAnimation> TAnimationQueue;
+
+	struct DLL_LINKAGE AnimationInfo
+	{
+		AnimationInfo();
+		~AnimationInfo();
+
+		///displayed on all affected targets.
+		TAnimationQueue affect;
+
+		///displayed on caster.
+		TAnimationQueue cast;
+
+		///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
+		TAnimationQueue hit;
+
+		///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
+		///use selectProjectile to access
+		std::vector<ProjectileInfo> projectile;
+
+		template <typename Handler> void serialize(Handler & h, const int version)
+		{
+			h & projectile & hit & cast;
+		}
+
+		std::string selectProjectile(const double angle) const;
+	} animationInfo;
+public:
+	struct LevelInfo
+	{
+		std::string description; //descriptions of spell for skill level
+		si32 cost;
+		si32 power;
+		si32 AIValue;
+
+		bool smartTarget;
+		bool clearTarget;
+		bool clearAffected;
+		std::string range;
+
+		std::vector<Bonus> effects;
+
+		LevelInfo();
+		~LevelInfo();
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & description & cost & power & AIValue & smartTarget & range & effects;
+			h & clearTarget & clearAffected;
+		}
+	};
+
+	/** \brief Low level accessor. Don`t use it if absolutely necessary
+	 *
+	 * \param level. spell school level
+	 * \return Spell level info structure
+	 *
+	 */
+	const CSpell::LevelInfo& getLevelInfo(const int level) const;
+public:
+	enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
+	enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
+
+	struct TargetInfo
+	{
+		ETargetType type;
+		bool smart;
+		bool massive;
+		bool onlyAlive;
+		///no immunity on primary target (mostly spell-like attack)
+		bool alwaysHitDirectly;
+
+		bool clearTarget;
+		bool clearAffected;
+
+		TargetInfo(const CSpell * spell, const int level);
+		TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
+
+	private:
+		void init(const CSpell * spell, const int level);
+	};
+
+	SpellID id;
+	std::string identifier; //???
+	std::string name;
+
+	si32 level;
+
+	std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
+
+	si32 power; //spell's power
+
+	std::map<TFaction, si32> probabilities; //% chance to gain for castles
+
+	bool combatSpell; //is this spell combat (true) or adventure (false)
+	bool creatureAbility; //if true, only creatures can use this spell
+	si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative
+
+	std::vector<SpellID> counteredSpells; //spells that are removed when effect of this spell is placed on creature (for bless-curse, haste-slow, and similar pairs)
+
+	CSpell();
+	~CSpell();
+
+	bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
+
+	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
+	ETargetType getTargetType() const; //deprecated
+
+	CSpell::TargetInfo getTargetInfo(const int level) const;
+
+	bool isCombatSpell() const;
+	bool isAdventureSpell() const;
+	bool isCreatureAbility() const;
+
+	bool isPositive() const;
+	bool isNegative() const;
+	bool isNeutral() const;
+
+	bool isDamageSpell() const;
+	bool isHealingSpell() const;
+	bool isRisingSpell() const;
+	bool isOffensiveSpell() const;
+
+	bool isSpecialSpell() const;
+
+	bool hasEffects() const;
+	void getEffects(std::vector<Bonus> &lst, const int level) const;
+
+	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
+
+	//internal, for use only by Mechanics classes
+	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
+
+	//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
+
+	//internal, for use only by Mechanics classes. applying secondary skills
+	ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
+
+	///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
+	ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
+
+	///selects from allStacks actually affected stacks
+	std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
+
+	si32 getCost(const int skillLevel) const;
+
+	/**
+	 * Returns spell level power, base power ignored
+	 */
+	si32 getPower(const int skillLevel) const;
+
+	si32 getProbability(const TFaction factionId) const;
+
+	/**
+	 * Calls cb for each school this spell belongs to
+	 *
+	 * Set stop to true to abort looping
+	 */
+	void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
+
+	/**
+	 * Returns resource name of icon for SPELL_IMMUNITY bonus
+	 */
+	const std::string& getIconImmune() const;
+
+	const std::string& getCastSound() const;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & identifier & id & name & level & power
+		  & probabilities  & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
+		h & isRising & isDamage & isOffensive;
+		h & targetType;
+		h & immunities & limiters & absoluteImmunities & absoluteLimiters;
+		h & iconImmune;
+		h & defaultProbability;
+		h & isSpecial;
+		h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
+		h & levels;
+		h & school;
+		h & animationInfo;
+
+		if(!h.saving)
+			setup();
+	}
+	friend class CSpellHandler;
+	friend class Graphics;
+public:
+	///Server logic. Has write access to GameState via packets.
+	///May be executed on client side by (future) non-cheat-proof scripts.
+
+	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
+	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
+
+public:
+	///Client-server logic. Has direct write access to GameState.
+	///Shall be called (only) when applying packets on BOTH SIDES
+
+	///implementation of BattleSpellCast applying
+	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
+
+private:
+	void setIsOffensive(const bool val);
+	void setIsRising(const bool val);
+
+	//call this after load or deserialization. cant be done in constructor.
+	void setup();
+	void setupMechanics();
+private:
+	si32 defaultProbability;
+
+	bool isRising;
+	bool isDamage;
+	bool isOffensive;
+	bool isSpecial;
+
+	std::string attributes; //reference only attributes //todo: remove or include in configuration format, currently unused
+
+	ETargetType targetType;
+
+	std::vector<Bonus::BonusType> immunities; //any of these grants immunity
+	std::vector<Bonus::BonusType> absoluteImmunities; //any of these grants immunity, can't be negated
+	std::vector<Bonus::BonusType> limiters; //all of them are required to be affected
+	std::vector<Bonus::BonusType> absoluteLimiters; //all of them are required to be affected, can't be negated
+
+	///graphics related stuff
+	std::string iconImmune;
+	std::string iconBook;
+	std::string iconEffect;
+	std::string iconScenarioBonus;
+	std::string iconScroll;
+
+	///sound related stuff
+	std::string castSound;
+
+	std::vector<LevelInfo> levels;
+
+	ISpellMechanics * mechanics;//(!) do not serialize
+};
+
+bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
+
+class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
+{
+public:
+	CSpellHandler();
+	virtual ~CSpellHandler();
+
+	///IHandler base
+	std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
+	void afterLoadFinalization() override;
+	void beforeValidate(JsonNode & object) override;
+
+	/**
+	 * Gets a list of default allowed spells. OH3 spells are all allowed by default.
+	 *
+	 * @return a list of allowed spells, the index is the spell id and the value either 0 for not allowed or 1 for allowed
+	 */
+	std::vector<bool> getDefaultAllowed() const override;
+
+	const std::string getTypeName() const override;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & objects ;
+	}
+
+protected:
+	CSpell * loadFromJson(const JsonNode & json) override;
+};

+ 95 - 0
lib/spells/CreatureSpellMechanics.cpp

@@ -0,0 +1,95 @@
+/*
+ * CreatureSpellMechanics.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 "CreatureSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.h"
+
+///AcidBreathDamageMechanics
+void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+
+	for(auto & attackedCre : ctx.attackedCres) //no immunities
+	{
+		BattleStackAttacked bsa;
+		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
+		bsa.spellID = owner->id;
+		bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
+		bsa.stackAttacked = (attackedCre)->ID;
+		bsa.attackerID = -1;
+		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+		ctx.si.stacks.push_back(bsa);
+	}
+}
+
+///DeathStareMechanics
+void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	if(!ctx.attackedCres.empty())
+		vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
+
+	for(auto & attackedCre : ctx.attackedCres)
+	{
+		BattleStackAttacked bsa;
+		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
+		bsa.spellID = owner->id;
+		bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
+		bsa.stackAttacked = (attackedCre)->ID;
+		bsa.attackerID = -1;
+		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+		ctx.si.stacks.push_back(bsa);
+	}
+}
+
+///DispellHelpfulMechanics
+void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(battle, packet);
+
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			return Selector::positiveSpellEffects(b);
+		});
+	}
+}
+
+ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster,  const CStack * obj) const
+{
+	TBonusListPtr spellBon = obj->getSpellBonuses();
+	bool hasPositiveSpell = false;
+	for(const Bonus * b : *spellBon)
+	{
+		if(SpellID(b->sid).toSpell()->isPositive())
+		{
+			hasPositiveSpell = true;
+			break;
+		}
+	}
+	if(!hasPositiveSpell)
+	{
+		return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
+	}
+
+	//use default algorithm only if there is no mechanics-related problem
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);
+}

+ 40 - 0
lib/spells/CreatureSpellMechanics.h

@@ -0,0 +1,40 @@
+/*
+ * CreatureSpellMechanics.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 "ISpellMechanics.h"
+#include "CDefaultSpellMechanics.h"
+
+class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
+{
+public:
+	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
+{
+public:
+	DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+};
+
+class DLL_LINKAGE DispellHelpfulMechanics : public DefaultSpellMechanics
+{
+public:
+	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
+
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+};

+ 87 - 0
lib/spells/ISpellMechanics.cpp

@@ -0,0 +1,87 @@
+/*
+ * ISpellMechanics.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 "ISpellMechanics.h"
+
+#include "CDefaultSpellMechanics.h"
+
+#include "AdventureSpellMechanics.h"
+#include "BattleSpellMechanics.h"
+#include "CreatureSpellMechanics.h"
+
+///ISpellMechanics
+ISpellMechanics::ISpellMechanics(CSpell * s):
+	owner(s)
+{
+
+}
+
+ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
+{
+	switch (s->id)
+	{
+	case SpellID::ACID_BREATH_DAMAGE:
+		return new AcidBreathDamageMechanics(s);
+	case SpellID::CHAIN_LIGHTNING:
+		return new ChainLightningMechanics(s);
+	case SpellID::CLONE:
+		return new CloneMechanics(s);
+	case SpellID::CURE:
+		return new CureMechanics(s);
+	case SpellID::DEATH_STARE:
+		return new DeathStareMechanics(s);
+	case SpellID::DISPEL:
+		return new DispellMechanics(s);
+	case SpellID::DISPEL_HELPFUL_SPELLS:
+		return new DispellHelpfulMechanics(s);
+	case SpellID::FIRE_WALL:
+	case SpellID::FORCE_FIELD:
+		return new WallMechanics(s);
+	case SpellID::HYPNOTIZE:
+		return new HypnotizeMechanics(s);
+	case SpellID::LAND_MINE:
+	case SpellID::QUICKSAND:
+		return new ObstacleMechanics(s);
+	case SpellID::REMOVE_OBSTACLE:
+		return new RemoveObstacleMechanics(s);
+	case SpellID::SACRIFICE:
+		return new SacrificeMechanics(s);
+	case SpellID::SUMMON_FIRE_ELEMENTAL:
+	case SpellID::SUMMON_EARTH_ELEMENTAL:
+	case SpellID::SUMMON_WATER_ELEMENTAL:
+	case SpellID::SUMMON_AIR_ELEMENTAL:
+		return new SummonMechanics(s);
+	case SpellID::TELEPORT:
+		return new TeleportMechanics(s);
+	case SpellID::SUMMON_BOAT:
+		return new SummonBoatMechanics(s);
+	case SpellID::SCUTTLE_BOAT:
+		return new ScuttleBoatMechanics(s);
+	case SpellID::DIMENSION_DOOR:
+		return new DimensionDoorMechanics(s);
+	case SpellID::FLY:
+	case SpellID::WATER_WALK:
+	case SpellID::VISIONS:
+	case SpellID::DISGUISE:
+		return new DefaultSpellMechanics(s); //implemented using bonus system
+	case SpellID::TOWN_PORTAL:
+		return new TownPortalMechanics(s);
+	case SpellID::VIEW_EARTH:
+		return new ViewEarthMechanics(s);
+	case SpellID::VIEW_AIR:
+		return new ViewAirMechanics(s);
+	default:
+		if(s->isRisingSpell())
+			return new SpecialRisingSpellMechanics(s);
+		else
+			return new DefaultSpellMechanics(s);
+	}
+}

+ 16 - 21
lib/SpellMechanics.h → lib/spells/ISpellMechanics.h

@@ -1,5 +1,5 @@
 /*
- * SpellMechanics.h, part of VCMI engine
+ * ISpellMechanics.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -7,48 +7,43 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
- 
+
 #pragma once
 
 #include "CSpellHandler.h"
-#include "BattleHex.h"
+#include "../BattleHex.h"
 
 class DLL_LINKAGE ISpellMechanics
 {
 public:
-	
 	struct DLL_LINKAGE SpellTargetingContext
 	{
-		const CBattleInfoCallback * cb;		
+		const CBattleInfoCallback * cb;
 		CSpell::TargetInfo ti;
 		ECastingMode::ECastingMode mode;
 		BattleHex destination;
 		PlayerColor casterColor;
 		int schoolLvl;
-		
+
 		SpellTargetingContext(const CSpell * s, const CBattleInfoCallback * c, ECastingMode::ECastingMode m, PlayerColor cc, int lvl, BattleHex dest)
 			: cb(c), ti(s,lvl, m), mode(m), destination(dest), casterColor(cc), schoolLvl(lvl)
 		{};
-		
+
 	};
-	
 public:
 	ISpellMechanics(CSpell * s);
-	virtual ~ISpellMechanics(){};	
-	
-	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const = 0;
+	virtual ~ISpellMechanics(){};
+
+	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
 	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
-	
+
 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
-                   
-	//virtual bool adventureCast(const SpellCastContext & context) const = 0; 
-	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0; 	
-	
+
+	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
+	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
+	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
+
 	static ISpellMechanics * createMechanics(CSpell * s);
-	
-	virtual void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
-	
 protected:
-	CSpell * owner;	
+	CSpell * owner;
 };
-

+ 27 - 0
lib/spells/ViewSpellInt.cpp

@@ -0,0 +1,27 @@
+/*
+ * ViewSpellInt.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 "ViewSpellInt.h"
+
+#include "../mapObjects/CObjectHandler.h"
+
+ObjectPosInfo::ObjectPosInfo():
+	pos(),id(Obj::NO_OBJ), subId(-1), owner(PlayerColor::CANNOT_DETERMINE)
+{
+
+}
+
+ObjectPosInfo::ObjectPosInfo(const CGObjectInstance * obj):
+	pos(obj->pos),id(obj->ID), subId(obj->subID), owner(obj->tempOwner)
+{
+
+}

+ 32 - 0
lib/spells/ViewSpellInt.h

@@ -0,0 +1,32 @@
+/*
+ * ViewSpellInt.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 "../int3.h"
+ #include "../GameConstants.h"
+
+ class CGObjectInstance;
+
+ struct DLL_LINKAGE ObjectPosInfo
+ {
+ 	int3 pos;
+ 	Obj id;
+ 	si32 subId;
+ 	PlayerColor owner;
+ 	ObjectPosInfo();
+ 	ObjectPosInfo(const CGObjectInstance * obj);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & pos & id & subId & owner;
+	}
+ };
+

+ 29 - 219
server/CGameHandler.cpp

@@ -9,7 +9,7 @@
 #include "../lib/CArtHandler.h"
 #include "../lib/CBuildingHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CCreatureHandler.h"
@@ -67,8 +67,11 @@ public:
 	void sendAndApply(CPackForClient * info) const override;	
 	CRandomGenerator & getRandomGenerator() const override;
 	void complain(const std::string & problem) const override;
+	const CMap * getMap() const override;
+	const CGameInfoCallback * getCb() const override;
+	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const override;	
 private:
-	CGameHandler * gh;	
+	mutable CGameHandler * gh;	
 };
 
 CondSh<bool> battleMadeAction;
@@ -4970,222 +4973,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
 {
 	const CSpell *s = spellID.toSpell();
-	int cost = h->getSpellCost(s);
-	int schoolLevel = h->getSpellSchoolLevel(s);
-
-	if(!h->canCastThisSpell(s))
-		COMPLAIN_RET("Hero cannot cast this spell!");
-	if(h->mana < cost)
-		COMPLAIN_RET("Hero doesn't have enough spell points to cast this spell!");
-	if(s->combatSpell)
-		COMPLAIN_RET("This function can be used only for adventure map spells!");
-
-	AdvmapSpellCast asc;
-	asc.caster = h;
-	asc.spellID = spellID;
-	sendAndApply(&asc);
-
-	switch(spellID)
-	{
-	case SpellID::SUMMON_BOAT:
-		{
-			//check if spell works at all
-			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-
-			//try to find unoccupied boat to summon
-			const CGBoat *nearest = nullptr;
-			double dist = 0;
-			int3 summonPos = h->bestLocation();
-			if(summonPos.x < 0)
-				COMPLAIN_RET("There is no water tile available!");
-
-			for(const CGObjectInstance *obj : gs->map->objects)
-			{
-				if(obj && obj->ID == Obj::BOAT)
-				{
-					const CGBoat *b = static_cast<const CGBoat*>(obj);
-					if(b->hero) continue; //we're looking for unoccupied boat
-
-					double nDist = distance(b->pos, h->getPosition());
-					if(!nearest || nDist < dist) //it's first boat or closer than previous
-					{
-						nearest = b;
-						dist = nDist;
-					}
-				}
-			}
-
-			if(nearest) //we found boat to summon
-			{
-				ChangeObjPos cop;
-				cop.objid = nearest->id;
-				cop.nPos = summonPos + int3(1,0,0);;
-				cop.flags = 1;
-				sendAndApply(&cop);
-			}
-			else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
-				sendAndApply(&iw);
-			}
-			else //create boat
-			{
-				NewObject no;
-				no.ID = Obj::BOAT;
-				no.subID = h->getBoatType();
-				no.pos = summonPos + int3(1,0,0);;
-				sendAndApply(&no);
-			}
-			break;
-		}
-
-	case SpellID::SCUTTLE_BOAT:
-		{
-			//check if spell works at all
-			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-			if(!gs->map->isInTheMap(pos))
-				COMPLAIN_RET("Invalid dst tile for scuttle!");
-
-			//TODO: test range, visibility
-			const TerrainTile *t = &gs->map->getTile(pos);
-			if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
-				COMPLAIN_RET("There is no boat to scuttle!");
-
-			RemoveObject ro;
-			ro.id = t->visitableObjects.back()->id;
-			sendAndApply(&ro);
-			break;
-		}
-	case SpellID::DIMENSION_DOOR:
-		{
-			const TerrainTile *dest = getTile(pos);
-			const TerrainTile *curr = getTile(h->getSightCenter());
-
-			if(!dest)
-				COMPLAIN_RET("Destination tile doesn't exist!");
-			if(!h->movement)
-				COMPLAIN_RET("Hero needs movement points to cast Dimension Door!");
-			if(h->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= s->getPower(schoolLevel)) //limit casts per turn
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, SpellID::DIMENSION_DOOR);
-			sendAndApply(&gb);
-
-			if(!dest->isClear(curr)) //wrong dest tile
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
-				sendAndApply(&iw);
-				break;
-			}
-
-			if (moveHero(h->id, pos + h->getVisitableOffset(), true))
-			{
-				SetMovePoints smp;
-				smp.hid = h->id;
-				smp.val = std::max<ui32>(0, h->movement - 300);
-				sendAndApply(&smp);
-			}
-		}
-		break;
-	case SpellID::FLY:
-		{
-			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::FLYING_MOVEMENT, Bonus::SPELL_EFFECT, 0, SpellID::FLY, subtype);
-			sendAndApply(&gb);
-		}
-		break;
-	case SpellID::WATER_WALK:
-		{
-			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, SpellID::WATER_WALK, subtype);
-			sendAndApply(&gb);
-		}
-		break;
-
-	case SpellID::TOWN_PORTAL:
-		{
-			if (!gs->map->isInTheMap(pos))
-				COMPLAIN_RET("Destination tile not present!")
-			TerrainTile tile = gs->map->getTile(pos);
-			if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN )
-				COMPLAIN_RET("Town not found for Town Portal!");
-
-			CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
-			if (town->tempOwner != h->tempOwner)
-				COMPLAIN_RET("Can't teleport to another player!");
-			if (town->visitingHero)
-				COMPLAIN_RET("Can't teleport to occupied town!");
-
-			if (h->getSpellSchoolLevel(s) < 2)
-			{
-				si32 dist = town->pos.dist2dSQ(h->pos);
-				ObjectInstanceID nearest = town->id; //nearest town's ID
-				for(const CGTownInstance * currTown : gs->getPlayer(h->tempOwner)->towns)
-				{
-					si32 currDist = currTown->pos.dist2dSQ(h->pos);
-					if (currDist < dist)
-					{
-						nearest = currTown->id;
-						dist = currDist;
-					}
-				}
-				if (town->id != nearest)
-					COMPLAIN_RET("This hero can only teleport to nearest town!")
-			}
-			moveHero(h->id, town->visitablePos() + h->getVisitableOffset() ,1);
-		}
-		break;
-
-	case SpellID::VISIONS:
-	case SpellID::VIEW_EARTH:
-	case SpellID::DISGUISE:
-	case SpellID::VIEW_AIR:
-	default:
-		COMPLAIN_RET("This spell is not implemented yet!");
-	}
-
-	SetMana sm;
-	sm.hid = h->id;
-	sm.absolute = false;
-	sm.val = -cost;
-	sendAndApply(&sm);
-
-	return true;
+	
+	AdventureSpellCastParameters p;
+	p.caster = h;
+	p.pos = pos;
+	
+	return s->adventureCast(spellEnv, p);
 }
 
 void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
@@ -6010,7 +5803,7 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 	winnerHero = loserHero = nullptr;
 }
 
-
+///ServerSpellCastEnvironment
 ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
 {
 	
@@ -6030,3 +5823,20 @@ void ServerSpellCastEnvironment::complain(const std::string& problem) const
 {
 	gh->complain(problem);
 }
+
+const CGameInfoCallback * ServerSpellCastEnvironment::getCb() const
+{
+	return gh;
+}
+
+
+const CMap * ServerSpellCastEnvironment::getMap() const
+{
+	return gh->gameState()->map;
+}
+
+bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker) const
+{
+	return gh->moveHero(hid, dst, teleporting, asker);
+}
+

+ 1 - 1
server/CVCMIServer.cpp

@@ -12,7 +12,7 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CBuildingHandler.h"
-#include "../lib/CSpellHandler.h"
+#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CCreatureHandler.h"
 #include "zlib.h"
 #include "CVCMIServer.h"