Sfoglia il codice sorgente

Rewritten handling mouse movement over hex and l-clicking hex into one procedure. That way the tooltip and cursor are always accurate, because they're set by the same routing that selects action. Having that logic duplicated in two methods was unmaintainable. [though the new one is still monstrous...] By the way fixed numerous issues, including:
* #785 and parts of #760
* first aid tent can heal only creatures that suffered damage
* war machines can't be healed by tent
* creatures casting spells won't try to cast them during tactic phase
* console tooltips for first aid tent
* console tooltips for teleport spell
* cursor is reset to pointer when action is requested
* fixed a few other missing or wrong tooltips/cursors

Implemented opening creature window by l-clicking on stack. Master Genie's spell is picked by server, not client.
Minor changes.

Michał W. Urbańczyk 13 anni fa
parent
commit
a9af0da0ab

+ 460 - 604
client/BattleInterface/CBattleInterface.cpp

@@ -912,296 +912,12 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
 }
 void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 {
-	std::string consoleMsg;
-
-	if(activeStack && !spellDestSelectMode)
-	{
-        int lastMouseHoveredStack = mouseHoveredStack;
-		bool stackCastsSpell;
-		mouseHoveredStack = -1;
-		int myNumber = -1; //number of hovered tile
-		for(int g = 0; g < GameConstants::BFIELD_SIZE; ++g)
-		{
-			if(bfield[g].hovered && bfield[g].strictHovered)
-			{
-				myNumber = g;
-				break;
-			}
-		}
-		if(myNumber == -1)
-		{
-			CCS->curh->changeGraphic(1, 6);
-			if(console->whoSetAlter == 0)
-			{
-				console->alterTxt = "";
-			}
-		}
-		else //battlefield hex
-		{
-            if(!vstd::contains(occupyableHexes, myNumber) || activeStack->coversPos(myNumber))
-			{
-				const CStack *shere = curInt->cb->battleGetStackByPos(myNumber, false);
-				const CStack *sactive = activeStack;
-				if(shere)
-				{
-					bool ourStack = shere->owner == curInt->playerID;
-					//determine if creature spell is going to be cast
-					stackCastsSpell = false;
-					if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
-					{
-						if ((int)creatureSpellToCast > -1) //use randomized spell (Faerie Dragon), or only avaliable spell (Archangel)
-						{
-							if (shere != sactive) //can't cast on itself
-							{
-								const CSpell * spell =  CGI->spellh->spells[creatureSpellToCast];
-								if (curInt->cb->battleCanCreatureCastThisSpell(spell, BattleHex(myNumber)) == ESpellCastProblem::OK)
-								{
-									if ((!spell->isNegative() && ourStack) || (!spell->isPositive() && !ourStack))
-									{
-										CCS->curh->changeGraphic(3, 0);
-										stackCastsSpell = true;
-										std::string buf = CGI->generaltexth->allTexts[27]; //cast %s on &s
-										boost::replace_first (buf, "%s", spell->name);
-										boost::replace_first (buf, "%s", shere->getName());
-										console->alterTxt = buf;
-										console->whoSetAlter = 0;
-									}
-								}
-							}
-						}
-						else if (ourStack) //must have only random positive spell (genie)
-						{
-							if (shere != sactive) //can't cast on itself
-							{
-								int spellID = curInt->cb->battleGetRandomStackSpell(shere, CBattleInfoCallback::RANDOM_GENIE);
-								if (spellID > -1) //can cast any spell on target stack
-								{
-									CCS->curh->changeGraphic(3, 0);
-									stackCastsSpell = true;
-									std::string buf = CGI->generaltexth->allTexts[301]; //Cast spell on %s
-									boost::replace_first (buf, "%s", shere->getName().c_str());
-									console->alterTxt = buf;
-									console->whoSetAlter = 0;
-								}
-							}
-						}
-					}
-
-					if(ourStack) //our stack
-					{
-						if (shere->alive())
-						{
-							if (!stackCastsSpell) //use other abilities or display info
-							{
-								if(sactive->hasBonusOfType(Bonus::HEALER))
-								{
-									//display the possibility to heal this creature
-									CCS->curh->changeGraphic(1, 17);
-								}
-								else
-								{
-									//info about creature
-									CCS->curh->changeGraphic(1,5);
-								}
-								//setting console text
-								consoleMsg += CGI->generaltexth->allTexts[297];
-								boost::replace_first (consoleMsg, "%s", shere->getName());
-								console->alterText (consoleMsg);
-								console->whoSetAlter = 0;
-								const time_t curTime = time(NULL);
-								if (shere->ID != lastMouseHoveredStack &&
-								   curTime > lastMouseHoveredStackAnimationTime + HOVER_ANIM_DELTA &&
-								   creAnims[shere->ID]->getType() == CCreatureAnim::HOLDING &&
-								   creAnims[shere->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
-								{
-									creAnims[shere->ID]->playOnce(CCreatureAnim::MOUSEON);
-									lastMouseHoveredStackAnimationTime = curTime;
-								}
-							}
-						} //end of alive
-						else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING) && sactive->casts)
-						{
-							CCS->curh->changeGraphic(3, 0);
-						}
-						mouseHoveredStack = shere->ID; //for dead also?
-					}
-					//end of our stack
-					else if (!stackCastsSpell) //if not, then try attack
-					{
-						if (curInt->cb->battleCanShoot(activeStack,myNumber)) //we can shoot enemy
-						{
-							if(curInt->cb->battleHasDistancePenalty(activeStack, myNumber) ||
-								curInt->cb->battleHasWallPenalty(activeStack, myNumber))
-							{
-								CCS->curh->changeGraphic(1,15);
-							}
-							else
-							{
-								CCS->curh->changeGraphic(1,3);
-							}
-							//calculating estimated dmg
-							std::pair<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere);
-							std::string estDmg;
-							estDmg += boost::lexical_cast<std::string>(estimatedDmg.first) += " - ";
-							estDmg += boost::lexical_cast<std::string>(estimatedDmg.second);
-							//printing
-							consoleMsg += CGI->generaltexth->allTexts[296];
-							boost::replace_first (consoleMsg, "%s", shere->getName());
-							boost::replace_first (consoleMsg, "%d", boost::lexical_cast<std::string>(sactive->shots));
-							boost::replace_first (consoleMsg, "%s", estDmg);
-							console->alterText (consoleMsg);
-							console->whoSetAlter = 0;
-						}
-						else if (shere->alive() && isTileAttackable(myNumber)) //available enemy (melee attackable)
-						{
-							//handle direction of cursor and attackable tile
-							setBattleCursor(myNumber);
-
-							//calculating estimated dmg
-							std::pair<ui32, ui32> estimatedDmg = curInt->cb->battleEstimateDamage(sactive, shere);
-							std::string estDmg;
-							estDmg += boost::lexical_cast<std::string>(estimatedDmg.first) += " - ";
-							estDmg += boost::lexical_cast<std::string>(estimatedDmg.second);
-							//printing
-							consoleMsg += CGI->generaltexth->allTexts[36];
-							boost::replace_first (consoleMsg, "%s", shere->getName());
-							boost::replace_first (consoleMsg, "%s", estDmg);
-							console->alterText (consoleMsg);
-							console->whoSetAlter = 0;
-						}
-						else //unavailable enemy
-						{
-							CCS->curh->changeGraphic (1,0);
-							console->alterTxt = "";
-							console->whoSetAlter = 0;
-						}
-					}
-				} //end of stack
-				//TODO: allow aiming for creature spells
-				else if (sactive && sactive->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(myNumber)) //catapulting
-				{
-					CCS->curh->changeGraphic(1,16);
-					console->alterTxt = "";
-					console->whoSetAlter = 0;
-				}
-				else //empty unavailable tile
-				{
-					CCS->curh->changeGraphic(1,0);
-					console->alterTxt = "";
-					console->whoSetAlter = 0;
-				}
-			}
-			else //available tile
-			{
-				//setting console text and cursor
-				if (activeStack) //there can be a moment when stack is dead ut next is not yet activated
-				{
-					if(activeStack->hasBonusOfType(Bonus::FLYING))
-					{
-						CCS->curh->changeGraphic(1,2);
-						consoleMsg += CGI->generaltexth->allTexts[295]; //Fly %s here
-					}
-					else
-					{
-						CCS->curh->changeGraphic(1,1);
-						consoleMsg += CGI->generaltexth->allTexts[294]; //Move %s here
-						
-					}
-					boost::replace_first (consoleMsg, "%s", activeStack->getName());
-					console->alterText(consoleMsg);
-					console->whoSetAlter = 0;
-				}
-			}
-		}
-	}
-	else if (spellDestSelectMode)
+	auto hexItr = std::find_if(bfield.begin(), bfield.end(), [](const CClickableHex &hex)
 	{
-		int myNumber = -1; //number of hovered tile
-		for(int g=0; g<GameConstants::BFIELD_SIZE; ++g)
-		{
-			if(bfield[g].hovered && bfield[g].strictHovered)
-			{
-				myNumber = g;
-				break;
-			}
-		}
-		if(myNumber == -1)
-		{
-			CCS->curh->changeGraphic(1, 0);
-			//setting console text
-			console->alterTxt = CGI->generaltexth->allTexts[23];
-			console->whoSetAlter = 0;
-		}
-		else
-		{
-			//get dead stack if we cast resurrection or animate dead
-			const CStack * stackUnder = curInt->cb->battleGetStackByPos(myNumber, vstd::contains(CGI->spellh->risingSpells, spellToCast->additionalInfo));
-
-			if(stackUnder && spellToCast->additionalInfo == 39 && !stackUnder->hasBonusOfType(Bonus::UNDEAD)) //animate dead can be cast only on undead creatures
-				stackUnder = NULL;
-
-			bool potentialTargetStack; //for cases 1, 2 and 3
-			switch (spellSelMode)
-			{
-					case SpellSelectionType::FRIENDLY_CREATURE:
-						potentialTargetStack = stackUnder != NULL && curInt->playerID == stackUnder->owner;
-						break;
-					case SpellSelectionType::HOSTILE_CREATURE:
-						potentialTargetStack = stackUnder != NULL && curInt->playerID != stackUnder->owner;
-						break;
-					case SpellSelectionType::ANY_CREATURE:
-						potentialTargetStack = stackUnder != NULL;
-						break;
-			}
+		return hex.hovered && hex.strictHovered;
+	});
 
-			switch(spellSelMode)
-			{
-			case SpellSelectionType::ANY_LOCATION:
-					CCS->curh->changeGraphic(3, 0);
-					//setting console text
-					consoleMsg += CGI->generaltexth->allTexts[26];
-					boost::replace_first (consoleMsg, "%s", CGI->spellh->spells[spellToCast->additionalInfo]->name);
-					console->alterText (consoleMsg);
-					console->whoSetAlter = 0;
-					break;
-				case SpellSelectionType::FRIENDLY_CREATURE:
-				case SpellSelectionType::HOSTILE_CREATURE:
-				case SpellSelectionType::ANY_CREATURE:
-					if( potentialTargetStack )
-					{
-						if (curInt->cb->battleCanCastThisSpell (CGI->spellh->spells[spellToCast->additionalInfo], BattleHex(myNumber)))
-							CCS->curh->changeGraphic(1, 0);
-						else
-							CCS->curh->changeGraphic(3, 0);
-						//setting console text
-						consoleMsg += CGI->generaltexth->allTexts[27];
-						boost::replace_first (consoleMsg, "%s", CGI->spellh->spells[spellToCast->additionalInfo]->name);
-						boost::replace_first (consoleMsg, "%s", stackUnder->getName());
-						console->alterText (consoleMsg);
-						console->whoSetAlter = 0;
-						break;
-					}
-					else
-					{
-						CCS->curh->changeGraphic(1, 0);
-						//setting console text
-						console->alterTxt = CGI->generaltexth->allTexts[23];
-						console->whoSetAlter = 0;
-					}
-					break;
-				case SpellSelectionType::OBSTACLE: //TODO: implement this case
-					if ( blockedByObstacle(myNumber) )
-					{
-						CCS->curh->changeGraphic(3, 0);
-					}
-					else
-					{
-						CCS->curh->changeGraphic(1, 0);
-					}
-					break;
-			}
-		}
-	}
+	handleHex(hexItr == bfield.end() ? -1 : hexItr->myNumber, MOVE);
 }
 
 void CBattleInterface::setBattleCursor(const int myNumber)
@@ -1697,19 +1413,6 @@ bool CBattleInterface::isTileAttackable(const BattleHex & number) const
 	return false;
 }
 
-bool CBattleInterface::blockedByObstacle(BattleHex hex) const
-{
-	std::vector<CObstacleInstance> obstacles = curInt->cb->battleGetAllObstacles();
-	std::set<BattleHex> coveredHexes;
-	for(size_t b = 0; b < obstacles.size(); ++b)
-	{
-		std::vector<BattleHex> blocked = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getBlocked(obstacles[b].pos);
-		for(size_t w = 0; w < blocked.size(); ++w)
-			coveredHexes.insert(blocked[w]);
-	}
-	return vstd::contains(coveredHexes, hex);
-}
-
 bool CBattleInterface::isCatapultAttackable(BattleHex hex) const
 {
 	if(!siegeH)
@@ -1740,307 +1443,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero()
 
 void CBattleInterface::hexLclicked(int whichOne)
 {
-	const CStack * actSt = activeStack;
-	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";
-		return;
-	}
-
-	if( ((whichOne%GameConstants::BFIELD_WIDTH)!=0 && (whichOne%GameConstants::BFIELD_WIDTH)!=(GameConstants::BFIELD_WIDTH-1)) //if player is trying to attack enemey unit or move creature stack
-		|| ((actSt->hasBonusOfType(Bonus::CATAPULT) && !spellDestSelectMode) || dest ) //enemy's first aid tent can stand there and we want to shoot it
-		)
-	{
-		if(!myTurn)
-			return; //we are not permit to do anything
-		if(spellDestSelectMode) //select spell target //TODO: choose target for area creature spell
-		{
-			//checking destination
-			bool allowCasting = true;
-			//bool onlyAlive = vstd::contains(CGI->spellh->risingSpells, spellToCast->additionalInfo); //when casting resurrection or animate dead we should be allow to select dead stack
-			//TODO: more general handling of dead targets
-			switch(spellSelMode)
-			{
-			case FRIENDLY_CREATURE:
-			case HOSTILE_CREATURE:
-			case ANY_CREATURE:
-				if (curInt->cb->battleCanCastThisSpell (CGI->spellh->spells[spellToCast->additionalInfo], BattleHex(whichOne)) != ESpellCastProblem::OK)
-					allowCasting = false;
-				break;
-			case OBSTACLE:
-				if(!blockedByObstacle(whichOne))
-					allowCasting = false;
-			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))
-				{
-					allowCasting = false;
-				}
-				break;
-			}
-			//destination checked
-			if(allowCasting)
-			{
-				spellToCast->destinationTile = whichOne;
-				curInt->cb->battleMakeAction(spellToCast);
-				endCastingSpell();
-			}
-		}
-		else //creature casting
-		{
-			bool walkableTile = false;
-			bool spellCast = false;
-			if (dest)
-			{
-				bool ourStack = actSt->owner == dest->owner;
-
-				//try to cast stack spell first
-				if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell
-				{
-					if (dest != actSt) //can't cast on itself
-					{
-						if ((int)creatureSpellToCast > -1) //use randomized spell (Faerie Dragon), or only avaliable spell (Archangel)
-						{
-							const CSpell * spell =  CGI->spellh->spells[creatureSpellToCast];
-
-							if (curInt->cb->battleCanCreatureCastThisSpell(spell, BattleHex(whichOne)) == ESpellCastProblem::OK)
-							{
-								if ((!spell->isNegative() && ourStack) || (!spell->isPositive() && !ourStack))
-								{
-									giveCommand(BattleAction::MONSTER_SPELL, whichOne, actSt->ID, creatureSpellToCast);
-									spellCast = true;
-								}
-							}
-						}
-						else if (ourStack) //must have only random positive spell (genie)
-						{
-							int spellID = curInt->cb->battleGetRandomStackSpell(dest, CBattleInfoCallback::RANDOM_GENIE);
-							if (spellID > -1) //can cast any spell on target stack
-							{
-								giveCommand(BattleAction::MONSTER_SPELL, whichOne, actSt->ID, spellID); //use randomized spell
-								spellCast = true;
-							}
-						}
-					}
-					if (spellCast)
-					{
-						creatureSpellToCast = -1;
-						return; //no further action after cast
-					}
-				}
-
-				if (dest->alive())
-				{
-					if(dest->owner != actSt->owner && curInt->cb->battleCanShoot(activeStack, whichOne)) //shooting
-					{
-						CCS->curh->changeGraphic(1, 6); //cursor should be changed
-						giveCommand (BattleAction::SHOOT, whichOne, activeStack->ID);
-					}
-					else if(!ourStack) //attacking
-					{
-						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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::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<BattleHex> 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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH+1 : GameConstants::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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::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<BattleHex> 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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::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/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::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::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(BattleAction::DAEMON_SUMMONING, whichOne, activeStack->ID);
-
-					CCS->curh->changeGraphic(1, 6); //cursor should be changed
-				}
-				else //not a subject of resurrection
-					walkableTile = true;
-			}
-			else
-			{
-				walkableTile = true;
-			}
-
-			if (walkableTile) // we can try to move to this tile
-			{
-				if(std::find(occupyableHexes.begin(), occupyableHexes.end(), whichOne) != occupyableHexes.end())// and it's in our range
-				{
-					CCS->curh->changeGraphic(1, 6); //cursor should be changed
-					if(activeStack->doubleWide())
-					{
-						std::vector<BattleHex> 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);
-					}
-					else
-					{
-						giveCommand(BattleAction::WALK, whichOne, activeStack->ID);
-					}
-				}
-				else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult)
-				{
-					giveCommand(BattleAction::CATAPULT, whichOne, activeStack->ID);
-				}
-			}
-		}
-	}
+	handleHex(whichOne, LCLICK);
 }
 
 void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
@@ -2505,11 +1908,22 @@ void CBattleInterface::activateStack()
 	bFlee->block(!curInt->cb->battleCanFlee());
 	bSurrender->block(curInt->cb->battleGetSurrenderCost() < 0);
 
+
 	//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::RANDOM_SPELLCASTER)))
+	const Bonus *spellcaster = s->getBonus(Selector::type(Bonus::SPELLCASTER)),
+		*randomSpellcaster = s->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
+	if (s->casts &&  (spellcaster || randomSpellcaster))
 	{
 		stackCanCastSpell = true;
-		creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
+		if(randomSpellcaster)
+			creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
+		else
+			creatureSpellToCast = spellcaster->subtype;
+
+		if(creatureSpellToCast < 0) //TODO proper way of detecting casters of positive spells
+			spellSelMode = FRIENDLY_CREATURE;
+		else
+			spellSelMode = selectionTypeByPositiveness(*CGI->spellh->spells[creatureSpellToCast]);
 	}
 	else
 	{
@@ -3100,6 +2514,448 @@ CBattleInterface::SpellSelectionType CBattleInterface::selectionTypeByPositivene
 	return NO_LOCATION; //should never happen
 }
 
+std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
+{
+	if(dmgRange.first != dmgRange.second)
+		return (boost::format("%d - %d") % dmgRange.first % dmgRange.second).str();
+	else
+		return (boost::format("%d") % dmgRange.first).str();
+}
+
+void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
+{
+	if(!myTurn) //we are not permit to do anything
+		return; 
+
+	// This function handles mouse move over hexes and l-clicking on them. 
+	// First we decide what happens if player clicks on this hex and set appropriately
+	// consoleMsg, cursorFrame/Type and prepare lambda realizeAction.
+	// 
+	// Then, depending whether it was hover/click we either call the action or set tooltip/cursor.
+
+	//used when hovering -> tooltip message and cursor to be set
+	std::string consoleMsg;
+	bool setCursor = true; //if we want to suppress setting cursor
+	int cursorType = ECursor::COMBAT, cursorFrame = ECursor::COMBAT_POINTER;
+	
+	//used when l-clicking -> action to be called upon the click
+	std::function<void()> realizeAction;
+
+	//helper lambda that appropriately realizes action / sets cursor and tooltip
+	auto realizeThingsToDo = [&]()
+	{
+		if(eventType == MOVE)
+		{
+			if(setCursor)
+				CCS->curh->changeGraphic(cursorType, cursorFrame);
+			this->console->alterText(consoleMsg);
+			this->console->whoSetAlter = 0;
+		}
+		if(eventType == LCLICK && realizeAction)
+		{
+			realizeAction();
+			CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
+			this->console->alterText("");
+		}
+	};
+
+	const CStack * const shere = curInt->cb->battleGetStackByPos(myNumber, false);
+	const CStack * const sactive = activeStack;
+
+	if(!sactive)
+		return;
+
+	bool creatureCasting = !spellDestSelectMode && stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED
+							&& shere != sactive;
+
+	bool noStackIsHovered = true; //will cause removing a blue glow
+
+	//handle spellcasting (by hero or creature)
+	if(!tacticsMode && (spellDestSelectMode || creatureCasting))
+	{
+		bool isCastingPossible = true;
+
+		int spellID = -1;
+		if(creatureCasting)
+		{
+			if(creatureSpellToCast > -1)
+				spellID = creatureSpellToCast;
+			else
+			{
+				if(!shere || curInt->cb->battleGetRandomStackSpell(shere, CBattleInfoCallback::RANDOM_GENIE)< 0) //no possible spell for this dest
+					goto pastCastingSpells; //behave as if stack were not a caster
+			}
+		}
+		else if(spellDestSelectMode) //hero casting
+			spellID  = spellToCast->additionalInfo;
+
+		const CSpell *sp = NULL;
+		if(spellID >= 0) 
+			sp = CGI->spellh->spells[spellID];
+		if(!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
+			isCastingPossible = false;
+
+
+		const ui8 skill = sp ? getActiveHero()->getSpellSchoolLevel(sp) : 0; //skill level 
+		//TODO master genies cast on adv level, that was somewhere in bonuses
+		if(sp)
+		{
+			if(creatureCasting)
+				isCastingPossible = (curInt->cb->battleCanCreatureCastThisSpell(sp, myNumber) == ESpellCastProblem::OK);
+			else
+				isCastingPossible = (curInt->cb->battleCanCastThisSpell(sp, myNumber) == ESpellCastProblem::OK);
+		}
+		else if(shere)
+		{
+			//needed for genie, otherwise covered by battleCan*CastThisSpell
+			if(spellSelMode == HOSTILE_CREATURE && shere->owner == sactive->owner
+				|| spellSelMode == FRIENDLY_CREATURE && shere->owner != sactive->owner)
+			{
+				isCastingPossible = false;
+			}
+		}
+
+		switch(spellSelMode)
+		{
+		case FRIENDLY_CREATURE:
+		case HOSTILE_CREATURE:
+		case ANY_CREATURE:
+			if(isCastingPossible && shere)
+			{
+				if(sp)
+					consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
+				else
+					consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %s
+			}
+			if(!shere) //there must be a creature as target
+				isCastingPossible = false;
+			break;
+		case OBSTACLE:
+			if(isCastingPossible)
+				consoleMsg = CGI->generaltexth->allTexts[550]; //Remove this obstacle
+			break;
+		case TELEPORT: //teleport
+			if (!curInt->cb->battleCanTeleportTo(activeStack, myNumber, skill))
+				isCastingPossible = false;
+
+			if(!isCastingPossible)
+				consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
+			else
+				consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
+
+			break;
+		}
+		//destination checked
+		if(isCastingPossible)
+		{
+			cursorType = ECursor::SPELLBOOK;
+			cursorFrame = 0;
+			if(consoleMsg.empty() && sp)
+				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
+
+			realizeAction = [=]
+			{
+				if(creatureCasting)
+				{
+					giveCommand(BattleAction::MONSTER_SPELL, myNumber, sactive->ID, creatureSpellToCast);
+				}
+				else
+				{
+					spellToCast->destinationTile = myNumber;
+					curInt->cb->battleMakeAction(spellToCast);
+					endCastingSpell();
+				}
+			};
+		}
+		else if(creatureCasting) //if creature can't cast on dest tile, we act as if it were not a caster
+		{
+			goto pastCastingSpells;
+		}
+		else
+		{
+			cursorFrame = ECursor::COMBAT_BLOCKED;
+			consoleMsg = CGI->generaltexth->allTexts[23];
+		}
+
+		//consume our settings and do not look further
+		realizeThingsToDo();
+		return;
+	}
+
+pastCastingSpells:
+
+	if(!vstd::contains(occupyableHexes, myNumber) || activeStack->coversPos(myNumber))
+	{
+		if(shere)
+		{
+			bool ourStack = shere->owner == curInt->playerID;
+
+			if(ourStack) //our stack
+			{
+				if (shere->alive())
+				{
+					if(sactive->hasBonusOfType(Bonus::HEALER) && shere->canBeHealed()) //heal
+					{
+						cursorFrame = ECursor::COMBAT_HEAL;
+						consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
+						realizeAction = [=]{ giveCommand(BattleAction::STACK_HEAL, myNumber, activeStack->ID); }; //command healing
+					}
+					else //info about creature
+					{
+						cursorFrame = ECursor::COMBAT_QUERY;
+						consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
+						realizeAction = [=]{ GH.pushInt(createCreWindow(shere, true)); };
+					}
+					//setting console text
+					const time_t curTime = time(NULL);
+					CCreatureAnimation *hoveredStackAnim = creAnims[shere->ID];
+
+					if (shere->ID != mouseHoveredStack
+						&& curTime > lastMouseHoveredStackAnimationTime + HOVER_ANIM_DELTA
+						&& hoveredStackAnim->getType() == CCreatureAnim::HOLDING 
+						&& hoveredStackAnim->framesInGroup(CCreatureAnim::MOUSEON) > 0)
+					{
+						hoveredStackAnim->playOnce(CCreatureAnim::MOUSEON);
+						lastMouseHoveredStackAnimationTime = curTime;
+						mouseHoveredStack = shere->ID;
+						noStackIsHovered = false;
+					}
+				} //end of alive
+				else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING) && sactive->casts)
+				{
+					cursorType = ECursor::SPELLBOOK;
+					realizeAction = [=]{ giveCommand(BattleAction::DAEMON_SUMMONING, myNumber, activeStack->ID); };
+				}
+			}
+			//end of our stack hovered
+			else if(curInt->cb->battleCanShoot(activeStack,myNumber)) //we can shoot enemy
+			{
+				if(curInt->cb->battleHasShootingPenalty(activeStack, myNumber))
+					cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
+				else
+					cursorFrame = ECursor::COMBAT_SHOOT;
+
+				realizeAction = [=] {giveCommand(BattleAction::SHOOT, myNumber, activeStack->ID);};
+				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(sactive, shere)); //calculating estimated dmg
+				//printing - Shoot %s (%d shots left, %s damage)
+				consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots % estDmgText).str();
+			}
+			else if (shere->alive() && isTileAttackable(myNumber)) //available enemy (melee attackable)
+			{
+				setBattleCursor(myNumber); //handle direction of cursor and attackable tile
+				setCursor = false; //don't overwrite settings from the call above
+				realizeAction = [=]
+				{
+					BattleHex attackFromHex = fromWhichHexAttack(myNumber);
+					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, myNumber);
+					}
+				};
+
+				std::string estDmgText = formatDmgRange(curInt->cb->battleEstimateDamage(sactive, shere)); //calculating estimated dmg
+				consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
+			}
+			else //unavailable enemy
+			{
+				cursorFrame = ECursor::COMBAT_BLOCKED;
+			}
+		} //end of stack on dest
+		else if (sactive && sactive->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(myNumber)) //"catapulting"
+		{
+			cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
+			realizeAction = [=]{ giveCommand(BattleAction::CATAPULT, myNumber, activeStack->ID); };
+		}
+		else //empty unavailable tile
+		{
+			cursorFrame = ECursor::COMBAT_BLOCKED;
+		}
+	}
+	else //occuppiable tile
+	{
+		if (activeStack) //there can be a moment when stack is dead ut next is not yet activated
+		{
+			if(activeStack->hasBonusOfType(Bonus::FLYING))
+			{
+				cursorFrame = ECursor::COMBAT_FLY;
+				consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % activeStack->getName()).str(); //Fly %s here
+			}
+			else
+			{
+				cursorFrame = ECursor::COMBAT_MOVE;
+				consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % activeStack->getName()).str(); //Move %s here
+			}
+
+			realizeAction = [=]
+			{
+				if(activeStack->doubleWide())
+				{
+					std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+					int shiftedDest = myNumber + (activeStack->attackerOwned ? 1 : -1);
+					if(vstd::contains(acc, myNumber))
+						giveCommand (BattleAction::WALK ,myNumber, activeStack->ID);
+					else if(vstd::contains(acc, shiftedDest))
+						giveCommand (BattleAction::WALK, shiftedDest, activeStack->ID);
+				}
+				else
+				{
+					giveCommand(BattleAction::WALK, myNumber, activeStack->ID);
+				}
+			};
+		}
+	}
+
+	realizeThingsToDo();
+	if(noStackIsHovered)
+		mouseHoveredStack = -1;
+}
+
+BattleHex CBattleInterface::fromWhichHexAttack(BattleHex myNumber)
+{
+	//TODO far too much repeating code
+	BattleHex destHex = -1;
+	switch(CCS->curh->number)
+	{
+	case 12: //from bottom right
+		{
+			bool doubleWide = activeStack->doubleWide();
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
+				(activeStack->attackerOwned && doubleWide ? 1 : 0);
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(activeStack->attackerOwned) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 7: //from bottom left
+		{
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(activeStack->attackerOwned) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 8: //from left
+		{
+			if(activeStack->doubleWide() && !activeStack->attackerOwned)
+			{
+				std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+				if(vstd::contains(acc, myNumber))
+					return myNumber - 1;
+				else
+					return myNumber - 2;
+			}
+			else
+			{
+				return myNumber - 1;
+			}
+			break;
+		}
+	case 9: //from top left
+		{
+			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH+1 : GameConstants::BFIELD_WIDTH );
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(activeStack->attackerOwned) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 10: //from top right
+		{
+			bool doubleWide = activeStack->doubleWide();
+			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
+				(activeStack->attackerOwned && doubleWide ? 1 : 0);
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(activeStack->attackerOwned) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 11: //from right
+		{
+			if(activeStack->doubleWide() && activeStack->attackerOwned)
+			{
+				std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack, false);
+				if(vstd::contains(acc, myNumber))
+					return myNumber + 1;
+				else
+					return myNumber + 2;
+			}
+			else
+			{
+				return myNumber + 1;
+			}
+			break;
+		}
+	case 13: //from bottom
+		{
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 14: //from top
+		{
+			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	}
+}
 std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"};
 
 CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)

+ 5 - 3
client/BattleInterface/CBattleInterface.h

@@ -130,7 +130,7 @@ private:
 	bool spellDestSelectMode; //if true, player is choosing destination for his spell
 	SpellSelectionType spellSelMode;
 	BattleAction * spellToCast; //spell for which player is choosing destination
-	ui32 creatureSpellToCast;
+	si32 creatureSpellToCast;
 	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
@@ -144,7 +144,6 @@ private:
 	void projectileShowHelper(SDL_Surface * to); //prints projectiles present on the battlefield
 	void giveCommand(ui8 action, BattleHex tile, ui32 stack, si32 additional=-1);
 	bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
-	bool blockedByObstacle(BattleHex hex) const;
 	bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
 
 	std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
@@ -243,12 +242,15 @@ public:
 	void castThisSpell(int spellID); //called when player has chosen a spell from spellbook
 	void displayEffect(ui32 effect, int destTile); //displays effect of a spell on the battlefield; affected: true - attacker. false - defender
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
-	void setBattleCursor(const int myNumber); //really complex and messy
+	void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
 	void endAction(const BattleAction* action);
 	void hideQueue();
 	void showQueue();
 	SpellSelectionType selectionTypeByPositiveness(const CSpell & spell);
 
+	void handleHex(BattleHex myNumber, int eventType);
+
+	BattleHex fromWhichHexAttack(BattleHex myNumber);
 	
 	friend class CPlayerInterface;
 	friend class CAdventureMapButton;

+ 8 - 5
client/CCreatureWindow.cpp

@@ -527,9 +527,6 @@ CCreInfoWindow::CCreInfoWindow(const CStackInstance &stack, bool LClicked, boost
 
 			dismiss = new CAdventureMapButton("", CGI->generaltexth->zelp[445].second, dialog, 21, 237, "IVIEWCR2",SDLK_d);
 		}
-
-		ok = new CAdventureMapButton("", CGI->generaltexth->zelp[445].second,
-			boost::bind(&CCreInfoWindow::close,this), 216, 237, "IOKAY.DEF", SDLK_RETURN);
 	}
 }
 
@@ -612,9 +609,15 @@ void CCreInfoWindow::init(const CCreature *creature, const CBonusSystemNode *sta
 	luck->set(stackNode);
 
 	if(!LClicked)
+	{
 		abilityText = new CLabel(17, 231, FONT_SMALL, TOPLEFT, Colors::Cornsilk, creature->abilityText);
+	}
 	else
+	{
 		abilityText = NULL;
+		ok = new CAdventureMapButton("", CGI->generaltexth->zelp[445].second,
+			boost::bind(&CCreInfoWindow::close,this), 216, 237, "IOKAY.DEF", SDLK_RETURN);
+	}
 
 	//if we are displying window fo r stack in battle, there are several more things that we need to display
 	if(const CStack *battleStack = dynamic_cast<const CStack*>(stackNode))
@@ -639,10 +642,10 @@ void CCreInfoWindow::clickRight(tribool down, bool previousState)
 	close();
 }
 
-CIntObject * createCreWindow(const CStack *s)
+CIntObject * createCreWindow(const CStack *s, bool lclick/* = false*/)
 {
 	if(settings["general"]["classicCreatureWindow"].Bool())
-		return new CCreInfoWindow(*s);
+		return new CCreInfoWindow(*s, lclick);
 	else
 		return new CCreatureWindow(*s, CCreatureWindow::BATTLE);
 }

+ 1 - 1
client/CCreatureWindow.h

@@ -130,6 +130,6 @@ public:
 	void show(SDL_Surface * to);
 };
 
-CIntObject *createCreWindow(const CStack *s);
+CIntObject *createCreWindow(const CStack *s, bool lclick = false);
 CIntObject *createCreWindow(int Cid, int Type, int creatureCount);
 CIntObject *createCreWindow(const CStackInstance *s, int type, boost::function<void()> Upg = 0, boost::function<void()> Dsm = 0, UpgradeInfo *ui = NULL);

+ 11 - 0
client/UIFramework/CCursorHandler.h

@@ -16,6 +16,17 @@ struct SDL_Surface;
  *
  */
 
+namespace ECursor
+{
+	enum ECursorTypes { ADVENTURE, COMBAT, DEFAULT, SPELLBOOK };
+
+	enum EBattleCursors { COMBAT_BLOCKED, COMBAT_MOVE, COMBAT_FLY, COMBAT_SHOOT,
+						COMBAT_HERO, COMBAT_QUERY, COMBAT_POINTER, 
+						//various attack frames
+						COMBAT_SHOOT_PENALTY = 15, COMBAT_SHOOT_CATAPULT, COMBAT_HEAL,
+						COMBAT_SACRIFICE, COMBAT_TELEPORT};
+}
+
 /// handles mouse cursor
 class CCursorHandler 
 {

+ 5 - 0
lib/BattleHex.cpp

@@ -82,4 +82,9 @@ void BattleHex::checkAndPush(int tile, std::vector<BattleHex> & ret)
 	if( tile>=0 && tile<GameConstants::BFIELD_SIZE && (tile%GameConstants::BFIELD_WIDTH != (GameConstants::BFIELD_WIDTH - 1)) 
 		&& (tile%GameConstants::BFIELD_WIDTH != 0) )
 		ret.push_back(BattleHex(tile));
+}
+
+bool BattleHex::isAvailable() const
+{
+	return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH-1;
 }

+ 1 - 0
lib/BattleHex.h

@@ -106,4 +106,5 @@ struct DLL_LINKAGE BattleHex
 	}
 	static void checkAndPush(int tile, std::vector<BattleHex> & ret);
 
+	bool isAvailable() const; //valid position not in first or last column
 };

+ 44 - 0
lib/BattleState.cpp

@@ -1948,6 +1948,31 @@ ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int
 	if(moreGeneralProblem != ESpellCastProblem::OK)
 		return moreGeneralProblem;
 
+	if(spell->getTargetType() == CSpell::OBSTACLE  &&  !isObstacleOnTile(dest))
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+
+
+	//get dead stack if we cast resurrection or animate dead
+	const CStack * stackUnder = getStackT(dest, false);
+
+	if(spell->isRisingSpell())
+	{
+		if(!stackUnder || stackUnder->alive())
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->id == Spells::ANIMATE_DEAD  &&  !stackUnder->hasBonusOfType(Bonus::UNDEAD))
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+	if(spell->getTargetType() == CSpell::CREATURE)
+	{
+		if(!stackUnder)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->isNegative() && stackUnder->owner == player)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(spell->isPositive() && stackUnder->owner != player)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+
 	if (mode != ECastingMode::CREATURE_ACTIVE_CASTING && mode != ECastingMode::ENCHANTER_CASTING)
 		return battleIsImmune(getHero(player), spell, mode, dest);
 	else
@@ -2333,6 +2358,18 @@ int BattleInfo::getIdForNewStack() const
 	return 0;
 }
 
+bool BattleInfo::isObstacleOnTile(BattleHex tile) const
+{
+	std::set<BattleHex> coveredHexes;
+	BOOST_FOREACH(const CObstacleInstance &obs, obstacles)
+	{
+		std::vector<BattleHex> blocked = VLC->heroh->obstacles.find(obs.ID)->second.getBlocked(obs.pos);
+		for(size_t w = 0; w < blocked.size(); ++w)
+			coveredHexes.insert(blocked[w]);
+	}
+	return vstd::contains(coveredHexes, tile);
+}
+
 CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S)
 	: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),   
 	counterAttacks(1)
@@ -2861,6 +2898,13 @@ bool CStack::isValidTarget(bool allowDead/* = false*/) const /*alive non-turret
 	return (alive() || allowDead) && position.isValid();
 }
 
+bool CStack::canBeHealed() const
+{
+	return firstHPleft != MaxHealth()
+		&& alive()
+		&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
+}
+
 bool CMP_stack::operator()( const CStack* a, const CStack* b )
 {
 	switch(phase)

+ 2 - 0
lib/BattleState.h

@@ -96,6 +96,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode
 	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex 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<BattleHex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL) const; //returns vector of accessible tiles (taking into account the creature range)
 
+	bool isObstacleOnTile(BattleHex tile) const;
 	bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
 
 	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
@@ -188,6 +189,7 @@ public:
 	bool ableToRetaliate() const; //if stack can retaliate after attacked
 	bool moved(int turn = 0) const; //if stack was already moved this turn
 	bool canMove(int turn = 0) const; //if stack can move
+	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
 	ui32 Speed(int turn = 0, bool useBind = false) const; //get speed of creature with all modificators
 	si32 magicResistance() const; //include aura of resistance
 	static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);

+ 5 - 0
lib/CSpellHandler.cpp

@@ -215,6 +215,11 @@ bool CSpell::isNegative() const
 	return positiveness == NEGATIVE;
 }
 
+bool CSpell::isRisingSpell() const
+{
+	return vstd::contains(VLC->spellh->risingSpells, id);
+}
+
 static bool startsWithX(const std::string &s)
 {
 	return s.size() && s[0] == 'x';

+ 1 - 0
lib/CSpellHandler.h

@@ -44,6 +44,7 @@ public:
 
 	bool isPositive() const;
 	bool isNegative() const;
+	bool isRisingSpell() const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 13 - 6
lib/IGameCallback.cpp

@@ -133,7 +133,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCreatureCastT
 	return gs->curB->battleCanCastThisSpellHere(player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination);
 }
 
-TSpell CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode)
+si32 CBattleInfoCallback::battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode)
 {
 	switch (mode)
 	{
@@ -199,11 +199,11 @@ int CBattleInfoCallback::battleGetBattlefieldType()
 	return gs->curB->battlefieldType;
 }
 
-int CBattleInfoCallback::battleGetObstaclesAtTile(BattleHex tile) //returns bitfield 
-{
-	//TODO - write
-	return -1;
-}
+// int CBattleInfoCallback::battleGetObstaclesAtTile(BattleHex tile) //returns bitfield 
+// {
+// 	//TODO - write
+// 	return -1;
+// }
 
 std::vector<CObstacleInstance> CBattleInfoCallback::battleGetAllObstacles()
 {
@@ -405,6 +405,13 @@ const CGHeroInstance * CBattleInfoCallback::battleGetFightingHero(ui8 side) cons
 	return gs->curB->heroes[side];
 }
 
+bool CBattleInfoCallback::battleIsBlockedByObstacle(BattleHex tile)
+{
+	if(!gs->curB)
+		return 0;
+
+	return gs->curB->isObstacleOnTile(tile);
+}
 
 
 CGameState *const CPrivilagedInfoCallback::gameState ()

+ 7 - 2
lib/IGameCallback.h

@@ -94,7 +94,8 @@ public:
 
 	//battle
 	int battleGetBattlefieldType(); //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
-	int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield
+	//int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield
+	bool battleIsBlockedByObstacle(BattleHex tile);
 	std::vector<CObstacleInstance> battleGetAllObstacles(); //returns all obstacles on the battlefield
 	const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos
@@ -110,7 +111,7 @@ public:
 	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description)
 	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination); //if hero can cast spell here
 	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination); //determines if creature can cast a spell here
-	ui32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode);
+	si32 battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode);
 	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
@@ -121,6 +122,10 @@ public:
 	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //returns hero corresponding to given side (0 - attacker, 1 - defender)
 	si8 battleHasDistancePenalty(const CStack * stack, BattleHex destHex); //checks if given stack has distance penalty
 	si8 battleHasWallPenalty(const CStack * stack, BattleHex destHex); //checks if given stack has wall penalty
+	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
+	{
+		return battleHasDistancePenalty(stack, destHex) || battleHasWallPenalty(stack, destHex);
+	}
 	si8 battleCanTeleportTo(const CStack * stack, BattleHex destHex, int telportLevel); //checks if teleportation of given stack to given position can take place
 	si8 battleGetTacticDist(); //returns tactic distance for calling player or 0 if player is not in tactic phase
 	ui8 battleGetMySide(); //return side of player in battle (attacker/defender)

+ 23 - 13
server/CGameHandler.cpp

@@ -3367,19 +3367,29 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			int spellID = ba.additionalInfo;
 			BattleHex destination(ba.destinationTile);
 
-			int spellLvl = 0;
-			Bonus * bonus = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
-			if (bonus)
-				vstd::amax(spellLvl, bonus->val);
-			bonus = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
-			if (bonus)
-				vstd::amax(spellLvl, bonus->val);
-			vstd::amin (spellLvl, 3);
-
-			int casterSide = gs->curB->whatSide(stack->owner);
-			const CGHeroInstance * secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
-
-			handleSpellCasting(spellID, spellLvl, destination, casterSide, stack->owner, NULL, secHero, 0, ECastingMode::CREATURE_ACTIVE_CASTING, stack);
+			const Bonus *randSpellcaster = stack->getBonus(Selector::type(Bonus::RANDOM_SPELLCASTER));
+			const Bonus * spellcaster = stack->getBonus(Selector::typeSubtype(Bonus::SPELLCASTER, spellID));
+
+			//TODO special bonus for genies ability
+			if(randSpellcaster && battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_AIMED) < 0)
+				spellID = battleGetRandomStackSpell(stack, CBattleInfoCallback::RANDOM_GENIE);
+
+			if(spellID < 0)
+				complain("That stack can't cast spells!");
+			else
+			{
+				int spellLvl = 0;
+				if (spellcaster)
+					vstd::amax(spellLvl, spellcaster->val);
+				if (randSpellcaster)
+					vstd::amax(spellLvl, randSpellcaster->val);
+				vstd::amin (spellLvl, 3);
+
+				int casterSide = gs->curB->whatSide(stack->owner);
+				const CGHeroInstance * secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
+
+				handleSpellCasting(spellID, spellLvl, destination, casterSide, stack->owner, NULL, secHero, 0, ECastingMode::CREATURE_ACTIVE_CASTING, stack);
+			}
 
 			sendAndApply(&end_action);
 			break;

+ 1 - 1
server/CGameHandler.h

@@ -80,7 +80,7 @@ struct CasualtiesAfterBattle
 	void takeFromArmy(CGameHandler *gh);
 };
 
-class CGameHandler : public IGameCallback
+class CGameHandler : public IGameCallback, CBattleInfoCallback
 {
 private:
 	void makeStackDoNothing(const CStack * next);