Sfoglia il codice sorgente

Merge pull request #313 from vcmi/spellCastQuery

Spell cast query
ArseniyShestakov 8 anni fa
parent
commit
b670bcb46f

+ 5 - 0
AI/EmptyAI/CEmptyAI.cpp

@@ -38,3 +38,8 @@ void CEmptyAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance
 {
 	cb->selectionMade(0, queryID);
 }
+
+void CEmptyAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects)
+{
+	cb->selectionMade(0, askID);
+}

+ 1 - 0
AI/EmptyAI/CEmptyAI.h

@@ -17,6 +17,7 @@ public:
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
 	void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
+	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 };
 
 #define NAME "EmptyAI 0.1"

+ 8 - 0
AI/VCAI/VCAI.cpp

@@ -701,6 +701,14 @@ void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *do
 	});
 }
 
+void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects)
+{
+	status.addQuery(askID, "Map object select query");
+	requestActionASAP([=]{ answerQuery(askID, 0); });
+
+	//TODO: Town portal destination selection goes here
+}
+
 void VCAI::saveGame(BinarySerializer & h, const int version)
 {
 	LOG_TRACE_PARAMS(logAi, "version '%i'", version);

+ 1 - 0
AI/VCAI/VCAI.h

@@ -194,6 +194,7 @@ public:
 	virtual void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
 	virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
+	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 	virtual void saveGame(BinarySerializer & h, const int version) override; //saving
 	virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading
 	virtual void finish() override;

+ 9 - 2
CCallback.cpp

@@ -45,15 +45,22 @@ bool CCallback::moveHero(const CGHeroInstance *h, int3 dst, bool transit)
 }
 
 int CCallback::selectionMade(int selection, QueryID queryID)
+{
+	JsonNode reply(JsonNode::DATA_INTEGER);
+	reply.Integer() = selection;
+	return sendQueryReply(reply, queryID);
+}
+
+int CCallback::sendQueryReply(const JsonNode & reply, QueryID queryID)
 {
 	ASSERT_IF_CALLED_WITH_PLAYER
 	if(queryID == QueryID(-1))
 	{
 		logGlobal->errorStream() << "Cannot answer the query -1!";
-		return false;
+		return -1;
 	}
 
-	QueryReply pack(queryID,selection);
+	QueryReply pack(queryID, reply);
 	pack.player = *player;
 	return sendRequest(&pack);
 }

+ 2 - 0
CCallback.h

@@ -61,6 +61,7 @@ public:
 	virtual void trade(const CGObjectInstance *market, EMarketMode::EMarketMode mode, int id1, int id2, int val1, const CGHeroInstance *hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
 
 	virtual int selectionMade(int selection, QueryID queryID) =0;
+	virtual int sendQueryReply(const JsonNode & reply, QueryID queryID) =0;
 	virtual int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//swaps creatures between two possibly different garrisons // TODO: AI-unsafe code - fix it!
 	virtual int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)=0;//joins first stack to the second (creatures must be same type)
 	virtual int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) =0; //first goes to the second
@@ -121,6 +122,7 @@ public:
 	bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile)
 	bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);
 	int selectionMade(int selection, QueryID queryID) override;
+	int sendQueryReply(const JsonNode & reply, QueryID queryID) override;
 	int swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override;
 	int mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second
 	int mergeStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2) override; //first goes to the second

+ 47 - 6
client/CPlayerInterface.cpp

@@ -15,6 +15,7 @@
 #include "gui/SDL_Extensions.h"
 #include "widgets/CComponent.h"
 #include "windows/CTradeWindow.h"
+#include "windows/CSpellWindow.h"
 #include "../lib/CConfigHandler.h"
 #include "battle/CCreatureAnimation.h"
 #include "Graphics.h"
@@ -1191,6 +1192,43 @@ void CPlayerInterface::showTeleportDialog(TeleportChannelID channel, TTeleportEx
 	cb->selectionMade(choosenExit, askID);
 }
 
+void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+
+	auto selectCallback = [=](int selection)
+	{
+		JsonNode reply(JsonNode::DATA_INTEGER);
+		reply.Integer() = selection;
+		cb->sendQueryReply(reply, askID);
+	};
+
+	auto cancelCallback = [=]()
+	{
+		JsonNode reply(JsonNode::DATA_NULL);
+		cb->sendQueryReply(reply, askID);
+	};
+
+	const std::string localTitle = title.toString();
+	const std::string localDescription = description.toString();
+
+	std::vector<int> tempList;
+	tempList.reserve(objects.size());
+
+	for(auto item : objects)
+		tempList.push_back(item.getNum());
+
+	CComponent * localIconC = new CComponent(icon);
+
+	CIntObject * localIcon = localIconC->image;
+	localIconC->removeChild(localIcon, false);
+	delete localIconC;
+
+	CObjectListWindow * wnd = new CObjectListWindow(tempList, localIcon, localTitle, localDescription, selectCallback);
+	wnd->onExit = cancelCallback;
+	GH.pushInt(wnd);
+}
+
 void CPlayerInterface::tileRevealed(const std::unordered_set<int3, ShashInt3> &pos)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -2213,13 +2251,16 @@ void CPlayerInterface::viewWorldMap()
 void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if (spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
-	{
+
+	if(dynamic_cast<CSpellWindow *>(GH.topInt()))
+		GH.popIntTotally(GH.topInt());
+
+	if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
 		eraseCurrentPathOf(caster, false);
-	}
-	const CSpell * spell = CGI->spellh->objects[spellID];
 
-	if (spellID == SpellID::VIEW_EARTH)
+	const CSpell * spell = CGI->spellh->objects.at(spellID);
+
+	if(spellID == SpellID::VIEW_EARTH)
 	{
 		//TODO: implement on server side
 		int level = caster->getSpellSchoolLevel(spell);
@@ -2227,7 +2268,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 	}
 
 	auto castSoundPath = spell->getCastSound();
-	if (!castSoundPath.empty())
+	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 }
 

+ 1 - 0
client/CPlayerInterface.h

@@ -166,6 +166,7 @@ public:
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
 	void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
+	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
 	void showPuzzleMap() override;
 	void viewWorldMap() override;
 	void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;

+ 5 - 0
client/NetPacksClient.cpp

@@ -592,6 +592,11 @@ void TeleportDialog::applyCl(CClient *cl)
 	CALL_ONLY_THAT_INTERFACE(hero->tempOwner,showTeleportDialog,channel,exits,impassable,queryID);
 }
 
+void MapObjectSelectDialog::applyCl(CClient * cl)
+{
+	CALL_ONLY_THAT_INTERFACE(player, showMapObjectSelectDialog, queryID, icon, title, description, objects);
+}
+
 void BattleStart::applyFirstCl(CClient *cl)
 {
 	//Cannot use the usual macro because curB is not set yet

+ 2 - 102
client/windows/CSpellWindow.cpp

@@ -636,116 +636,16 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 
 			auto guard = vstd::makeScopeGuard([this]
 			{
-				(owner->myInt->battleInt ? owner->myInt->spellbookSettings.spellbookLastTabBattle : owner->myInt->spellbookSettings.spellbookLastTabAdvmap) = owner->selectedTab;
-				(owner->myInt->battleInt ? owner->myInt->spellbookSettings.spellbookLastPageBattle : owner->myInt->spellbookSettings.spellbokLastPageAdvmap) = owner->currentPage;
-				delete owner;
+				owner->myInt->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab;
+				owner->myInt->spellbookSettings.spellbokLastPageAdvmap = owner->currentPage;
 			});
 
-			if(mySpell->id == SpellID::TOWN_PORTAL)
-			{
-				//special case
-				//todo: move to mechanics
-
-				std::vector <int> availableTowns;
-				std::vector <const CGTownInstance*> Towns = owner->myInt->cb->getTownsInfo(false);
-
-				vstd::erase_if(Towns, [this](const CGTownInstance * t)
-				{
-					const auto relations = owner->myInt->cb->getPlayerRelations(t->tempOwner, owner->myInt->playerID);
-					return relations == PlayerRelations::ENEMIES;
-				});
-
-				if (Towns.empty())
-				{
-					owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[124]);
-					return;
-				}
-
-				const int movementCost = (h->getSpellSchoolLevel(mySpell) >= 3) ? 200 : 300;
-
-				if(h->movement < movementCost)
-				{
-					owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[125]);
-					return;
-				}
-
-				if (h->getSpellSchoolLevel(mySpell) < 2) //not advanced or expert - teleport to nearest available city
-				{
-					auto nearest = Towns.cbegin(); //nearest town's iterator
-					si32 dist = owner->myInt->cb->getTown((*nearest)->id)->pos.dist2dSQ(h->pos);
-
-					for (auto i = nearest + 1; i != Towns.cend(); ++i)
-					{
-						const CGTownInstance * dest = owner->myInt->cb->getTown((*i)->id);
-						si32 curDist = dest->pos.dist2dSQ(h->pos);
-
-						if (curDist < dist)
-						{
-							nearest = i;
-							dist = curDist;
-						}
-					}
-
-					if ((*nearest)->visitingHero)
-						owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[123]);
-					else
-					{
-						const CGTownInstance * town = owner->myInt->cb->getTown((*nearest)->id);
-						owner->myInt->cb->castSpell(h, mySpell->id, 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
-						{
-							availableTowns.push_back(t->id.getNum());//add to the list
-						}
-					}
-
-					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())
-						owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[124]);
-					else
-						GH.pushInt (new CObjectListWindow(availableTowns,
-							new CAnimImage("SPELLSCR",mySpell->id),
-							CGI->generaltexth->jktexts[40], CGI->generaltexth->jktexts[41],
-							castTownPortal));
-				}
-				return;
-			}
-
-			if(mySpell->id == SpellID::SUMMON_BOAT)
-			{
-				//special case
-				//todo: move to mechanics
-				int3 pos = h->bestLocation();
-				if(pos.x < 0)
-				{
-					owner->myInt->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
-					return;
-				}
-			}
-
 			if(mySpell->getTargetType() == CSpell::LOCATION)
-			{
 				adventureInt->enterCastingMode(mySpell);
-			}
 			else if(mySpell->getTargetType() == CSpell::NO_TARGET)
-			{
 				owner->myInt->cb->castSpell(h, mySpell->id);
-			}
 			else
-			{
 				logGlobal->error("Invalid spell target type");
-			}
 		}
 	}
 }

+ 9 - 1
client/windows/GUIClasses.cpp

@@ -1767,7 +1767,7 @@ void CObjectListWindow::init(CIntObject * titlePic, std::string _title, std::str
 
 	ok = new CButton(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN);
 	ok->block(true);
-	exit = new CButton( Point(228, 402), "ICANCEL.DEF", CButton::tooltip(), std::bind(&CGuiHandler::popIntTotally,&GH, this), SDLK_ESCAPE);
+	exit = new CButton( Point(228, 402), "ICANCEL.DEF", CButton::tooltip(), std::bind(&CObjectListWindow::exitPressed, this), SDLK_ESCAPE);
 
 	if (titlePic)
 	{
@@ -1796,6 +1796,14 @@ void CObjectListWindow::elementSelected()
 	toCall(where);//and send selected object
 }
 
+void CObjectListWindow::exitPressed()
+{
+	std::function<void()> toCall = onExit;//save
+	GH.popIntTotally(this);//then destroy window
+	if(toCall)
+		toCall();
+}
+
 void CObjectListWindow::changeSelection(size_t which)
 {
 	ok->block(false);

+ 5 - 0
client/windows/GUIClasses.h

@@ -166,13 +166,18 @@ class CObjectListWindow : public CWindowObject
 	std::vector< std::pair<int, std::string> > items;//all items present in list
 
 	void init(CIntObject * titlePic, std::string _title, std::string _descr);
+	void exitPressed();
 public:
 	size_t selected;//index of currently selected item
+
+	std::function<void()> onExit;//optional exit callback
+
 	/// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item
 	/// Image can be nullptr
 	///item names will be taken from map objects
 	CObjectListWindow(const std::vector<int> &_items, CIntObject * titlePic, std::string _title, std::string _descr,
                       std::function<void(int)> Callback);
+
 	CObjectListWindow(const std::vector<std::string> &_items, CIntObject * titlePic, std::string _title, std::string _descr,
                       std::function<void(int)> Callback);
 

+ 1 - 0
lib/CGameInterface.h

@@ -98,6 +98,7 @@ 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 showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) = 0;
+	virtual void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) = 0;
 	virtual void finish(){}; //if for some reason we want to end
 
 	virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions){};

+ 3 - 0
lib/CGameState.cpp

@@ -146,6 +146,9 @@ void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst
 		case COLOR:
 			vec = &VLC->generaltexth->capColors;
 			break;
+		case JK_TXT:
+			vec = &VLC->generaltexth->jktexts;
+			break;
 		default:
 			logGlobal->errorStream() << "Failed string substitution because type is " << type;
 			dst = "#@#";

+ 23 - 10
lib/NetPacks.h

@@ -3,6 +3,7 @@
 #include "NetPacksBase.h"
 
 #include "battle/BattleAction.h"
+#include "JsonNode.h"
 #include "mapObjects/CGHeroInstance.h"
 #include "ConstTransitivePtr.h"
 #include "int3.h"
@@ -1141,12 +1142,6 @@ struct BlockingDialog : public Query
 		soundID = 0;
 	};
 
-	void addResourceComponents(TResources resources)
-	{
-		for(TResources::nziterator i(resources); i.valid(); i++)
-			components.push_back(Component(Component::RESOURCE, i->resType, i->resVal, 0));
-	}
-
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & queryID & text & components & player & flags & soundID;
@@ -1205,6 +1200,24 @@ struct TeleportDialog : public Query
 	}
 };
 
+struct MapObjectSelectDialog : public Query
+{
+	PlayerColor player;
+	Component icon;
+	MetaString title;
+	MetaString description;
+	std::vector<ObjectInstanceID> objects;
+
+	MapObjectSelectDialog(){};
+
+	void applyCl(CClient * cl);
+
+	template <typename Handler> void serialize(Handler & h, const int version)
+	{
+		h & queryID & player & icon & title & description & objects;
+	}
+};
+
 struct BattleInfo;
 struct BattleStart : public CPackForClient
 {
@@ -2058,16 +2071,16 @@ struct BuildBoat : public CPackForServer
 
 struct QueryReply : public CPackForServer
 {
-	QueryReply():answer(0){};
-	QueryReply(QueryID QID, ui32 Answer):qid(QID),answer(Answer){};
+	QueryReply(){};
+	QueryReply(QueryID QID, const JsonNode & Reply):qid(QID), reply(Reply){};
 	QueryID qid;
-	ui32 answer; //hero and artifact id
 	PlayerColor player;
+	JsonNode reply;
 
 	bool applyGh(CGameHandler *gh);
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & qid & answer & player;
+		h & qid & player & reply;
 	}
 };
 

+ 2 - 2
lib/NetPacksBase.h

@@ -44,11 +44,11 @@ private:
 	enum EMessage {TEXACT_STRING, TLOCAL_STRING, TNUMBER, TREPLACE_ESTRING, TREPLACE_LSTRING, TREPLACE_NUMBER, TREPLACE_PLUSNUMBER};
 public:
 	enum {GENERAL_TXT=1, XTRAINFO_TXT, OBJ_NAMES, RES_NAMES, ART_NAMES, ARRAY_TXT, CRE_PL_NAMES, CREGENS, MINE_NAMES,
-		MINE_EVNTS, ADVOB_TXT, ART_EVNTS, SPELL_NAME, SEC_SKILL_NAME, CRE_SING_NAMES, CREGENS4, COLOR, ART_DESCR};
+		MINE_EVNTS, ADVOB_TXT, ART_EVNTS, SPELL_NAME, SEC_SKILL_NAME, CRE_SING_NAMES, CREGENS4, COLOR, ART_DESCR, JK_TXT};
 
 	std::vector<ui8> message; //vector of EMessage
 
-	std::vector<std::pair<ui8,ui32> > localStrings; //pairs<text handler type, text number>; types: 1 - generaltexthandler->all; 2 - objh->xtrainfo; 3 - objh->names; 4 - objh->restypes; 5 - arth->artifacts[id].name; 6 - generaltexth->arraytxt; 7 - creh->creatures[os->subID].namePl; 8 - objh->creGens; 9 - objh->mines[ID]->first; 10 - objh->mines[ID]->second; 11 - objh->advobtxt
+	std::vector<std::pair<ui8,ui32> > localStrings;
 	std::vector<std::string> exactStrings;
 	std::vector<si32> numbers;
 

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -290,6 +290,7 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<Query, GarrisonDialog>();
 	s.template registerType<Query, ExchangeDialog>();
 	s.template registerType<Query, TeleportDialog>();
+	s.template registerType<Query, MapObjectSelectDialog>();
 
 	s.template registerType<CPackForClient, CGarrisonOperationPack>();
 	s.template registerType<CGarrisonOperationPack, ChangeStackCount>();

+ 275 - 76
lib/spells/AdventureSpellMechanics.cpp

@@ -20,7 +20,12 @@
 #include "../CPlayerState.h"
 
 ///AdventureSpellMechanics
-bool AdventureSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+AdventureSpellMechanics::AdventureSpellMechanics(const CSpell * s):
+	IAdventureSpellMechanics(s)
+{
+}
+
+bool AdventureSpellMechanics::adventureCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	if(!owner->isAdventureSpell())
 	{
@@ -50,32 +55,15 @@ bool AdventureSpellMechanics::adventureCast(const SpellCastEnvironment * env, Ad
 		return false;
 	}
 
-	{
-		AdvmapSpellCast asc;
-		asc.caster = caster;
-		asc.spellID = owner->id;
-		env->sendAndApply(&asc);
-	}
+	ESpellCastResult result = beginCast(env, parameters);
 
-	switch(applyAdventureEffects(env, parameters))
-	{
-	case ESpellCastResult::OK:
-		{
-			SetMana sm;
-			sm.hid = caster->id;
-			sm.absolute = false;
-			sm.val = -cost;
-			env->sendAndApply(&sm);
-			return true;
-		}
-		break;
-	case ESpellCastResult::CANCEL:
-		return true;
-	}
-	return false;
+	if(result == ESpellCastResult::OK)
+		performCast(env, parameters);
+
+	return result != ESpellCastResult::ERROR;
 }
 
-ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	if(owner->hasEffects())
 	{
@@ -104,10 +92,62 @@ ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastE
 	}
 }
 
+ESpellCastResult AdventureSpellMechanics::beginCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+    return ESpellCastResult::OK;
+}
+
+void AdventureSpellMechanics::performCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	AdvmapSpellCast asc;
+	asc.caster = parameters.caster;
+	asc.spellID = owner->id;
+	env->sendAndApply(&asc);
+
+	ESpellCastResult result = applyAdventureEffects(env, parameters);
+	endCast(env, parameters, result);
+}
+
+void AdventureSpellMechanics::endCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const ESpellCastResult result) const
+{
+	const int cost = parameters.caster->getSpellCost(owner);
+
+	switch(result)
+	{
+	case ESpellCastResult::OK:
+		{
+			SetMana sm;
+			sm.hid = parameters.caster->id;
+			sm.absolute = false;
+			sm.val = -cost;
+			env->sendAndApply(&sm);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
 ///SummonBoatMechanics
-ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+SummonBoatMechanics::SummonBoatMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
+	int3 summonPos = parameters.caster->bestLocation();
+	if(summonPos.x < 0)
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 334);//There is no place to put the boat.
+		env->sendAndApply(&iw);
+		return ESpellCastResult::CANCEL;
+	}
+
 	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
 	{
@@ -122,13 +162,6 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvir
 	//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 ESpellCastResult::ERROR;
-	}
-
 	for(const CGObjectInstance * obj : env->getMap()->objects)
 	{
 		if(obj && obj->ID == Obj::BOAT)
@@ -150,7 +183,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvir
 	{
 		ChangeObjPos cop;
 		cop.objid = nearest->id;
-		cop.nPos = summonPos + int3(1,0,0);;
+		cop.nPos = summonPos + int3(1,0,0);
 		cop.flags = 1;
 		env->sendAndApply(&cop);
 	}
@@ -166,14 +199,19 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvir
 		NewObject no;
 		no.ID = Obj::BOAT;
 		no.subID = parameters.caster->getBoatType();
-		no.pos = summonPos + int3(1,0,0);;
+		no.pos = summonPos + int3(1,0,0);
 		env->sendAndApply(&no);
 	}
 	return ESpellCastResult::OK;
 }
 
 ///ScuttleBoatMechanics
-ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+ScuttleBoatMechanics::ScuttleBoatMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
 	//check if spell works at all
@@ -208,7 +246,12 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvi
 }
 
 ///DimensionDoorMechanics
-ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+DimensionDoorMechanics::DimensionDoorMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	if(!env->getMap()->isInTheMap(parameters.pos))
 	{
@@ -276,77 +319,221 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(const SpellCastEn
 }
 
 ///TownPortalMechanics
-ESpellCastResult TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
+TownPortalMechanics::TownPortalMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
-	if (!env->getMap()->isInTheMap(parameters.pos))
+	const CGTownInstance * destination = nullptr;
+	const int moveCost = movementCost(parameters);
+
+    if(parameters.caster->getSpellSchoolLevel(owner) < 2)
+    {
+		std::vector <const CGTownInstance*> pool = getPossibleTowns(env, parameters);
+		destination = findNearestTown(env, parameters, pool);
+
+		if(nullptr == destination)
+			return ESpellCastResult::ERROR;
+
+		if(parameters.caster->movement < moveCost)
+			return ESpellCastResult::ERROR;
+
+		if(destination->visitingHero)
+		{
+			InfoWindow iw;
+			iw.player = parameters.caster->tempOwner;
+			iw.text.addTxt(MetaString::GENERAL_TXT, 123);
+			env->sendAndApply(&iw);
+			return ESpellCastResult::CANCEL;
+		}
+    }
+    else if(env->getMap()->isInTheMap(parameters.pos))
 	{
-		env->complain("Destination tile not present!");
-		return ESpellCastResult::ERROR;
-	}
+		const TerrainTile & tile = env->getMap()->getTile(parameters.pos);
+		if(tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
+		{
+			env->complain("No town at destination tile");
+			return ESpellCastResult::ERROR;
+		}
+
+		destination = dynamic_cast<CGTownInstance*>(tile.visitableObjects.back());
+
+		if(nullptr == destination)
+		{
+			env->complain("[Internal error] invalid town object");
+			return ESpellCastResult::ERROR;
+		}
+
+		const auto relations = env->getCb()->getPlayerRelations(destination->tempOwner, parameters.caster->tempOwner);
+
+		if(relations == PlayerRelations::ENEMIES)
+		{
+			env->complain("Can't teleport to enemy!");
+			return ESpellCastResult::ERROR;
+		}
+
+		if(parameters.caster->movement < moveCost)
+		{
+			env->complain("This hero has not enough movement points!");
+			return ESpellCastResult::ERROR;
+		}
 
-	TerrainTile tile = env->getMap()->getTile(parameters.pos);
-	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
+		if(destination->visitingHero)
+		{
+			env->complain("Can't teleport to occupied town!");
+			return ESpellCastResult::ERROR;
+		}
+	}
+	else
 	{
-		env->complain("Town not found for Town Portal!");
+		env->complain("Invalid destination tile");
 		return ESpellCastResult::ERROR;
 	}
 
-	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
+	if(env->moveHero(parameters.caster->id, destination->visitablePos() + parameters.caster->getVisitableOffset(), true))
+	{
+		SetMovePoints smp;
+		smp.hid = parameters.caster->id;
+		smp.val = std::max<ui32>(0, parameters.caster->movement - moveCost);
+		env->sendAndApply(&smp);
+	}
+	return ESpellCastResult::OK;
+}
 
-	const auto relations = env->getCb()->getPlayerRelations(town->tempOwner, parameters.caster->tempOwner);
+ESpellCastResult TownPortalMechanics::beginCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	std::vector<const CGTownInstance *>	towns = getPossibleTowns(env, parameters);
 
-	if(relations == PlayerRelations::ENEMIES)
+	if(towns.empty())
 	{
-		env->complain("Can't teleport to enemy!");
-		return ESpellCastResult::ERROR;
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 124);
+		env->sendAndApply(&iw);
+		return ESpellCastResult::CANCEL;
 	}
 
-	if (town->visitingHero)
+	const int moveCost = movementCost(parameters);
+
+	if(parameters.caster->movement < moveCost)
 	{
-		env->complain("Can't teleport to occupied town!");
-		return ESpellCastResult::ERROR;
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 125);
+		env->sendAndApply(&iw);
+		return ESpellCastResult::CANCEL;
 	}
 
-	if (parameters.caster->getSpellSchoolLevel(owner) < 2)
+	if(!parameters.pos.valid() && 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)
+		auto queryCallback = [=](const JsonNode & reply) -> void
 		{
-			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
-			if (currDist < dist)
+			if(reply.getType() == JsonNode::DATA_INTEGER)
 			{
-				nearest = currTown->id;
-				dist = currDist;
+				ObjectInstanceID townId(reply.Integer());
+
+				const CGObjectInstance * o = env->getCb()->getObj(townId, true);
+				if(o == nullptr)
+				{
+					env->complain("Invalid object instance selected");
+					return;
+				}
+
+				if(!dynamic_cast<const CGTownInstance *>(o))
+				{
+					env->complain("Object instance is not town");
+					return;
+				}
+
+				AdventureSpellCastParameters p;
+				p.caster = parameters.caster;
+				p.pos = o->visitablePos();
+				performCast(env, p);
 			}
+		};
+
+		MapObjectSelectDialog request;
+
+		for(auto t : towns)
+		{
+			if(t->visitingHero == nullptr) //empty town
+				request.objects.push_back(t->id);
 		}
-		if (town->id != nearest)
+
+		if(request.objects.empty())
 		{
-			env->complain("This hero can only teleport to nearest town!");
-			return ESpellCastResult::ERROR;
+			InfoWindow iw;
+			iw.player = parameters.caster->tempOwner;
+			iw.text.addTxt(MetaString::GENERAL_TXT, 124);
+			env->sendAndApply(&iw);
+			return ESpellCastResult::CANCEL;
 		}
 
+		request.player = parameters.caster->getOwner();
+		request.title.addTxt(MetaString::JK_TXT, 40);
+		request.description.addTxt(MetaString::JK_TXT, 41);
+		request.icon.id = Component::SPELL;
+		request.icon.subtype = owner->id.toEnum();
+
+		env->genericQuery(&request, request.player, queryCallback);
+
+		return ESpellCastResult::PENDING;
 	}
 
-	const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
+	return ESpellCastResult::OK;
+}
+
+const CGTownInstance * TownPortalMechanics::findNearestTown(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance *> & pool) const
+{
+	if(pool.empty())
+		return nullptr;
+
+	auto nearest = pool.cbegin(); //nearest town's iterator
+	si32 dist = (*nearest)->pos.dist2dSQ(parameters.caster->pos);
 
-	if(parameters.caster->movement < movementCost)
+	for(auto i = nearest + 1; i != pool.cend(); ++i)
 	{
-		env->complain("This hero has not enough movement points!");
-		return ESpellCastResult::ERROR;
+		si32 curDist = (*i)->pos.dist2dSQ(parameters.caster->pos);
+
+		if(curDist < dist)
+		{
+			nearest = i;
+			dist = curDist;
+		}
 	}
+	return *nearest;
+}
+
+std::vector <const CGTownInstance*> TownPortalMechanics::getPossibleTowns(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
+{
+	std::vector <const CGTownInstance*> ret;
+
+	const TeamState * team = env->getCb()->getPlayerTeam(parameters.caster->getOwner());
 
-	if(env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1))
+	for(const auto & color : team->players)
 	{
-		SetMovePoints smp;
-		smp.hid = parameters.caster->id;
-		smp.val = std::max<ui32>(0, parameters.caster->movement - movementCost);
-		env->sendAndApply(&smp);
+		for(auto currTown : env->getCb()->getPlayer(color)->towns)
+		{
+			ret.push_back(currTown.get());
+		}
 	}
-	return ESpellCastResult::OK;
+	return ret;
+}
+
+int TownPortalMechanics::movementCost(const AdventureSpellCastParameters & parameters) const
+{
+	return GameConstants::BASE_MOVEMENT_COST * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
 }
 
-ESpellCastResult ViewMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+///ViewMechanics
+ViewMechanics::ViewMechanics(const CSpell * s):
+	AdventureSpellMechanics(s)
+{
+}
+
+ESpellCastResult ViewMechanics::applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	ShowWorldViewEx pack;
 
@@ -373,13 +560,25 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(const SpellCastEnvironment
 	return ESpellCastResult::OK;
 }
 
+///ViewAirMechanics
+ViewAirMechanics::ViewAirMechanics(const CSpell * s):
+	ViewMechanics(s)
+{
+}
+
 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);
+	return (obj->ID == Obj::ARTIFACT) || (spellLevel > 1 && obj->ID == Obj::HERO) || (spellLevel > 2 && obj->ID == Obj::TOWN);
+}
+
+///ViewEarthMechanics
+ViewEarthMechanics::ViewEarthMechanics(const CSpell * s):
+	ViewMechanics(s)
+{
 }
 
 bool ViewEarthMechanics::filterObject(const CGObjectInstance * obj, const int spellLevel) const
 {
-	return (obj->ID == Obj::RESOURCE) || (spellLevel>1 && obj->ID == Obj::MINE);
+	return (obj->ID == Obj::RESOURCE) || (spellLevel > 1 && obj->ID == Obj::MINE);
 }
 

+ 27 - 16
lib/spells/AdventureSpellMechanics.h

@@ -12,69 +12,80 @@
 
 #include "ISpellMechanics.h"
 
+class CGTownInstance;
+
 enum class ESpellCastResult
 {
 	OK,
 	CANCEL,//cast failed but it is not an error
+	PENDING,
 	ERROR//internal error occurred
 };
 
-class DLL_LINKAGE AdventureSpellMechanics: public IAdventureSpellMechanics
+class DLL_LINKAGE AdventureSpellMechanics : public IAdventureSpellMechanics
 {
 public:
-	AdventureSpellMechanics(CSpell * s): IAdventureSpellMechanics(s){};
+	AdventureSpellMechanics(const CSpell * s);
 
-	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
+	bool adventureCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override final;
 protected:
 	///actual adventure cast implementation
-	virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
+	virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	virtual ESpellCastResult beginCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	void performCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
+	void endCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const ESpellCastResult result) const;
 };
 
 class DLL_LINKAGE SummonBoatMechanics : public AdventureSpellMechanics
 {
 public:
-	SummonBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
+	SummonBoatMechanics(const CSpell * s);
 protected:
-	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 };
 
 class DLL_LINKAGE ScuttleBoatMechanics : public AdventureSpellMechanics
 {
 public:
-	ScuttleBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
+	ScuttleBoatMechanics(const CSpell * s);
 protected:
-	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 };
 
 class DLL_LINKAGE DimensionDoorMechanics : public AdventureSpellMechanics
 {
 public:
-	DimensionDoorMechanics(CSpell * s): AdventureSpellMechanics(s){};
+	DimensionDoorMechanics(const CSpell * s);
 protected:
-	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
 };
 
 class DLL_LINKAGE TownPortalMechanics : public AdventureSpellMechanics
 {
 public:
-	TownPortalMechanics(CSpell * s): AdventureSpellMechanics(s){};
+	TownPortalMechanics(const CSpell * s);
 protected:
-	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult beginCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const override;
+private:
+	const CGTownInstance * findNearestTown(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters, const std::vector <const CGTownInstance*> & pool) const;
+	int movementCost(const AdventureSpellCastParameters & parameters) const;
+	std::vector <const CGTownInstance*> getPossibleTowns(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 };
 
 class DLL_LINKAGE ViewMechanics : public AdventureSpellMechanics
 {
 public:
-	ViewMechanics(CSpell * s): AdventureSpellMechanics(s){};
+	ViewMechanics(const CSpell * s);
 protected:
-	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
+	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, const 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){};
+	ViewAirMechanics(const CSpell * s);
 protected:
 	bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
 };
@@ -82,7 +93,7 @@ protected:
 class DLL_LINKAGE ViewEarthMechanics : public ViewMechanics
 {
 public:
-	ViewEarthMechanics(CSpell * s): ViewMechanics(s){};
+	ViewEarthMechanics(const CSpell * s);
 protected:
 	bool filterObject(const CGObjectInstance * obj, const int spellLevel) const override;
 };

+ 105 - 1
lib/spells/BattleSpellMechanics.cpp

@@ -18,6 +18,11 @@
 #include "../mapObjects/CGTownInstance.h"
 
 ///HealingSpellMechanics
+HealingSpellMechanics::HealingSpellMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	EHealLevel healLevel = getHealLevel(parameters.effectLevel);
@@ -48,6 +53,11 @@ int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, co
 }
 
 ///AntimagicMechanics
+AntimagicMechanics::AntimagicMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);
@@ -74,6 +84,11 @@ void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast
 }
 
 ///ChainLightningMechanics
+ChainLightningMechanics::ChainLightningMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 std::vector<const CStack *> ChainLightningMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
 	std::vector<const CStack *> res;
@@ -111,6 +126,11 @@ std::vector<const CStack *> ChainLightningMechanics::calculateAffectedStacks(con
 }
 
 ///CloneMechanics
+CloneMechanics::CloneMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	const CStack * clonedStack = nullptr;
@@ -180,10 +200,14 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpel
 }
 
 ///CureMechanics
+CureMechanics::CureMechanics(const CSpell * s):
+	HealingSpellMechanics(s)
+{
+}
+
 void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);
-
 	doDispell(battle, packet, dispellSelector);
 }
 
@@ -212,6 +236,11 @@ ESpellCastProblem::ESpellCastProblem CureMechanics::isImmuneByStack(const ISpell
 }
 
 ///DispellMechanics
+DispellMechanics::DispellMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);
@@ -261,6 +290,11 @@ void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, cons
 }
 
 ///EarthquakeMechanics
+EarthquakeMechanics::EarthquakeMechanics(const CSpell * s):
+	SpecialSpellMechanics(s)
+{
+}
+
 void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	if(nullptr == parameters.cb->battleGetDefendedTown())
@@ -391,6 +425,11 @@ bool EarthquakeMechanics::requiresCreatureTarget() const
 }
 
 ///HypnotizeMechanics
+HypnotizeMechanics::HypnotizeMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	//todo: maybe do not resist on passive cast
@@ -407,6 +446,11 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const I
 }
 
 ///ObstacleMechanics
+ObstacleMechanics::ObstacleMechanics(const CSpell * s):
+	SpecialSpellMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem ObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
 	const auto side = cb->playerToSide(ctx.caster->getOwner());
@@ -481,6 +525,11 @@ void ObstacleMechanics::placeObstacle(const SpellCastEnvironment * env, const Ba
 }
 
 ///PatchObstacleMechanics
+PatchObstacleMechanics::PatchObstacleMechanics(const CSpell * s):
+	ObstacleMechanics(s)
+{
+}
+
 void PatchObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	std::vector<BattleHex> availableTiles;
@@ -500,6 +549,11 @@ void PatchObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env
 }
 
 ///LandMineMechanics
+LandMineMechanics::LandMineMechanics(const CSpell * s):
+	PatchObstacleMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem LandMineMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
 	//LandMine are useless if enemy has native stack and can see mines, check for LandMine damage immunity is done in general way by CSpell
@@ -528,6 +582,11 @@ void LandMineMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
 }
 
 ///QuicksandMechanics
+QuicksandMechanics::QuicksandMechanics(const CSpell * s):
+	PatchObstacleMechanics(s)
+{
+}
+
 bool QuicksandMechanics::requiresCreatureTarget() const
 {
 	return false;
@@ -541,6 +600,11 @@ void QuicksandMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
 }
 
 ///WallMechanics
+WallMechanics::WallMechanics(const CSpell * s):
+	ObstacleMechanics(s)
+{
+}
+
 std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
 {
 	std::vector<BattleHex> ret;
@@ -578,6 +642,11 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
 }
 
 ///FireWallMechanics
+FireWallMechanics::FireWallMechanics(const CSpell * s):
+	WallMechanics(s)
+{
+}
+
 bool FireWallMechanics::requiresCreatureTarget() const
 {
 	return true;
@@ -606,6 +675,11 @@ void FireWallMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
 }
 
 ///ForceFieldMechanics
+ForceFieldMechanics::ForceFieldMechanics(const CSpell * s):
+	WallMechanics(s)
+{
+}
+
 bool ForceFieldMechanics::requiresCreatureTarget() const
 {
 	return false;
@@ -631,6 +705,11 @@ void ForceFieldMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
 }
 
 ///RemoveObstacleMechanics
+RemoveObstacleMechanics::RemoveObstacleMechanics(const CSpell * s):
+	SpecialSpellMechanics(s)
+{
+}
+
 void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	auto obstacleToRemove = parameters.cb->battleGetAllObstaclesOnPos(parameters.getFirstDestinationHex(), false);
@@ -714,6 +793,11 @@ bool RemoveObstacleMechanics::requiresCreatureTarget() const
 }
 
 ///RisingSpellMechanics
+RisingSpellMechanics::RisingSpellMechanics(const CSpell * s):
+	HealingSpellMechanics(s)
+{
+}
+
 HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
 {
 	//this may be even distinct class
@@ -724,6 +808,11 @@ HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectL
 }
 
 ///SacrificeMechanics
+SacrificeMechanics::SacrificeMechanics(const CSpell * s):
+	RisingSpellMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
 	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
@@ -809,6 +898,11 @@ bool SacrificeMechanics::requiresCreatureTarget() const
 }
 
 ///SpecialRisingSpellMechanics
+SpecialRisingSpellMechanics::SpecialRisingSpellMechanics(const CSpell * s):
+	RisingSpellMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
 	//find alive possible target
@@ -870,6 +964,11 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 }
 
 ///SummonMechanics
+SummonMechanics::SummonMechanics(const CSpell * s, CreatureID cre):
+	SpecialSpellMechanics(s), creatureToSummon(cre)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
 	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
@@ -920,6 +1019,11 @@ bool SummonMechanics::requiresCreatureTarget() const
 }
 
 ///TeleportMechanics
+TeleportMechanics::TeleportMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	if(parameters.destinations.size() == 2)

+ 21 - 29
lib/spells/BattleSpellMechanics.h

@@ -25,7 +25,7 @@ public:
 		TRUE_RESURRECT
 	};
 
-	HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	HealingSpellMechanics(const CSpell * s);
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 	virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
@@ -35,15 +35,14 @@ protected:
 class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
 {
 public:
-	AntimagicMechanics(CSpell * s): DefaultSpellMechanics(s){};
-
+	AntimagicMechanics(const CSpell * s);
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 };
 
 class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
 {
 public:
-	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ChainLightningMechanics(const CSpell * s);
 protected:
 	std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 };
@@ -51,7 +50,7 @@ protected:
 class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
 {
 public:
-	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	CloneMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
@@ -60,11 +59,9 @@ protected:
 class DLL_LINKAGE CureMechanics : public HealingSpellMechanics
 {
 public:
-	CureMechanics(CSpell * s): HealingSpellMechanics(s){};
-
+	CureMechanics(const CSpell * s);
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
-
 	EHealLevel getHealLevel(int effectLevel) const override final;
 private:
     static bool dispellSelector(const Bonus * b);
@@ -73,9 +70,8 @@ private:
 class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
 {
 public:
-	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	DispellMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
-
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
@@ -84,7 +80,7 @@ protected:
 class DLL_LINKAGE EarthquakeMechanics : public SpecialSpellMechanics
 {
 public:
-	EarthquakeMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	EarthquakeMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 	bool requiresCreatureTarget() const	override;
 protected:
@@ -94,14 +90,14 @@ protected:
 class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
 {
 public:
-	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	HypnotizeMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
 
 class DLL_LINKAGE ObstacleMechanics : public SpecialSpellMechanics
 {
 public:
-	ObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	ObstacleMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 protected:
 	static bool isHexAviable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
@@ -112,7 +108,7 @@ protected:
 class PatchObstacleMechanics : public ObstacleMechanics
 {
 public:
-	PatchObstacleMechanics(CSpell * s): ObstacleMechanics(s){};
+	PatchObstacleMechanics(const CSpell * s);
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
@@ -120,7 +116,7 @@ protected:
 class DLL_LINKAGE LandMineMechanics : public PatchObstacleMechanics
 {
 public:
-	LandMineMechanics(CSpell * s): PatchObstacleMechanics(s){};
+	LandMineMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 	bool requiresCreatureTarget() const	override;
 protected:
@@ -130,7 +126,7 @@ protected:
 class DLL_LINKAGE QuicksandMechanics : public PatchObstacleMechanics
 {
 public:
-	QuicksandMechanics(CSpell * s): PatchObstacleMechanics(s){};
+	QuicksandMechanics(const CSpell * s);
 	bool requiresCreatureTarget() const	override;
 protected:
 	void setupObstacle(SpellCreatedObstacle * obstacle) const override;
@@ -139,14 +135,14 @@ protected:
 class DLL_LINKAGE WallMechanics : public ObstacleMechanics
 {
 public:
-	WallMechanics(CSpell * s): ObstacleMechanics(s){};
+	WallMechanics(const CSpell * s);
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
 };
 
 class DLL_LINKAGE FireWallMechanics : public WallMechanics
 {
 public:
-	FireWallMechanics(CSpell * s): WallMechanics(s){};
+	FireWallMechanics(const CSpell * s);
 	bool requiresCreatureTarget() const	override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
@@ -156,7 +152,7 @@ protected:
 class DLL_LINKAGE ForceFieldMechanics : public WallMechanics
 {
 public:
-	ForceFieldMechanics(CSpell * s): WallMechanics(s){};
+	ForceFieldMechanics(const CSpell * s);
 	bool requiresCreatureTarget() const	override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
@@ -166,7 +162,7 @@ protected:
 class DLL_LINKAGE RemoveObstacleMechanics : public SpecialSpellMechanics
 {
 public:
-	RemoveObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	RemoveObstacleMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 	bool requiresCreatureTarget() const	override;
@@ -180,16 +176,14 @@ private:
 class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
 {
 public:
-	RisingSpellMechanics(CSpell * s): HealingSpellMechanics(s){};
-
+	RisingSpellMechanics(const CSpell * s);
 	EHealLevel getHealLevel(int effectLevel) const override;
 };
 
 class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
 {
 public:
-	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
-
+	SacrificeMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 	bool requiresCreatureTarget() const	override;
 protected:
@@ -201,7 +195,7 @@ protected:
 class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
 {
 public:
-	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
+	SpecialRisingSpellMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
@@ -209,8 +203,7 @@ public:
 class DLL_LINKAGE SummonMechanics : public SpecialSpellMechanics
 {
 public:
-	SummonMechanics(CSpell * s, CreatureID cre): SpecialSpellMechanics(s), creatureToSummon(cre){};
-
+	SummonMechanics(const CSpell * s, CreatureID cre);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 	bool requiresCreatureTarget() const	override;
 protected:
@@ -222,8 +215,7 @@ private:
 class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
 {
 public:
-	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
-
+	TeleportMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;

+ 11 - 0
lib/spells/CDefaultSpellMechanics.cpp

@@ -246,6 +246,11 @@ void SpellCastContext::afterCast()
 }
 
 ///DefaultSpellMechanics
+DefaultSpellMechanics::DefaultSpellMechanics(const CSpell * s):
+	ISpellMechanics(s)
+{
+};
+
 void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	if (packet->castByHero)
@@ -884,6 +889,12 @@ bool DefaultSpellMechanics::requiresCreatureTarget() const
 	return true;
 }
 
+///SpecialSpellMechanics
+SpecialSpellMechanics::SpecialSpellMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 ESpellCastProblem::ESpellCastProblem SpecialSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
 	//no problems by default

+ 2 - 2
lib/spells/CDefaultSpellMechanics.h

@@ -45,7 +45,7 @@ private:
 class DLL_LINKAGE DefaultSpellMechanics : public ISpellMechanics
 {
 public:
-	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
+	DefaultSpellMechanics(const CSpell * s);
 
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
 	std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override final;
@@ -94,7 +94,7 @@ private:
 class DLL_LINKAGE SpecialSpellMechanics : public DefaultSpellMechanics
 {
 public:
-	SpecialSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	SpecialSpellMechanics(const CSpell * s);
 
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 protected:

+ 1 - 1
lib/spells/CSpellHandler.cpp

@@ -118,7 +118,7 @@ void CSpell::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) co
 	mechanics->applyBattle(battle, packet);
 }
 
-bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+bool CSpell::adventureCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const
 {
 	assert(env);
 

+ 1 - 1
lib/spells/CSpellHandler.h

@@ -306,7 +306,7 @@ 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;
+	bool adventureCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const;
 	void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const;
 
 public:

+ 15 - 0
lib/spells/CreatureSpellMechanics.cpp

@@ -17,6 +17,11 @@
 #include "../battle/BattleInfo.h"
 
 ///AcidBreathDamageMechanics
+AcidBreathDamageMechanics::AcidBreathDamageMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//todo: this should be effectValue
@@ -56,6 +61,11 @@ ESpellCastProblem::ESpellCastProblem AcidBreathDamageMechanics::isImmuneByStack(
 }
 
 ///DeathStareMechanics
+DeathStareMechanics::DeathStareMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//calculating dmg to display
@@ -80,6 +90,11 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
 }
 
 ///DispellHelpfulMechanics
+DispellHelpfulMechanics::DispellHelpfulMechanics(const CSpell * s):
+	DefaultSpellMechanics(s)
+{
+}
+
 void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);

+ 4 - 8
lib/spells/CreatureSpellMechanics.h

@@ -16,10 +16,8 @@
 class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
 {
 public:
-	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
-
+	AcidBreathDamageMechanics(const CSpell * s);
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
-
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
@@ -27,7 +25,7 @@ protected:
 class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
 {
 public:
-	DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	DeathStareMechanics(const CSpell * s);
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
@@ -35,11 +33,9 @@ protected:
 class DLL_LINKAGE DispellHelpfulMechanics : public DefaultSpellMechanics
 {
 public:
-	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
-
+	DispellHelpfulMechanics(const CSpell * s);
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
-
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 private:
-	 static bool positiveSpellEffects(const Bonus * b);
+	static bool positiveSpellEffects(const Bonus * b);
 };

+ 4 - 6
lib/spells/ISpellMechanics.cpp

@@ -113,13 +113,12 @@ int BattleSpellCastParameters::getEffectValue() const
 }
 
 ///ISpellMechanics
-ISpellMechanics::ISpellMechanics(CSpell * s):
+ISpellMechanics::ISpellMechanics(const CSpell * s):
 	owner(s)
 {
-
 }
 
-std::unique_ptr<ISpellMechanics> ISpellMechanics::createMechanics(CSpell * s)
+std::unique_ptr<ISpellMechanics> ISpellMechanics::createMechanics(const CSpell * s)
 {
 	switch (s->id)
 	{
@@ -174,13 +173,12 @@ std::unique_ptr<ISpellMechanics> ISpellMechanics::createMechanics(CSpell * s)
 }
 
 //IAdventureSpellMechanics
-IAdventureSpellMechanics::IAdventureSpellMechanics(CSpell * s):
+IAdventureSpellMechanics::IAdventureSpellMechanics(const CSpell * s):
 	owner(s)
 {
-
 }
 
-std::unique_ptr<IAdventureSpellMechanics> IAdventureSpellMechanics::createMechanics(CSpell * s)
+std::unique_ptr<IAdventureSpellMechanics> IAdventureSpellMechanics::createMechanics(const CSpell * s)
 {
 	switch (s->id)
 	{

+ 11 - 9
lib/spells/ISpellMechanics.h

@@ -13,6 +13,7 @@
 #include "CSpellHandler.h"
 #include "../battle/BattleHex.h"
 
+struct Query;
 
 ///callback to be provided by server
 class DLL_LINKAGE SpellCastEnvironment
@@ -27,7 +28,9 @@ public:
 	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
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) const = 0;	//TODO: remove
+
+	virtual void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) const = 0;//TODO: type safety on query, use generic query packet when implemented
 };
 
 ///all parameters of particular cast event
@@ -101,13 +104,12 @@ struct DLL_LINKAGE SpellTargetingContext
 	SpellTargetingContext(const CSpell * s, ECastingMode::ECastingMode mode_, const ISpellCaster * caster_, int schoolLvl_, BattleHex destination_)
 		: ti(s,schoolLvl_, mode_), mode(mode_), destination(destination_), caster(caster_), schoolLvl(schoolLvl_)
 	{};
-
 };
 
 class DLL_LINKAGE ISpellMechanics
 {
 public:
-	ISpellMechanics(CSpell * s);
+	ISpellMechanics(const CSpell * s);
 	virtual ~ISpellMechanics(){};
 
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
@@ -125,9 +127,9 @@ public:
 	//if true use generic algorithm for target existence check, see CSpell::canBeCast
 	virtual bool requiresCreatureTarget() const = 0;
 
-	static std::unique_ptr<ISpellMechanics> createMechanics(CSpell * s);
+	static std::unique_ptr<ISpellMechanics> createMechanics(const CSpell * s);
 protected:
-	CSpell * owner;
+	const CSpell * owner;
 };
 
 struct DLL_LINKAGE AdventureSpellCastParameters
@@ -139,12 +141,12 @@ struct DLL_LINKAGE AdventureSpellCastParameters
 class DLL_LINKAGE IAdventureSpellMechanics
 {
 public:
-	IAdventureSpellMechanics(CSpell * s);
+	IAdventureSpellMechanics(const CSpell * s);
 	virtual ~IAdventureSpellMechanics() = default;
 
-	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
+	virtual bool adventureCast(const SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0;
 
-	static std::unique_ptr<IAdventureSpellMechanics> createMechanics(CSpell * s);
+	static std::unique_ptr<IAdventureSpellMechanics> createMechanics(const CSpell * s);
 protected:
-	CSpell * owner;
+	const CSpell * owner;
 };

+ 25 - 30
server/CGameHandler.cpp

@@ -71,7 +71,8 @@ public:
 	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;
+	bool moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) const override;
+	void genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode &)> callback) const override;
 private:
 	mutable CGameHandler * gh;
 };
@@ -310,7 +311,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 	}
 	else if (hlu.skills.size() > 1)
 	{
-		auto levelUpQuery = std::make_shared<CHeroLevelUpDialogQuery>(hlu);
+		auto levelUpQuery = std::make_shared<CHeroLevelUpDialogQuery>(this, hlu);
 		hlu.queryID = levelUpQuery->queryID;
 		queries.addQuery(levelUpQuery);
 		sendAndApply(&hlu);
@@ -448,7 +449,7 @@ void CGameHandler::levelUpCommander(const CCommanderInstance * c)
 	}
 	else if (skillAmount > 1) //apply and ask for secondary skill
 	{
-		auto commanderLevelUp = std::make_shared<CCommanderLevelUpDialogQuery>(clu);
+		auto commanderLevelUp = std::make_shared<CCommanderLevelUpDialogQuery>(this, clu);
 		clu.queryID = commanderLevelUp->queryID;
 		queries.addQuery(commanderLevelUp);
 		sendAndApply(&clu);
@@ -1438,7 +1439,6 @@ CGameHandler::CGameHandler(void)
 	applier = new CApplier<CBaseForGHApply>;
 	registerTypesServerPacks(*applier);
 	visitObjectAfterVictory = false;
-	queries.gh = this;
 
 	spellEnv = new ServerSpellCastEnvironment(this);
 }
@@ -2144,7 +2144,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	{
 		LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->name % tmh.start % tmh.end);
 
-		auto moveQuery = std::make_shared<CHeroMovementQuery>(tmh, h);
+		auto moveQuery = std::make_shared<CHeroMovementQuery>(this, tmh, h);
 		queries.addQuery(moveQuery);
 
 		if (leavingTile == LEAVING_TILE)
@@ -2317,7 +2317,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner)
 
 void CGameHandler::showBlockingDialog(BlockingDialog *iw)
 {
-	auto dialogQuery = std::make_shared<CBlockingDialogQuery>(*iw);
+	auto dialogQuery = std::make_shared<CBlockingDialogQuery>(this, *iw);
 	queries.addQuery(dialogQuery);
 	iw->queryID = dialogQuery->queryID;
 	sendToAllClients(iw);
@@ -2325,7 +2325,7 @@ void CGameHandler::showBlockingDialog(BlockingDialog *iw)
 
 void CGameHandler::showTeleportDialog(TeleportDialog *iw)
 {
-	auto dialogQuery = std::make_shared<CTeleportDialogQuery>(*iw);
+	auto dialogQuery = std::make_shared<CTeleportDialogQuery>(this, *iw);
 	queries.addQuery(dialogQuery);
 	iw->queryID = dialogQuery->queryID;
 	sendToAllClients(iw);
@@ -2453,7 +2453,7 @@ void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedI
 
 	setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
 
-	auto battleQuery = std::make_shared<CBattleQuery>(gs->curB);
+	auto battleQuery = std::make_shared<CBattleQuery>(this, gs->curB);
 	queries.addQuery(battleQuery);
 
 	boost::thread(&CGameHandler::runBattle, this);
@@ -2620,7 +2620,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 
 	if (getPlayerRelations(h1->getOwner(), h2->getOwner()))
 	{
-		auto exchange = std::make_shared<CGarrisonDialogQuery>(h1, h2);
+		auto exchange = std::make_shared<CGarrisonDialogQuery>(this, h1, h2);
 		ExchangeDialog hex;
 		hex.queryID = exchange->queryID;
 		hex.heroes[0] = getHero(hero1);
@@ -3735,20 +3735,19 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 	return true;
 }
 
-bool CGameHandler::queryReply(QueryID qid, ui32 answer, PlayerColor player)
+bool CGameHandler::queryReply(QueryID qid, const JsonNode & answer, PlayerColor player)
 {
 	boost::unique_lock<boost::recursive_mutex> lock(gsm);
 
-	logGlobal->trace("Player %s attempts answering query %d with answer %d", player, qid, answer);
+	logGlobal->trace("Player %s attempts answering query %d with answer:", player, qid);
+	logGlobal->traceStream() << answer;
 
 	auto topQuery = queries.topQuery(player);
 	COMPLAIN_RET_FALSE_IF(!topQuery, "This player doesn't have any queries!");
 	COMPLAIN_RET_FALSE_IF(topQuery->queryID != qid, "This player top query has different ID!");
 	COMPLAIN_RET_FALSE_IF(!topQuery->endsByPlayerAnswer(), "This query cannot be ended by player's answer!");
 
-	if (auto dialogQuery = std::dynamic_pointer_cast<CDialogQuery>(topQuery))
-		dialogQuery->answer = answer;
-
+	topQuery->setReply(answer);
 	queries.popQuery(topQuery);
 	return true;
 }
@@ -4853,7 +4852,7 @@ void CGameHandler::showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID h
 	assert(lowerArmy);
 	assert(upperArmy);
 
-	auto garrisonQuery = std::make_shared<CGarrisonDialogQuery>(upperArmy, lowerArmy);
+	auto garrisonQuery = std::make_shared<CGarrisonDialogQuery>(this, upperArmy, lowerArmy);
 	queries.addQuery(garrisonQuery);
 
 	GarrisonDialog gd;
@@ -4924,7 +4923,7 @@ bool CGameHandler::isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2)
 void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInstance * h)
 {
 	logGlobal->debug("%s visits %s (%d:%d)", h->nodeName(), obj->getObjectName(), obj->ID, obj->subID);
-	auto visitQuery = std::make_shared<CObjectVisitQuery>(obj, h, obj->visitablePos());
+	auto visitQuery = std::make_shared<CObjectVisitQuery>(this, obj, h, obj->visitablePos());
 	queries.addQuery(visitQuery); //TODO real visit pos
 
 	HeroVisit hv;
@@ -5409,17 +5408,6 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
 	}
 }
 
-bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
-{
-	const CSpell *s = spellID.toSpell();
-
-	AdventureSpellCastParameters p;
-	p.caster = h;
-	p.pos = pos;
-
-	return s->adventureCast(spellEnv, p);
-}
-
 void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
 {
 	if (!t.visitableObjects.empty())
@@ -6533,13 +6521,20 @@ 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
+bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, bool teleporting) const
+{
+	return gh->moveHero(hid, dst, teleporting, false);
+}
+
+void ServerSpellCastEnvironment::genericQuery(Query * request, PlayerColor color, std::function<void(const JsonNode&)> callback) const
 {
-	return gh->moveHero(hid, dst, teleporting, false, asker);
+	auto query = std::make_shared<CGenericQuery>(&gh->queries, color, callback);
+	request->queryID = query->queryID;
+	gh->queries.addQuery(query);
+	gh->sendAndApply(request);
 }

+ 5 - 7
server/CGameHandler.h

@@ -31,7 +31,7 @@ struct NewStructures;
 class CGHeroInstance;
 class IMarket;
 
-class ServerSpellCastEnvironment;
+class SpellCastEnvironment;
 
 struct PlayerStatus
 {
@@ -92,6 +92,8 @@ public:
 	ui32 QID;
 	Queries queries;
 
+	SpellCastEnvironment * spellEnv;
+
 	bool isValidObject(const CGObjectInstance *obj) const;
 	bool isBlockedByQueries(const CPack *pack, PlayerColor player);
 	bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
@@ -199,7 +201,7 @@ public:
 	void stackTurnTrigger(const CStack *stack);
 	void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
 	void removeObstacle(const CObstacleInstance &obstacle);
-	bool queryReply( QueryID qid, ui32 answer, PlayerColor player );
+	bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player );
 	bool hireHero( const CGObjectInstance *obj, ui8 hid, PlayerColor player );
 	bool buildBoat( ObjectInstanceID objid );
 	bool setFormation( ObjectInstanceID hid, ui8 formation );
@@ -231,7 +233,6 @@ public:
 	void objectVisitEnded(const CObjectVisitQuery &query);
 	void engageIntoBattle( PlayerColor player );
 	bool dig(const CGHeroInstance *h);
-	bool castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos);
 	void moveArmy(const CArmedInstance *src, const CArmedInstance *dst, bool allowMerging);
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -257,7 +258,6 @@ public:
 		FinishingBattleHelper();
 		FinishingBattleHelper(std::shared_ptr<const CBattleQuery> Query, int RemainingBattleQueriesCount);
 
-		//std::shared_ptr<const CBattleQuery> query;
 		const CGHeroInstance *winnerHero, *loserHero;
 		PlayerColor victor, loser;
 
@@ -265,7 +265,7 @@ public:
 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
-			h & /*query & */winnerHero & loserHero & victor & loser;
+			h & winnerHero & loserHero & victor & loser;
 			if(version < 774 && !h.saving)
 			{
 				bool duel;
@@ -292,8 +292,6 @@ public:
 	CRandomGenerator & getRandomGenerator();
 
 private:
-	ServerSpellCastEnvironment * spellEnv;
-
 	std::list<PlayerColor> generatePlayerTurnOrder() const;
 	void makeStackDoNothing(const CStack * next);
 	void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;

+ 132 - 73
server/CQuery.cpp

@@ -7,7 +7,7 @@
 boost::mutex Queries::mx;
 
 template <typename Container>
-std::string formatContainer(const Container &c, std::string delimeter=", ", std::string opener="(", std::string closer=")")
+std::string formatContainer(const Container & c, std::string delimeter = ", ", std::string opener = "(", std::string closer=")")
 {
 	std::string ret = opener;
 	auto itr = std::begin(c);
@@ -24,17 +24,18 @@ std::string formatContainer(const Container &c, std::string delimeter=", ", std:
 	return ret;
 }
 
-std::ostream & operator<<(std::ostream &out, const CQuery &query)
+std::ostream & operator<<(std::ostream & out, const CQuery & query)
 {
 	return out << query.toString();
 }
 
-std::ostream & operator<<(std::ostream &out, QueryPtr query)
+std::ostream & operator<<(std::ostream & out, QueryPtr query)
 {
 	return out << "[" << query.get() << "] " << query->toString();
 }
 
-CQuery::CQuery(void)
+CQuery::CQuery(Queries * Owner):
+	owner(Owner)
 {
 	boost::unique_lock<boost::mutex> l(Queries::mx);
 
@@ -53,9 +54,7 @@ CQuery::~CQuery(void)
 void CQuery::addPlayer(PlayerColor color)
 {
 	if(color.isValidPlayer())
-	{
 		players.push_back(color);
-	}
 }
 
 std::string CQuery::toString() const
@@ -69,31 +68,59 @@ bool CQuery::endsByPlayerAnswer() const
 	return false;
 }
 
-void CQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CQuery::onRemoval(PlayerColor color)
 {
+
 }
 
-bool CQuery::blocksPack(const CPack *pack) const
+bool CQuery::blocksPack(const CPack * pack) const
 {
 	return false;
 }
 
-void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
+
 }
 
-void CQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
+void CQuery::onExposure(QueryPtr topQuery)
 {
-	gh->queries.popQuery(*this);
+	logGlobal->trace("Exposed query with id %d", queryID);
+	owner->popQuery(*this);
 }
 
-void CQuery::onAdding(CGameHandler *gh, PlayerColor color)
+void CQuery::onAdding(PlayerColor color)
 {
 
 }
 
-CObjectVisitQuery::CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile)
-	: visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
+void CQuery::onAdded(PlayerColor color)
+{
+
+}
+
+void CQuery::setReply(const JsonNode & reply)
+{
+
+}
+
+bool CQuery::blockAllButReply(const CPack * pack) const
+{
+	//We accept only query replies from correct player
+	if(auto reply = dynamic_ptr_cast<QueryReply>(pack))
+		return !vstd::contains(players, reply->player);
+
+	return true;
+}
+
+CGhQuery::CGhQuery(CGameHandler * owner):
+	CQuery(&owner->queries), gh(owner)
+{
+
+}
+
+CObjectVisitQuery::CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance * Obj, const CGHeroInstance * Hero, int3 Tile):
+	CGhQuery(owner), visitedObject(Obj), visitingHero(Hero), tile(Tile), removeObjectAfterVisit(false)
 {
 	addPlayer(Hero->tempOwner);
 }
@@ -105,7 +132,7 @@ bool CObjectVisitQuery::blocksPack(const CPack *pack) const
 	return true;
 }
 
-void CObjectVisitQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CObjectVisitQuery::onRemoval(PlayerColor color)
 {
 	gh->objectVisitEnded(*this);
 
@@ -115,13 +142,13 @@ void CObjectVisitQuery::onRemoval(CGameHandler *gh, PlayerColor color)
 		gh->removeObject(visitedObject);
 }
 
-void CObjectVisitQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
+void CObjectVisitQuery::onExposure(QueryPtr topQuery)
 {
 	//Object may have been removed and deleted.
 	if(gh->isValidObject(visitedObject))
 		topQuery->notifyObjectAboutRemoval(*this);
 
-	gh->queries.popQuery(*this);
+	owner->popQuery(*this);
 }
 
 void Queries::popQuery(PlayerColor player, QueryPtr query)
@@ -136,13 +163,11 @@ void Queries::popQuery(PlayerColor player, QueryPtr query)
 	queries[player] -= query;
 	auto nextQuery = topQuery(player);
 
-	query->onRemoval(gh, player);
+	query->onRemoval(player);
 
 	//Exposure on query below happens only if removal didn't trigger any new query
 	if(nextQuery && nextQuery == topQuery(player))
-	{
-		nextQuery->onExposure(gh, query);
-	}
+		nextQuery->onExposure(query);
 }
 
 void Queries::popQuery(const CQuery &query)
@@ -170,12 +195,15 @@ void Queries::addQuery(QueryPtr query)
 {
 	for(auto player : query->players)
 		addQuery(player, query);
+
+	for(auto player : query->players)
+		query->onAdded(player);
 }
 
 void Queries::addQuery(PlayerColor player, QueryPtr query)
 {
 	//LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
-	query->onAdding(gh, player);
+	query->onAdding(player);
 	queries[player].push_back(query);
 }
 
@@ -193,7 +221,7 @@ void Queries::popIfTop(QueryPtr query)
 	popIfTop(*query);
 }
 
-void Queries::popIfTop(const CQuery &query)
+void Queries::popIfTop(const CQuery & query)
 {
 	for(PlayerColor color : query.players)
 		if(topQuery(color).get() == &query)
@@ -203,8 +231,8 @@ void Queries::popIfTop(const CQuery &query)
 std::vector<std::shared_ptr<const CQuery>> Queries::allQueries() const
 {
 	std::vector<std::shared_ptr<const CQuery>> ret;
-	for(auto &playerQueries : queries)
-		for(auto &query : playerQueries.second)
+	for(auto & playerQueries : queries)
+		for(auto & query : playerQueries.second)
 			ret.push_back(query);
 
 	return ret;
@@ -214,53 +242,55 @@ std::vector<std::shared_ptr<CQuery>> Queries::allQueries()
 {
 	//TODO code duplication with const function :(
 	std::vector<std::shared_ptr<CQuery>> ret;
-	for(auto &playerQueries : queries)
-		for(auto &query : playerQueries.second)
-		ret.push_back(query);
+	for(auto & playerQueries : queries)
+		for(auto & query : playerQueries.second)
+			ret.push_back(query);
 
 	return ret;
 }
 
-void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CBattleQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	assert(result);
 	objectVisit.visitedObject->battleFinished(objectVisit.visitingHero, *result);
 }
 
-CBattleQuery::CBattleQuery(const BattleInfo *Bi)
+CBattleQuery::CBattleQuery(CGameHandler * owner, const BattleInfo * Bi):
+	CGhQuery(owner)
 {
 	belligerents[0] = Bi->sides[0].armyObject;
 	belligerents[1] = Bi->sides[1].armyObject;
 
 	bi = Bi;
 
-	for(auto &side : bi->sides)
+	for(auto & side : bi->sides)
 		addPlayer(side.color);
 }
 
-CBattleQuery::CBattleQuery()
-	:bi(nullptr)
+CBattleQuery::CBattleQuery(CGameHandler * owner):
+	CGhQuery(owner), bi(nullptr)
 {
 	belligerents[0] = belligerents[1] = nullptr;
 }
 
-bool CBattleQuery::blocksPack(const CPack *pack) const
+bool CBattleQuery::blocksPack(const CPack * pack) const
 {
 	const char * name = typeid(*pack).name();
 	return strcmp(name, typeid(MakeAction).name()) && strcmp(name, typeid(MakeCustomAction).name());
 }
 
-void CBattleQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CBattleQuery::onRemoval(PlayerColor color)
 {
 	gh->battleAfterLevelUp(*result);
 }
 
-void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CGarrisonDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	objectVisit.visitedObject->garrisonDialogClosed(objectVisit.visitingHero);
 }
 
-CGarrisonDialogQuery::CGarrisonDialogQuery(const CArmedInstance *up, const CArmedInstance *down)
+CGarrisonDialogQuery::CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance * up, const CArmedInstance * down):
+	CDialogQuery(owner)
 {
 	exchangingArmies[0] = up;
 	exchangingArmies[1] = down;
@@ -269,18 +299,16 @@ CGarrisonDialogQuery::CGarrisonDialogQuery(const CArmedInstance *up, const CArme
 	addPlayer(down->tempOwner);
 }
 
-bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const
+bool CGarrisonDialogQuery::blocksPack(const CPack * pack) const
 {
 	std::set<ObjectInstanceID> ourIds;
 	ourIds.insert(this->exchangingArmies[0]->id);
 	ourIds.insert(this->exchangingArmies[1]->id);
 
-	if (auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack))
-	{
+	if(auto stacks = dynamic_ptr_cast<ArrangeStacks>(pack))
 		return !vstd::contains(ourIds, stacks->id1) || !vstd::contains(ourIds, stacks->id2);
-	}
 
-	if (auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack))
+	if(auto arts = dynamic_ptr_cast<ExchangeArtifacts>(pack))
 	{
 		if(auto id1 = boost::apply_visitor(GetEngagedHeroIds(), arts->src.artHolder))
 			if(!vstd::contains(ourIds, *id1))
@@ -291,36 +319,32 @@ bool CGarrisonDialogQuery::blocksPack(const CPack *pack) const
 				return true;
 		return false;
 	}
-	if (auto dismiss = dynamic_ptr_cast<DisbandCreature>(pack))
-	{
+	if(auto dismiss = dynamic_ptr_cast<DisbandCreature>(pack))
 		return !vstd::contains(ourIds, dismiss->id);
-	}
 
-	if (auto dismiss = dynamic_ptr_cast<AssembleArtifacts>(pack))
-	{
+	if(auto dismiss = dynamic_ptr_cast<AssembleArtifacts>(pack))
 		return !vstd::contains(ourIds, dismiss->heroID);
-	}
 
 	if(auto upgrade = dynamic_ptr_cast<UpgradeCreature>(pack))
-	{
 		return !vstd::contains(ourIds, upgrade->id);
-	}
+
 	return CDialogQuery::blocksPack(pack);
 }
 
-void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CBlockingDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	assert(answer);
 	objectVisit.visitedObject->blockingDialogAnswered(objectVisit.visitingHero, *answer);
 }
 
-CBlockingDialogQuery::CBlockingDialogQuery(const BlockingDialog &bd)
+CBlockingDialogQuery::CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog & bd):
+	CDialogQuery(owner)
 {
 	this->bd = bd;
 	addPlayer(bd.player);
 }
 
-void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	// do not change to dynamic_ptr_cast - SIGSEGV!
 	auto obj = dynamic_cast<const CGTeleport*>(objectVisit.visitedObject);
@@ -330,71 +354,80 @@ void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &obj
 		logGlobal->error("Invalid instance in teleport query");
 }
 
-CTeleportDialogQuery::CTeleportDialogQuery(const TeleportDialog &td)
+CTeleportDialogQuery::CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog & td):
+	CDialogQuery(owner)
 {
 	this->td = td;
 	addPlayer(td.hero->tempOwner);
 }
 
-CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(const HeroLevelUp &Hlu)
+CHeroLevelUpDialogQuery::CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp & Hlu):
+	CDialogQuery(owner)
 {
 	hlu = Hlu;
 	addPlayer(hlu.hero->tempOwner);
 }
 
-void CHeroLevelUpDialogQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CHeroLevelUpDialogQuery::onRemoval(PlayerColor color)
 {
 	assert(answer);
 	logGlobal->trace("Completing hero level-up query. %s gains skill %d", hlu.hero->getObjectName(), answer.get());
 	gh->levelUpHero(hlu.hero, hlu.skills[*answer]);
 }
 
-void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CHeroLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
 }
 
-CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(const CommanderLevelUp &Clu)
+CCommanderLevelUpDialogQuery::CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp & Clu):
+	CDialogQuery(owner)
 {
 	clu = Clu;
 	addPlayer(clu.hero->tempOwner);
 }
 
-void CCommanderLevelUpDialogQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CCommanderLevelUpDialogQuery::onRemoval(PlayerColor color)
 {
 	assert(answer);
 	logGlobal->trace("Completing commander level-up query. Commander of hero %s gains skill %s", clu.hero->getObjectName(), answer.get());
 	gh->levelUpCommander(clu.hero->commander, clu.skills[*answer]);
 }
 
-void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
+void CCommanderLevelUpDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery & objectVisit) const
 {
 	objectVisit.visitedObject->heroLevelUpDone(objectVisit.visitingHero);
 }
 
+CDialogQuery::CDialogQuery(CGameHandler * owner):
+	CGhQuery(owner)
+{
+
+}
+
 bool CDialogQuery::endsByPlayerAnswer() const
 {
 	return true;
 }
 
-bool CDialogQuery::blocksPack(const CPack *pack) const
+bool CDialogQuery::blocksPack(const CPack * pack) const
 {
-	//We accept only query replies from correct player
-	if(auto reply = dynamic_ptr_cast<QueryReply>(pack))
-	{
-		return !vstd::contains(players, reply->player);
-	}
+	return blockAllButReply(pack);
+}
 
-	return true;
+void CDialogQuery::setReply(const JsonNode & reply)
+{
+	if(reply.getType() == JsonNode::DATA_INTEGER)
+		answer = reply.Integer();
 }
 
-CHeroMovementQuery::CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory)
-	: tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
+CHeroMovementQuery::CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory):
+	CGhQuery(owner), tmh(Tmh), visitDestAfterVictory(VisitDestAfterVictory), hero(Hero)
 {
 	players.push_back(hero->tempOwner);
 }
 
-void CHeroMovementQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
+void CHeroMovementQuery::onExposure(QueryPtr topQuery)
 {
 	assert(players.size() == 1);
 
@@ -407,10 +440,10 @@ void CHeroMovementQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
 		gh->visitObjectOnTile(*gh->getTile(CGHeroInstance::convertPosition(tmh.end, false)), hero);
 	}
 
-	gh->queries.popIfTop(*this);
+	owner->popIfTop(*this);
 }
 
-void CHeroMovementQuery::onRemoval(CGameHandler *gh, PlayerColor color)
+void CHeroMovementQuery::onRemoval(PlayerColor color)
 {
 	PlayerBlocked pb;
 	pb.player = color;
@@ -419,7 +452,7 @@ void CHeroMovementQuery::onRemoval(CGameHandler *gh, PlayerColor color)
 	gh->sendAndApply(&pb);
 }
 
-void CHeroMovementQuery::onAdding(CGameHandler *gh, PlayerColor color)
+void CHeroMovementQuery::onAdding(PlayerColor color)
 {
 	PlayerBlocked pb;
 	pb.player = color;
@@ -427,3 +460,29 @@ void CHeroMovementQuery::onAdding(CGameHandler *gh, PlayerColor color)
 	pb.startOrEnd = PlayerBlocked::BLOCKADE_STARTED;
 	gh->sendAndApply(&pb);
 }
+
+CGenericQuery::CGenericQuery(Queries * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback):
+	CQuery(Owner), callback(Callback)
+{
+	addPlayer(color);
+}
+
+bool CGenericQuery::blocksPack(const CPack * pack) const
+{
+	return blockAllButReply(pack);
+}
+
+bool CGenericQuery::endsByPlayerAnswer() const
+{
+	return true;
+}
+
+void CGenericQuery::onExposure(QueryPtr topQuery)
+{
+	//do nothing
+}
+
+void CGenericQuery::setReply(const JsonNode & reply)
+{
+	callback(reply);
+}

+ 63 - 39
server/CQuery.h

@@ -9,53 +9,63 @@ class CArmedInstance;
 class CGameHandler;
 class CObjectVisitQuery;
 class CQuery;
+class Queries;
+class CSpell;
+class SpellCastEnvironment;
 
 typedef std::shared_ptr<CQuery> QueryPtr;
 
 // This class represents any kind of prolonged interaction that may need to do something special after it is over.
 // It does not necessarily has to be "query" requiring player action, it can be also used internally within server.
 // Examples:
-// - all kinds of blocking dialog windows 
-// - battle 
+// - all kinds of blocking dialog windows
+// - battle
 // - object visit
 // - hero movement
 // Queries can cause another queries, forming a stack of queries for each player. Eg: hero movement -> object visit -> dialog.
 class CQuery
 {
-protected:
-	void addPlayer(PlayerColor color);
 public:
 	std::vector<PlayerColor> players; //players that are affected (often "blocked") by query
 	QueryID queryID;
 
-	CQuery(void);
+	CQuery(Queries * Owner);
 
 
 	virtual bool blocksPack(const CPack *pack) const; //query can block attempting actions by player. Eg. he can't move hero during the battle.
 
 	virtual bool endsByPlayerAnswer() const; //query is removed after player gives answer (like dialogs)
-	virtual void onAdding(CGameHandler *gh, PlayerColor color); //called just before query is pushed on stack
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color); //called after query is removed from stack
-	virtual void onExposure(CGameHandler *gh, QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top)
+	virtual void onAdding(PlayerColor color); //called just before query is pushed on stack
+	virtual void onAdded(PlayerColor color); //called right after query is pushed on stack
+	virtual void onRemoval(PlayerColor color); //called after query is removed from stack
+	virtual void onExposure(QueryPtr topQuery);//called when query immediately above is removed and this is exposed (becomes top)
 	virtual std::string toString() const;
 
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const;
 
-	virtual ~CQuery(void);
-
+	virtual void setReply(const JsonNode & reply);
 
-	template <typename Handler> void serialize(Handler &h, const int version)
-	{
-		h & players & queryID;
-	}
+	virtual ~CQuery(void);
+protected:
+	Queries * owner;
+	void addPlayer(PlayerColor color);
+	bool blockAllButReply(const CPack * pack) const;
 };
 
 std::ostream &operator<<(std::ostream &out, const CQuery &query);
 std::ostream &operator<<(std::ostream &out, QueryPtr query);
 
+class CGhQuery : public CQuery
+{
+public:
+	CGhQuery(CGameHandler * owner);
+protected:
+	CGameHandler * gh;
+};
+
 //Created when hero visits object.
 //Removed when query above is resolved (or immediately after visit if no queries were created)
-class CObjectVisitQuery : public CQuery
+class CObjectVisitQuery : public CGhQuery
 {
 public:
 	const CGObjectInstance *visitedObject;
@@ -63,14 +73,14 @@ public:
 	int3 tile; //may be different than hero pos -> eg. visit via teleport
 	bool removeObjectAfterVisit;
 
-	CObjectVisitQuery(const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile);
+	CObjectVisitQuery(CGameHandler * owner, const CGObjectInstance *Obj, const CGHeroInstance *Hero, int3 Tile);
 
 	virtual bool blocksPack(const CPack *pack) const override;
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
-	virtual void onExposure(CGameHandler *gh, QueryPtr topQuery) override;
+	virtual void onRemoval(PlayerColor color) override;
+	virtual void onExposure(QueryPtr topQuery) override;
 };
 
-class CBattleQuery : public CQuery
+class CBattleQuery : public CGhQuery
 {
 public:
 	std::array<const CArmedInstance *,2> belligerents;
@@ -78,35 +88,38 @@ public:
 	const BattleInfo *bi;
 	boost::optional<BattleResult> result;
 
-	CBattleQuery();
-	CBattleQuery(const BattleInfo *Bi); //TODO
+	CBattleQuery(CGameHandler * owner);
+	CBattleQuery(CGameHandler * owner, const BattleInfo * Bi); //TODO
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 	virtual bool blocksPack(const CPack *pack) const override;
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
+	virtual void onRemoval(PlayerColor color) override;
 };
 
 //Created when hero attempts move and something happens
 //(not necessarily position change, could be just an object interaction).
-class CHeroMovementQuery : public CQuery
+class CHeroMovementQuery : public CGhQuery
 {
 public:
 	TryMoveHero tmh;
 	bool visitDestAfterVictory; //if hero moved to guarded tile and it should be visited once guard is defeated
 	const CGHeroInstance *hero;
 
-	virtual void onExposure(CGameHandler *gh, QueryPtr topQuery) override;
+	virtual void onExposure(QueryPtr topQuery) override;
 
-	CHeroMovementQuery(const TryMoveHero &Tmh, const CGHeroInstance *Hero, bool VisitDestAfterVictory = false);
-	virtual void onAdding(CGameHandler *gh, PlayerColor color) override;
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
+	CHeroMovementQuery(CGameHandler * owner, const TryMoveHero & Tmh, const CGHeroInstance * Hero, bool VisitDestAfterVictory = false);
+	virtual void onAdding(PlayerColor color) override;
+	virtual void onRemoval(PlayerColor color) override;
 };
 
-class CDialogQuery : public CQuery
+class CDialogQuery : public CGhQuery
 {
 public:
-	boost::optional<ui32> answer;
+	CDialogQuery(CGameHandler * owner);
 	virtual bool endsByPlayerAnswer() const override;
 	virtual bool blocksPack(const CPack *pack) const override;
+	void setReply(const JsonNode & reply) override;
+protected:
+	boost::optional<ui32> answer;
 };
 
 class CGarrisonDialogQuery : public CDialogQuery //used also for hero exchange dialogs
@@ -114,7 +127,7 @@ class CGarrisonDialogQuery : public CDialogQuery //used also for hero exchange d
 public:
 	std::array<const CArmedInstance *,2> exchangingArmies;
 
-	CGarrisonDialogQuery(const CArmedInstance *up, const CArmedInstance *down);
+	CGarrisonDialogQuery(CGameHandler * owner, const CArmedInstance *up, const CArmedInstance *down);
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 	virtual bool blocksPack(const CPack *pack) const override;
 };
@@ -125,7 +138,7 @@ class CBlockingDialogQuery : public CDialogQuery
 public:
 	BlockingDialog bd; //copy of pack... debug purposes
 
-	CBlockingDialogQuery(const BlockingDialog &bd);
+	CBlockingDialogQuery(CGameHandler * owner, const BlockingDialog &bd);
 
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 };
@@ -135,7 +148,7 @@ class CTeleportDialogQuery : public CDialogQuery
 public:
 	TeleportDialog td; //copy of pack... debug purposes
 
-	CTeleportDialogQuery(const TeleportDialog &td);
+	CTeleportDialogQuery(CGameHandler * owner, const TeleportDialog &td);
 
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 };
@@ -143,27 +156,39 @@ public:
 class CHeroLevelUpDialogQuery : public CDialogQuery
 {
 public:
-	CHeroLevelUpDialogQuery(const HeroLevelUp &Hlu);
+	CHeroLevelUpDialogQuery(CGameHandler * owner, const HeroLevelUp &Hlu);
 
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
+	virtual void onRemoval(PlayerColor color) override;
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 
 	HeroLevelUp hlu;
 };
 
-
 class CCommanderLevelUpDialogQuery : public CDialogQuery
 {
 public:
-	CCommanderLevelUpDialogQuery(const CommanderLevelUp &Clu);
+	CCommanderLevelUpDialogQuery(CGameHandler * owner, const CommanderLevelUp &Clu);
 
-	virtual void onRemoval(CGameHandler *gh, PlayerColor color) override;
+	virtual void onRemoval(PlayerColor color) override;
 	virtual void notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const override;
 
 	CommanderLevelUp clu;
 };
 
-struct Queries
+class CGenericQuery : public CQuery
+{
+public:
+	CGenericQuery(Queries * Owner, PlayerColor color, std::function<void(const JsonNode &)> Callback);
+
+	bool blocksPack(const CPack * pack) const override;
+	bool endsByPlayerAnswer() const override;
+	void onExposure(QueryPtr topQuery) override;
+	void setReply(const JsonNode & reply) override;
+private:
+	std::function<void(const JsonNode &)> callback;
+};
+
+class Queries
 {
 private:
 	void addQuery(PlayerColor player, QueryPtr query);
@@ -172,7 +197,6 @@ private:
 	std::map<PlayerColor, std::vector<QueryPtr>> queries; //player => stack of queries
 
 public:
-	CGameHandler *gh;
 	static boost::mutex mx;
 
 	void addQuery(QueryPtr query);

+ 17 - 3
server/NetPacksServer.cpp

@@ -9,6 +9,8 @@
 #include "../lib/battle/BattleInfo.h"
 #include "../lib/battle/BattleAction.h"
 #include "../lib/serializer/Connection.h"
+#include "../lib/spells/CSpellHandler.h"
+#include "../lib/spells/ISpellMechanics.h"
 
 
 #define PLAYER_OWNS(id) (gh->getPlayerAt(c)==gh->getOwner(id))
@@ -235,7 +237,7 @@ bool QueryReply::applyGh( CGameHandler *gh )
 		COMPLAIN_AND_RETURN("Cannot answer the query with id -1!");
 
 	assert(vstd::contains(gh->states.players, player));
-	return gh->queryReply(qid, answer, player);
+	return gh->queryReply(qid, reply, player);
 }
 
 bool MakeAction::applyGh( CGameHandler *gh )
@@ -275,10 +277,22 @@ bool DigWithHero::applyGh( CGameHandler *gh )
 	return gh->dig(gh->getHero(id));
 }
 
-bool CastAdvSpell::applyGh( CGameHandler *gh )
+bool CastAdvSpell::applyGh(CGameHandler * gh)
 {
 	ERROR_IF_NOT_OWNS(hid);
-	return gh->castSpell(gh->getHero(hid), sid, pos);
+
+	const CSpell * s = sid.toSpell();
+	if(!s)
+		ERROR_AND_RETURN;
+	const CGHeroInstance * h = gh->getHero(hid);
+	if(!h)
+		ERROR_AND_RETURN;
+
+	AdventureSpellCastParameters p;
+	p.caster = h;
+	p.pos = pos;
+
+	return s->adventureCast(gh->spellEnv, p);
 }
 
 bool PlayerMessage::applyGh( CGameHandler *gh )