فهرست منبع

Lots of refactoring & work for creature spells.

No new features yet, but summon elementals now work properly again.
DjWarmonger 14 سال پیش
والد
کامیت
6fdb984799
13فایلهای تغییر یافته به همراه409 افزوده شده و 252 حذف شده
  1. 233 186
      client/CBattleInterface.cpp
  2. 4 1
      client/CBattleInterface.h
  3. 5 5
      client/NetPacksClient.cpp
  4. 1 1
      global.h
  5. 2 2
      lib/BattleAction.h
  6. 41 3
      lib/BattleState.cpp
  7. 1 0
      lib/BattleState.h
  8. 12 0
      lib/IGameCallback.cpp
  9. 1 0
      lib/IGameCallback.h
  10. 19 0
      lib/NetPacks.h
  11. 22 53
      lib/NetPacksLib.cpp
  12. 1 0
      lib/RegisterTypes.cpp
  13. 67 1
      server/CGameHandler.cpp

+ 233 - 186
client/CBattleInterface.cpp

@@ -1184,7 +1184,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation * anim)
 
 CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, CGHeroInstance *hero1, CGHeroInstance *hero2, const SDL_Rect & myRect, CPlayerInterface * att, CPlayerInterface * defen)
 	: queue(NULL), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0), 
-	  activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1),
+	  activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1), spellSelMode(NO_LOCATION),
 	  currentlyHoveredHex(-1), attackingHex(-1), tacticianInterface(NULL),  stackCanCastSpell(false), spellDestSelectMode(false), spellToCast(NULL),
 	  siegeH(NULL), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0), givenCommand(NULL),
 	  myTurn(false), resWindow(NULL), moveStarted(false), moveSh(-1), bresult(NULL)
@@ -2003,7 +2003,7 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
 }
 void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 {
-	if(activeStack!= NULL && !spellDestSelectMode)
+	if(activeStack && !spellDestSelectMode)
 	{
         int lastMouseHoveredStack = mouseHoveredStack;
 		mouseHoveredStack = -1;
@@ -2039,6 +2039,18 @@ void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 							//display the possibility to heal this creature
 							CCS->curh->changeGraphic(1,17);
 						}
+						else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING))
+						{
+							CCS->curh->changeGraphic(3, 0);
+						}
+						else if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
+						{
+							//spellDestSelectMode
+							if (curInt->cb->battleCanCastThisSpell(creatureSpellToCast, THex(myNumber)) == SpellCasting::OK)
+								CCS->curh->changeGraphic(3, 0);
+							//if (battleIsImmune(NULL, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING, myNumber) == SpellCasting::OK)
+							//if (battleCanCastThisSpell(curInt->playerID, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING))
+						}
 						else
 						{
 							//info about creature
@@ -2709,11 +2721,13 @@ void CBattleInterface::giveCommand(ui8 action, THex tile, ui32 stack, si32 addit
 	//some basic validations
 	switch(action)
 	{
-	case 6:
-		assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist
-	case 2: case 7: case 9:
-		assert(tile < BFIELD_SIZE);
-		break;
+		case BattleAction::WALK_AND_ATTACK:
+			assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist
+		case BattleAction::WALK:
+		case BattleAction::SHOOT:
+		case BattleAction::CATAPULT:
+			assert(tile < BFIELD_SIZE);
+			break;
 	}
 
 	if(!tacticsMode)
@@ -2784,7 +2798,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero()
 void CBattleInterface::hexLclicked(int whichOne)
 {
 	const CStack * actSt = activeStack;
-	const CStack* dest = curInt->cb->battleGetStackByPos(whichOne); //creature at destination tile; -1 if there is no one
+	const CStack* dest = curInt->cb->battleGetStackByPos(whichOne, false); //creature at destination tile; -1 if there is no one
 	if(!actSt)
 	{
 		tlog3 << "Hex l-clicked when no active stack!\n";
@@ -2797,29 +2811,30 @@ void CBattleInterface::hexLclicked(int whichOne)
 	{
 		if(!myTurn)
 			return; //we are not permit to do anything
-		if(spellDestSelectMode)
+		if(spellDestSelectMode) //TODO: choose target for area creature spell
 		{
 			//checking destination
 			bool allowCasting = true;
 			bool onlyAlive = spellToCast->additionalInfo != 38 && spellToCast->additionalInfo != 39; //when casting resurrection or animate dead we should be allow to select dead stack
+			//TODO: more general handling of dead targets
 			switch(spellSelMode)
 			{
-			case 1:
-				if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner )
+			case FRIENDLY_CREATURE:
+				if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != dest->owner )
 					allowCasting = false;
 				break;
-			case 2:
-				if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner )
+			case HOSTILE_CREATURE:
+				if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == dest->owner )
 					allowCasting = false;
 				break;
-			case 3:
+			case ANY_CREATURE:
 				if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive))
 					allowCasting = false;
 				break;
-			case 4:
+			case OBSTACLE:
 				if(!blockedByObstacle(whichOne))
 					allowCasting = false;
-			case 5: //teleport
+			case TELEPORT: //teleport
 				const CSpell *s = CGI->spellh->spells[spellToCast->additionalInfo];
 				ui8 skill = getActiveHero()->getSpellSchoolLevel(s); //skill level
 				if (!curInt->cb->battleCanTeleportTo(activeStack, whichOne, skill))
@@ -2836,197 +2851,218 @@ void CBattleInterface::hexLclicked(int whichOne)
 				endCastingSpell();
 			}
 		}
-		else //we don't cast any spell
+		else //we don't aim for spell target
 		{
-			if(!dest || !dest->alive()) //no creature at that tile
+			bool walkableTile = false;
+			if (dest)
 			{
-				if(std::find(occupyableHexes.begin(),occupyableHexes.end(),whichOne)!=occupyableHexes.end())// and it's in our range
+				if (dest->alive())
 				{
-					CCS->curh->changeGraphic(1, 6); //cursor should be changed
-					if(activeStack->doubleWide())
+					if(dest->owner != actSt->owner && curInt->cb->battleCanShoot(activeStack, whichOne)) //shooting
 					{
-						std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
-						int shiftedDest = whichOne + (activeStack->attackerOwned ? 1 : -1);
-						if(vstd::contains(acc, whichOne))
-							giveCommand(2,whichOne,activeStack->ID);
-						else if(vstd::contains(acc, shiftedDest))
-							giveCommand(2,shiftedDest,activeStack->ID);
+						CCS->curh->changeGraphic(1, 6); //cursor should be changed
+						giveCommand (BattleAction::SHOOT, whichOne, activeStack->ID);
 					}
-					else
+					else if(dest->owner != actSt->owner) //attacking
 					{
-						giveCommand(2,whichOne,activeStack->ID);
+						const CStack * actStack = activeStack;
+						int attackFromHex = -1; //hex from which we will attack chosen stack
+						switch(CCS->curh->number)
+						{
+						case 12: //from bottom right
+							{
+								bool doubleWide = actStack->doubleWide();
+								int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) +
+									(actStack->attackerOwned && doubleWide ? 1 : 0);
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(actStack->attackerOwned) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						case 7: //from bottom left
+							{
+								int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH );
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(actStack->attackerOwned) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						case 8: //from left
+							{
+								if(actStack->doubleWide() && !actStack->attackerOwned)
+								{
+									std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+									if(vstd::contains(acc, whichOne))
+										attackFromHex = whichOne - 1;
+									else
+										attackFromHex = whichOne - 2;
+								}
+								else
+								{
+									attackFromHex = whichOne - 1;
+								}
+								break;
+							}
+						case 9: //from top left
+							{
+								int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH );
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(actStack->attackerOwned) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						case 10: //from top right
+							{
+								bool doubleWide = actStack->doubleWide();
+								int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) +
+									(actStack->attackerOwned && doubleWide ? 1 : 0);
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(actStack->attackerOwned) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						case 11: //from right
+							{
+								if(actStack->doubleWide() && actStack->attackerOwned)
+								{
+									std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+									if(vstd::contains(acc, whichOne))
+										attackFromHex = whichOne + 1;
+									else
+										attackFromHex = whichOne + 2;
+								}
+								else
+								{
+									attackFromHex = whichOne + 1;
+								}
+								break;
+							}
+						case 13: //from bottom
+							{
+								int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 );
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						case 14: //from top
+							{
+								int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 );
+								if(vstd::contains(occupyableHexes, destHex))
+									attackFromHex = destHex;
+								else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
+								{
+									if(vstd::contains(occupyableHexes, destHex+1))
+										attackFromHex = destHex+1;
+								}
+								else //if we are defender
+								{
+									if(vstd::contains(occupyableHexes, destHex-1))
+										attackFromHex = destHex-1;
+								}
+								break;
+							}
+						}
+
+						if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
+						{
+							giveCommand(BattleAction::WALK_AND_ATTACK, attackFromHex, activeStack->ID, whichOne);
+					
+							CCS->curh->changeGraphic(1, 6); //cursor should be changed
+						}
+
 					}
-				}
-				else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult)
+					else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal
+					{ //TODO: spellDestSelectMode > -2 if we don't want to heal but perform some other (?) action
+						giveCommand(BattleAction::STACK_HEAL, whichOne, activeStack->ID); //command healing
+
+						CCS->curh->changeGraphic(1, 6); //cursor should be changed
+					}
+
+				} //stack is not alive
+				else if (actSt->hasBonusOfType(Bonus::DAEMON_SUMMONING) && actSt->casts &&
+						actSt->owner == dest->owner && spellSelMode > -2)//friendly body we can (and want) rise
 				{
-					giveCommand(9,whichOne,activeStack->ID);
+					giveCommand(BattleAction::DAEMON_SUMMONING, whichOne, activeStack->ID);
+
+					CCS->curh->changeGraphic(1, 6); //cursor should be changed
 				}
+				else //not a subject of resurrection
+					walkableTile = true;
 			}
-			else if(dest->owner != actSt->owner
-				&& curInt->cb->battleCanShoot(activeStack, whichOne) ) //shooting
+			else
 			{
-				CCS->curh->changeGraphic(1, 6); //cursor should be changed
-				giveCommand(7,whichOne,activeStack->ID);
+				walkableTile = true;
 			}
-			else if(dest->owner != actSt->owner) //attacking
+
+			if (walkableTile) // we can try to move to this tile
 			{
-				const CStack * actStack = activeStack;
-				int attackFromHex = -1; //hex from which we will attack chosen stack
-				switch(CCS->curh->number)
+				if(std::find(occupyableHexes.begin(), occupyableHexes.end(), whichOne) != occupyableHexes.end())// and it's in our range
 				{
-				case 12: //from bottom right
-					{
-						bool doubleWide = actStack->doubleWide();
-						int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) +
-							(actStack->attackerOwned && doubleWide ? 1 : 0);
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(actStack->attackerOwned) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
-					}
-				case 7: //from bottom left
-					{
-						int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH );
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(actStack->attackerOwned) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
-					}
-				case 8: //from left
-					{
-						if(actStack->doubleWide() && !actStack->attackerOwned)
-						{
-							std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
-							if(vstd::contains(acc, whichOne))
-								attackFromHex = whichOne - 1;
-							else
-								attackFromHex = whichOne - 2;
-						}
-						else
-						{
-							attackFromHex = whichOne - 1;
-						}
-						break;
-					}
-				case 9: //from top left
-					{
-						int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH );
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(actStack->attackerOwned) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
-					}
-				case 10: //from top right
-					{
-						bool doubleWide = actStack->doubleWide();
-						int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) +
-							(actStack->attackerOwned && doubleWide ? 1 : 0);
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(actStack->attackerOwned) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
-					}
-				case 11: //from right
-					{
-						if(actStack->doubleWide() && actStack->attackerOwned)
-						{
-							std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
-							if(vstd::contains(acc, whichOne))
-								attackFromHex = whichOne + 1;
-							else
-								attackFromHex = whichOne + 2;
-						}
-						else
-						{
-							attackFromHex = whichOne + 1;
-						}
-						break;
-					}
-				case 13: //from bottom
+					CCS->curh->changeGraphic(1, 6); //cursor should be changed
+					if(activeStack->doubleWide())
 					{
-						int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 );
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
+						std::vector<THex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+						int shiftedDest = whichOne + (activeStack->attackerOwned ? 1 : -1);
+						if(vstd::contains(acc, whichOne))
+							giveCommand (BattleAction::WALK ,whichOne, activeStack->ID);
+						else if(vstd::contains(acc, shiftedDest))
+							giveCommand (BattleAction::WALK, shiftedDest, activeStack->ID);
 					}
-				case 14: //from top
+					else
 					{
-						int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 );
-						if(vstd::contains(occupyableHexes, destHex))
-							attackFromHex = destHex;
-						else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
-						{
-							if(vstd::contains(occupyableHexes, destHex+1))
-								attackFromHex = destHex+1;
-						}
-						else //if we are defender
-						{
-							if(vstd::contains(occupyableHexes, destHex-1))
-								attackFromHex = destHex-1;
-						}
-						break;
+						giveCommand(BattleAction::WALK, whichOne, activeStack->ID);
 					}
 				}
-
-				if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
+				else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult)
 				{
-					giveCommand(6, attackFromHex, activeStack->ID, whichOne);
-					
-					CCS->curh->changeGraphic(1, 6); //cursor should be changed
+					giveCommand(BattleAction::CATAPULT, whichOne, activeStack->ID);
 				}
-
-			}
-			else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal
-			{
-				giveCommand(12, whichOne, activeStack->ID); //command healing
-
-				CCS->curh->changeGraphic(1, 6); //cursor should be changed
 			}
 		}
 	}
@@ -3451,6 +3487,7 @@ void CBattleInterface::activateStack()
 	activeStack = stackToActivate;
 	stackToActivate = NULL;
 	const CStack *s = activeStack;
+	stackSpells.clear();
 
 	myTurn = true;
 	if(attackerInt && defenderInt) //hotseat -> need to pick which interface "takes over" as active
@@ -3468,8 +3505,18 @@ void CBattleInterface::activateStack()
 
 	//set casting flag to true if creature can use it to not check it every time
 	if (s->casts && s->hasBonus(Selector::type(Bonus::SPELLCASTER) || Selector::type(Bonus::DAEMON_SUMMONING)))
+	{
 		stackCanCastSpell = true;
 
+		TBonusListPtr bl = s->getBonuses(Selector::type(Bonus::SPELLCASTER));
+		BOOST_FOREACH(Bonus * b, *bl)
+		{
+			stackSpells.push_back(CGI->spellh->spells[b->subtype]);
+		}
+		if (stackSpells.size())
+			creatureSpellToCast = stackSpells[111 % stackSpells.size()]; //TODO: randomize? weighted chance?
+	}
+
 	GH.fakeMouseMove();
 
 	if(!pendingAnims.size() && !active)

+ 4 - 1
client/CBattleInterface.h

@@ -5,6 +5,7 @@
 #include <list>
 #include "GUIBase.h"
 #include "../lib/CCreatureSet.h"
+#include "../lib/ConstTransitivePtr.h" //may be reundant
 #include "CAnimation.h"
 
 /*
@@ -419,7 +420,7 @@ class CBattleInterface : public CIntObject
 {
 	enum SpellSelectionType
 	{
-		ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1
+		ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1, STACK_SPELL_CANCELLED = -2
 	};
 private:
 	SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
@@ -456,6 +457,8 @@ private:
 	bool spellDestSelectMode; //if true, player is choosing destination for his spell
 	SpellSelectionType spellSelMode;
 	BattleAction * spellToCast; //spell for which player is choosing destination
+	CSpell * creatureSpellToCast;
+	std::vector< ConstTransitivePtr<CSpell> > stackSpells; //all spells possible to cast by the stack
 	void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled)
 
 	void showAliveStack(const CStack *stack, SDL_Surface * to); //helper function for function show

+ 5 - 5
client/NetPacksClient.cpp

@@ -632,11 +632,6 @@ void StartAction::applyFirstCl( CClient *cl )
 void BattleSpellCast::applyCl( CClient *cl )
 {
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleSpellCast,this);
-
-	if(id >= 66 && id <= 69) //elemental summoning
-	{
-		BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared,GS(cl)->curB->stacks.back());
-	}
 }
 
 void SetStackEffect::applyCl( CClient *cl )
@@ -691,6 +686,11 @@ void BattleStacksRemoved::applyCl( CClient *cl )
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksRemoved, *this);
 }
 
+void BattleStackAdded::applyCl( CClient *cl )
+{
+	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared, GS(cl)->curB->stacks.back());
+}
+
 CGameState* CPackForClient::GS( CClient *cl )
 {
 	return cl->gs;

+ 1 - 1
global.h

@@ -327,7 +327,7 @@ namespace SpellCasting
 	};
 
 	enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
-			MAGIC_MIRROR};
+			MAGIC_MIRROR, CREATURE_ACTIVE_CASTING};
 }
 
 namespace Buildings

+ 2 - 2
lib/BattleAction.h

@@ -22,10 +22,10 @@ struct DLL_EXPORT BattleAction
 	ui32 stackNumber;//stack ID, -1 left hero, -2 right hero,
 	enum ActionType
 	{
-		END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, STACK_HEAL
+		END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE,
+		STACK_HEAL, DAEMON_SUMMONING
 	};
 	si8 actionType; //use ActionType enum for values
-		//10 = Monster casts a spell (i.e. Faerie Dragons)	11 - Bad morale freeze	12 - stacks heals another stack
 	THex destinationTile;
 	si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 41 - 3
lib/BattleState.cpp

@@ -376,6 +376,41 @@ std::vector<THex> BattleInfo::getAccessibility( const CStack * stack, bool addOc
 
 	return ret;
 }
+
+int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos) const
+{
+	int pos;
+	if (initialPos > -1)
+		pos = initialPos;
+	else
+	{
+		if (attackerOwned)
+			pos = 0; //top left
+		else
+			pos = BFIELD_WIDTH; //top right
+	}
+
+	bool ac[BFIELD_SIZE];
+	std::set<THex> occupyable;
+	bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
+	bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING);
+	getAccessibilityMap(ac, twoHex, attackerOwned, true, occupyable, flying);
+	for (int g = pos; -1 < g < BFIELD_SIZE; )
+	{
+		if ((g % BFIELD_WIDTH != 0) && (g % BFIELD_WIDTH != BFIELD_WIDTH-1) && BattleInfo::isAccessible (g, ac, twoHex, attackerOwned, flying, true))
+		{
+			pos = g;
+			break;
+		}
+		if (attackerOwned)
+			++g; //probably some more sophisticated range-based iteration is needed
+		else
+			--g;
+	}
+
+	return pos;
+}
+
 bool BattleInfo::isStackBlocked(const CStack * stack) const
 {
 	if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked
@@ -1776,7 +1811,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastSpell(int player, Spell
 
 	switch (mode)
 	{
-	case SpellCasting::HERO_CASTING:
+		case SpellCasting::HERO_CASTING:
 		{
 			if(castSpells[side] > 0)
 				return SpellCasting::ALREADY_CASTED_THIS_TURN;
@@ -1800,7 +1835,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player,
 	int cside = sides[0] == player ? 0 : 1; //caster's side
 	switch(mode)
 	{
-	case SpellCasting::HERO_CASTING:
+		case SpellCasting::HERO_CASTING:
 		{
 			const CGHeroInstance * caster = heroes[cside];
 			if(!caster->canCastThisSpell(spell))
@@ -1900,7 +1935,10 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int play
 	if(moreGeneralProblem != SpellCasting::OK)
 		return moreGeneralProblem;
 
-	return battleIsImmune(getHero(player), spell, mode, dest);
+	if (mode != SpellCasting::CREATURE_ACTIVE_CASTING)
+		return battleIsImmune(getHero(player), spell, mode, dest);
+	else
+		return battleIsImmune(NULL, spell, mode, dest);
 }
 
 const CGHeroInstance * BattleInfo::getHero( int player ) const

+ 1 - 0
lib/BattleState.h

@@ -87,6 +87,7 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode
 	const CStack * getStackT(THex tileID, bool onlyAlive = true) const;
 	void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set<THex> & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes
 	static bool isAccessible(THex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS
+	int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects
 	void makeBFS(THex start, bool*accessibility, THex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result
 	std::pair< std::vector<THex>, int > getPath(THex start, THex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
 	std::vector<THex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<THex> * attackable = NULL) const; //returns vector of accessible tiles (taking into account the creature range)

+ 12 - 0
lib/IGameCallback.cpp

@@ -107,6 +107,18 @@ SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( con
 	return gs->curB->battleCanCastThisSpell(player, spell, SpellCasting::HERO_CASTING);
 }
 
+SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const CSpell * spell, THex destination)
+{
+	if(!gs->curB)
+	{
+
+		tlog1 << "battleCanCastThisSpell called when there is no battle!\n";
+		return SpellCasting::NO_HERO_TO_CAST_SPELL;
+	}
+
+	return gs->curB->battleCanCastThisSpellHere(player, spell, SpellCasting::CREATURE_ACTIVE_CASTING, destination);
+}
+
 si8 CBattleInfoCallback::battleGetTacticDist()
 {
 	if (!gs->curB)

+ 1 - 0
lib/IGameCallback.h

@@ -104,6 +104,7 @@ public:
 	bool battleCanShoot(const CStack * stack, THex dest); //returns true if unit with id ID can shoot to dest
 	bool battleCanCastSpell(); //returns true, if caller can cast a spell
 	SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description)
+	SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, THex destination); //determines if creature can cast a spell here
 	bool battleCanFlee(); //returns true if caller can flee from the battle
 	int battleGetSurrenderCost(); //returns cost of surrendering battle, -1 if surrendering is not possible
 	const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead

+ 19 - 0
lib/NetPacks.h

@@ -1483,6 +1483,25 @@ struct BattleStacksRemoved : public CPackForClient //3016
 	}
 };
 
+struct BattleStackAdded : public CPackForClient //3017
+{
+	BattleStackAdded(){type = 3017;};
+
+	DLL_EXPORT void applyGs(CGameState *gs);
+	void applyCl(CClient *cl);
+
+	int attacker; // if true, stack belongs to attacker
+	int creID;
+	int amount;
+	int pos;
+	int summoned; //if true, remove it afterwards
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & attacker & creID & amount & pos & summoned;
+	}
+};
+
 struct ShowInInfobox : public CPackForClient //107
 {
 	ShowInInfobox(){type = 107;};

+ 22 - 53
lib/NetPacksLib.cpp

@@ -1019,9 +1019,9 @@ DLL_EXPORT void StartAction::applyGs( CGameState *gs )
 	case BattleAction::WAIT:
 		st->state.insert(WAITING);
 		return;
-	case BattleAction::NO_ACTION: case BattleAction::WALK: case BattleAction::WALK_AND_ATTACK:
-	case BattleAction::SHOOT: case BattleAction::CATAPULT: case BattleAction::MONSTER_SPELL:
-	case BattleAction::BAD_MORALE: case BattleAction::STACK_HEAL:
+	case BattleAction::HERO_SPELL: //no change in current stack state
+		return;
+	default: //any active stack action - attack, catapult, heal, spell...
 		st->state.insert(MOVED);
 		break;
 	}
@@ -1046,7 +1046,8 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
 			spellCost = VLC->spellh->spells[id]->costs[skill];
 		}
 		h->mana -= spellCost;
-		if(h->mana < 0) h->mana = 0;
+		if (h->mana < 0)
+			h->mana = 0;
 	}
 	if(side >= 0 && side < 2 && castedByHero)
 	{
@@ -1069,55 +1070,6 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
 			}
 		}
 	}
-
-	//elemental summoning
-	if(id >= 66 && id <= 69)
-	{
-		int creID;
-		switch(id)
-		{
-		case 66:
-			creID = 114; //fire elemental
-			break;
-		case 67:
-			creID = 113; //earth elemental
-			break;
-		case 68:
-			creID = 115; //water elemental
-			break;
-		case 69:
-			creID = 112; //air elemental
-			break;
-		}
-// 		const int3 & tile = gs->curB->tile;
-// 		TerrainTile::EterrainType ter = gs->map->terrain[tile.x][tile.y][tile.z].tertype;
-
-		int pos; //position of stack on the battlefield - to be calculated
-
-		bool ac[BFIELD_SIZE];
-		std::set<THex> occupyable;
-		bool twoHex = VLC->creh->creatures[creID]->isDoubleWide();
-		bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING);
-		gs->curB->getAccessibilityMap(ac, twoHex, !side, true, occupyable, flying);
-		for(int g=0; g<BFIELD_SIZE; ++g)
-		{
-			if(g % BFIELD_WIDTH != 0 && g % BFIELD_WIDTH != BFIELD_WIDTH-1 && BattleInfo::isAccessible(g, ac, twoHex, !side, flying, true) )
-			{
-				pos = g;
-				break;
-			}
-		}
-
-		int summonedElementals = h->getPrimSkillLevel(2) * VLC->spellh->spells[id]->powers[skill] * (100 + h->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id) / 100.0f); //new feature - percentage bonus
-		CStackInstance *csi = new CStackInstance(creID, summonedElementals); //deleted by d-tor of summoned stack
-		csi->setArmyObj(h);
-		CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), !side, 255, pos);
-		summonedStack->state.insert(SUMMONED);
-		summonedStack->attachTo(csi);
-		summonedStack->postInit();
-		//summonedStack->addNewBonus( makeFeature(HeroBonus::SUMMONED, HeroBonus::ONE_BATTLE, 0, 0, HeroBonus::BONUS_FROM_HERO) );
-		gs->curB->stacks.push_back(summonedStack);
-	}
 }
 
 void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
@@ -1308,6 +1260,23 @@ DLL_EXPORT void BattleStacksRemoved::applyGs( CGameState *gs )
 	}
 }
 
+DLL_EXPORT void BattleStackAdded::applyGs(CGameState *gs)
+{
+	if (!THex(pos).isValid())
+	{
+		tlog2 << "No place found for new stack!\n";
+		return;
+	}
+	CStackInstance *csi = new CStackInstance(creID, amount);
+	csi->setArmyObj(gs->curB->belligerents[attacker ? 0 : 1]);
+	CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), attacker, 255, pos); //TODO: netpacks?
+	if (summoned)
+		summonedStack->state.insert(SUMMONED);
+	summonedStack->attachTo(csi);
+	summonedStack->postInit();
+	gs->curB->stacks.push_back(summonedStack); //the stack is not "SUMMONED", it is permanent
+}
+
 DLL_EXPORT void YourTurn::applyGs( CGameState *gs )
 {
 	gs->currentPlayer = player;

+ 1 - 0
lib/RegisterTypes.cpp

@@ -156,6 +156,7 @@ void registerTypes2(Serializer &s)
 	s.template registerType<ObstaclesRemoved>();
 	s.template registerType<CatapultAttack>();
 	s.template registerType<BattleStacksRemoved>();
+	s.template registerType<BattleStackAdded>();
 	s.template registerType<ShowInInfobox>();
 	s.template registerType<AdvmapSpellCast>();
 	s.template registerType<OpenWindow>();

+ 67 - 1
server/CGameHandler.cpp

@@ -3280,6 +3280,35 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			}
 
 
+			sendAndApply(&end_action);
+			break;
+		}
+		case BattleAction::DAEMON_SUMMONING:
+			//TODO: From Strategija:
+			//Summon Demon is a level 2 spell.
+			//Cloned Pit Lord stack can use the specialty as well.
+		{
+			StartAction start_action(ba);
+			sendAndApply(&start_action);
+
+			CStack *summoner = gs->curB->getStack(ba.stackNumber),
+				*destStack = gs->curB->getStackT(ba.destinationTile, false);
+
+			BattleStackAdded bsa;
+			bsa.attacker = summoner->attackerOwned;
+
+			bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream!
+			ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID);
+			bsa.amount = std::min ((ui32)(risedHp/destStack->MaxHealth()), destStack->baseAmount);
+
+			bsa.pos = gs->curB->getAvaliableHex(bsa.creID, bsa.attacker, destStack->position);
+			bsa.summoned = false;
+
+			BattleStacksRemoved bsr; //remove body
+			bsr.stackIDs.insert(destStack->ID);
+			sendAndApply(&bsr);
+			sendAndApply(&bsa);
+
 			sendAndApply(&end_action);
 			break;
 		}
@@ -3673,6 +3702,41 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati
 				sendAndApply(&shr);
 			break;
 		}
+	case 66:
+	case 67:
+	case 68:
+	case 69:
+		{ //elemental summoning
+			int creID;
+			switch(spellID)
+			{
+				case 66:
+					creID = 114; //fire elemental
+					break;
+				case 67:
+					creID = 113; //earth elemental
+					break;
+				case 68:
+					creID = 115; //water elemental
+					break;
+				case 69:
+					creID = 112; //air elemental
+					break;
+			}
+
+			BattleStackAdded bsa;
+
+			bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
+
+			bsa.amount = caster->getPrimSkillLevel(2) * VLC->spellh->spells[spellID]->powers[spellLvl] *
+				(100 + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID)) / 100.0f; //new feature - percentage bonus
+
+			bsa.creID = creID;
+			bsa.attacker = !(bool)casterSide;
+			bsa.summoned = true;
+			sendAndApply(&bsa);
+		}
+		break;
 	case 64: //remove obstacle
 		{
 			ObstaclesRemoved obr;
@@ -3731,6 +3795,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati
 	sendAndApply(&sc);
 	if(!si.stacks.empty()) //after spellcast info shows
 		sendAndApply(&si);
+
 	//Magic Mirror effect
 	if (spell->positiveness < 0 && mode != SpellCasting::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence
 	{
@@ -3779,7 +3844,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			}
 
 			const CSpell *s = VLC->spellh->spells[ba.additionalInfo];
-			if (s->mainEffectAnim > -1) //TODO: special effects, like Clone
+			if (s->mainEffectAnim > -1 || (s->id >= 66 || s->id <= 69)) //allow summon elementals
+				//TODO: special effects, like Clone
 			{
 				ui8 skill = h->getSpellSchoolLevel(s); //skill level