|  | @@ -11,6 +11,7 @@
 | 
	
		
			
				|  |  |  #include "CBattleInterface.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include "CBattleAnimations.h"
 | 
	
		
			
				|  |  | +#include "CBattleActionsController.h"
 | 
	
		
			
				|  |  |  #include "CBattleInterfaceClasses.h"
 | 
	
		
			
				|  |  |  #include "CCreatureAnimation.h"
 | 
	
		
			
				|  |  |  #include "CBattleProjectileController.h"
 | 
	
	
		
			
				|  | @@ -62,7 +63,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 | 
	
		
			
				|  |  |  		const SDL_Rect & myRect,
 | 
	
		
			
				|  |  |  		std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
 | 
	
		
			
				|  |  |  	: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
 | 
	
		
			
				|  |  | -	creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
 | 
	
		
			
				|  |  |  	attackerInt(att), defenderInt(defen), curInt(att),
 | 
	
		
			
				|  |  |  	myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
 | 
	
		
			
				|  |  |  {
 | 
	
	
		
			
				|  | @@ -129,6 +129,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 | 
	
		
			
				|  |  |  	//preparing menu background and terrain
 | 
	
		
			
				|  |  |  	fieldController.reset( new CBattleFieldController(this));
 | 
	
		
			
				|  |  |  	stacksController.reset( new CBattleStacksController(this));
 | 
	
		
			
				|  |  | +	actionsController.reset( new CBattleActionsController(this));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	//loading hero animations
 | 
	
		
			
				|  |  |  	if(hero1) // attacking hero
 | 
	
	
		
			
				|  | @@ -197,8 +198,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	currentAction = PossiblePlayerBattleAction::INVALID;
 | 
	
		
			
				|  |  | -	selectedAction = PossiblePlayerBattleAction::INVALID;
 | 
	
		
			
				|  |  |  	addUsedEvents(RCLICK | MOVE | KEYBOARD);
 | 
	
		
			
				|  |  |  	controlPanel->blockUI(true);
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -300,28 +299,28 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		enterCreatureCastingMode();
 | 
	
		
			
				|  |  | +		actionsController->enterCreatureCastingMode();
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	else if(key.keysym.sym == SDLK_ESCAPE)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		if(!battleActionsStarted)
 | 
	
		
			
				|  |  |  			CCS->soundh->stopSound(battleIntroSoundChannel);
 | 
	
		
			
				|  |  |  		else
 | 
	
		
			
				|  |  | -			endCastingSpell();
 | 
	
		
			
				|  |  | +			actionsController->endCastingSpell();
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	BattleHex selectedHex = fieldController->getHoveredHex();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	handleHex(selectedHex, MOVE);
 | 
	
		
			
				|  |  | +	actionsController->handleHex(selectedHex, MOVE);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void CBattleInterface::clickRight(tribool down, bool previousState)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	if (!down)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  | -		endCastingSpell();
 | 
	
		
			
				|  |  | +		actionsController->endCastingSpell();
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -453,7 +452,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void CBattleInterface::hexLclicked(int whichOne)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	handleHex(whichOne, LCLICK);
 | 
	
		
			
				|  |  | +	actionsController->handleHex(whichOne, LCLICK);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 | 
	
	
		
			
				|  | @@ -618,36 +617,6 @@ void CBattleInterface::setHeroAnimation(ui8 side, int phase)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void CBattleInterface::castThisSpell(SpellID spellID)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	spellToCast = std::make_shared<BattleAction>();
 | 
	
		
			
				|  |  | -	spellToCast->actionType = EActionType::HERO_SPELL;
 | 
	
		
			
				|  |  | -	spellToCast->actionSubtype = spellID; //spell number
 | 
	
		
			
				|  |  | -	spellToCast->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
 | 
	
		
			
				|  |  | -	spellToCast->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
 | 
	
		
			
				|  |  | -	spellDestSelectMode = true;
 | 
	
		
			
				|  |  | -	creatureCasting = false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//choosing possible targets
 | 
	
		
			
				|  |  | -	const CGHeroInstance *castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
 | 
	
		
			
				|  |  | -	assert(castingHero); // code below assumes non-null hero
 | 
	
		
			
				|  |  | -	sp = spellID.toSpell();
 | 
	
		
			
				|  |  | -	PossiblePlayerBattleAction spellSelMode = curInt->cb->getCasterAction(sp, castingHero, spells::Mode::HERO);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (spellSelMode == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		spellToCast->aimToHex(BattleHex::INVALID);
 | 
	
		
			
				|  |  | -		curInt->cb->battleMakeAction(spellToCast.get());
 | 
	
		
			
				|  |  | -		endCastingSpell();
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		possibleActions.clear();
 | 
	
		
			
				|  |  | -		possibleActions.push_back (spellSelMode); //only this one action can be performed at the moment
 | 
	
		
			
				|  |  | -		GH.fakeMouseMove();//update cursor
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	for(const auto & line : battleLog)
 | 
	
	
		
			
				|  | @@ -798,158 +767,10 @@ void CBattleInterface::activateStack()
 | 
	
		
			
				|  |  |  	myTurn = true;
 | 
	
		
			
				|  |  |  	queue->update();
 | 
	
		
			
				|  |  |  	fieldController->redrawBackgroundWithHexes();
 | 
	
		
			
				|  |  | -	possibleActions = getPossibleActionsForStack(s);
 | 
	
		
			
				|  |  | +	actionsController->activateStack();
 | 
	
		
			
				|  |  |  	GH.fakeMouseMove();
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void CBattleInterface::endCastingSpell()
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	if(spellDestSelectMode)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		spellToCast.reset();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		sp = nullptr;
 | 
	
		
			
				|  |  | -		spellDestSelectMode = false;
 | 
	
		
			
				|  |  | -		CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		if(stacksController->getActiveStack())
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			possibleActions = getPossibleActionsForStack(stacksController->getActiveStack()); //restore actions after they were cleared
 | 
	
		
			
				|  |  | -			myTurn = true;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		if(stacksController->getActiveStack())
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
 | 
	
		
			
				|  |  | -			GH.fakeMouseMove();
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -void CBattleInterface::enterCreatureCastingMode()
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	//silently check for possible errors
 | 
	
		
			
				|  |  | -	if (!myTurn)
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (tacticsMode)
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//hero is casting a spell
 | 
	
		
			
				|  |  | -	if (spellDestSelectMode)
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (!stacksController->getActiveStack())
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (!stacksController->activeStackSpellcaster())
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//random spellcaster
 | 
	
		
			
				|  |  | -	if (stacksController->activeStackSpellToCast() == SpellID::NONE)
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (vstd::contains(possibleActions, PossiblePlayerBattleAction::NO_LOCATION))
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		const spells::Caster * caster = stacksController->getActiveStack();
 | 
	
		
			
				|  |  | -		const CSpell * spell = stacksController->activeStackSpellToCast().toSpell();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		spells::Target target;
 | 
	
		
			
				|  |  | -		target.emplace_back();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		spells::BattleCast cast(curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		auto m = spell->battleMechanics(&cast);
 | 
	
		
			
				|  |  | -		spells::detail::ProblemImpl ignored;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		const bool isCastingPossible = m->canBeCastAt(target, ignored);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		if (isCastingPossible)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			myTurn = false;
 | 
	
		
			
				|  |  | -			giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, stacksController->activeStackSpellToCast());
 | 
	
		
			
				|  |  | -			stacksController->setSelectedStack(nullptr);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		possibleActions = getPossibleActionsForStack(stacksController->getActiveStack());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		auto actionFilterPredicate = [](const PossiblePlayerBattleAction x)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			return (x != PossiblePlayerBattleAction::ANY_LOCATION) && (x != PossiblePlayerBattleAction::NO_LOCATION) &&
 | 
	
		
			
				|  |  | -				(x != PossiblePlayerBattleAction::FREE_LOCATION) && (x != PossiblePlayerBattleAction::AIMED_SPELL_CREATURE) &&
 | 
	
		
			
				|  |  | -				(x != PossiblePlayerBattleAction::OBSTACLE);
 | 
	
		
			
				|  |  | -		};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		vstd::erase_if(possibleActions, actionFilterPredicate);
 | 
	
		
			
				|  |  | -		GH.fakeMouseMove();
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -std::vector<PossiblePlayerBattleAction> CBattleInterface::getPossibleActionsForStack(const CStack *stack)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	BattleClientInterfaceData data; //hard to get rid of these things so for now they're required data to pass
 | 
	
		
			
				|  |  | -	data.creatureSpellToCast = stacksController->activeStackSpellToCast();
 | 
	
		
			
				|  |  | -	data.tacticsMode = tacticsMode;
 | 
	
		
			
				|  |  | -	auto allActions = curInt->cb->getClientActionsForStack(stack, data);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	return std::vector<PossiblePlayerBattleAction>(allActions);
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -void CBattleInterface::reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	if(tacticsMode || possibleActions.empty()) return; //this function is not supposed to be called in tactics mode or before getPossibleActionsForStack
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	auto assignPriority = [&](PossiblePlayerBattleAction const & item) -> uint8_t //large lambda assigning priority which would have to be part of possibleActions without it
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		switch(item)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::ANY_LOCATION:
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::NO_LOCATION:
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::FREE_LOCATION:
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::OBSTACLE:
 | 
	
		
			
				|  |  | -			if(!stack->hasBonusOfType(Bonus::NO_SPELLCAST_BY_DEFAULT) && context == MouseHoveredHexContext::OCCUPIED_HEX)
 | 
	
		
			
				|  |  | -				return 1;
 | 
	
		
			
				|  |  | -			else
 | 
	
		
			
				|  |  | -				return 100;//bottom priority
 | 
	
		
			
				|  |  | -			break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
 | 
	
		
			
				|  |  | -			return 2; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::RISE_DEMONS:
 | 
	
		
			
				|  |  | -			return 3; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::SHOOT:
 | 
	
		
			
				|  |  | -			return 4; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
 | 
	
		
			
				|  |  | -			return 5; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::ATTACK:
 | 
	
		
			
				|  |  | -			return 6; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 | 
	
		
			
				|  |  | -			return 7; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::MOVE_STACK:
 | 
	
		
			
				|  |  | -			return 8; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::CATAPULT:
 | 
	
		
			
				|  |  | -			return 9; break;
 | 
	
		
			
				|  |  | -		case PossiblePlayerBattleAction::HEAL:
 | 
	
		
			
				|  |  | -			return 10; break;
 | 
	
		
			
				|  |  | -		default:
 | 
	
		
			
				|  |  | -			return 200; break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	auto comparer = [&](PossiblePlayerBattleAction const & lhs, PossiblePlayerBattleAction const & rhs)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		return assignPriority(lhs) > assignPriority(rhs);
 | 
	
		
			
				|  |  | -	};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	std::make_heap(possibleActions.begin(), possibleActions.end(), comparer);
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  void CBattleInterface::endAction(const BattleAction* action)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
 | 
	
	
		
			
				|  | @@ -1114,531 +935,6 @@ void CBattleInterface::tacticNextStack(const CStack * current)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -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();
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -bool CBattleInterface::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stackToMove);
 | 
	
		
			
				|  |  | -	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (vstd::contains(acc, myNumber))
 | 
	
		
			
				|  |  | -		return true;
 | 
	
		
			
				|  |  | -	else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest))
 | 
	
		
			
				|  |  | -		return true;
 | 
	
		
			
				|  |  | -	else
 | 
	
		
			
				|  |  | -		return false;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	if (!myTurn || !battleActionsStarted) //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
 | 
	
		
			
				|  |  | -	ECursor::ECursorTypes cursorType = ECursor::COMBAT;
 | 
	
		
			
				|  |  | -	int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//used when l-clicking -> action to be called upon the click
 | 
	
		
			
				|  |  | -	std::function<void()> realizeAction;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//Get stack on the hex - first try to grab the alive one, if not found -> allow dead stacks.
 | 
	
		
			
				|  |  | -	const CStack * shere = curInt->cb->battleGetStackByPos(myNumber, true);
 | 
	
		
			
				|  |  | -	if(!shere)
 | 
	
		
			
				|  |  | -		shere = curInt->cb->battleGetStackByPos(myNumber, false);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if(!stacksController->getActiveStack())
 | 
	
		
			
				|  |  | -		return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	bool ourStack = false;
 | 
	
		
			
				|  |  | -	if (shere)
 | 
	
		
			
				|  |  | -		ourStack = shere->owner == curInt->playerID;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	//stack may have changed, update selection border
 | 
	
		
			
				|  |  | -	stacksController->setHoveredStack(shere);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	localActions.clear();
 | 
	
		
			
				|  |  | -	illegalActions.clear();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	reorderPossibleActionsPriority(stacksController->getActiveStack(), shere ? MouseHoveredHexContext::OCCUPIED_HEX : MouseHoveredHexContext::UNOCCUPIED_HEX);
 | 
	
		
			
				|  |  | -	const bool forcedAction = possibleActions.size() == 1;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	for (PossiblePlayerBattleAction action : possibleActions)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		bool legalAction = false; //this action is legal and can be performed
 | 
	
		
			
				|  |  | -		bool notLegal = false; //this action is not legal and should display message
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		switch (action)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
 | 
	
		
			
				|  |  | -				if (shere && ourStack)
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::MOVE_TACTICS:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::MOVE_STACK:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				if (!(shere && shere->alive())) //we can walk on dead stacks
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if(canStackMoveHere(stacksController->getActiveStack(), myNumber))
 | 
	
		
			
				|  |  | -						legalAction = true;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ATTACK:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				if(curInt->cb->battleCanAttack(stacksController->getActiveStack(), shere, myNumber))
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if (fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
 | 
	
		
			
				|  |  | -						BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -						if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
 | 
	
		
			
				|  |  | -							legalAction = true;
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SHOOT:
 | 
	
		
			
				|  |  | -				if(curInt->cb->battleCanShoot(stacksController->getActiveStack(), myNumber))
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ANY_LOCATION:
 | 
	
		
			
				|  |  | -				if (myNumber > -1) //TODO: this should be checked for all actions
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
 | 
	
		
			
				|  |  | -						legalAction = true;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 | 
	
		
			
				|  |  | -				if(shere && isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				if(shere && ourStack && shere != stacksController->getActiveStack() && shere->alive()) //only positive spells for other allied creatures
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					int spellID = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), shere, CBattleInfoCallback::RANDOM_GENIE);
 | 
	
		
			
				|  |  | -					if(spellID > -1)
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						legalAction = true;
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::OBSTACLE:
 | 
	
		
			
				|  |  | -				if(isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::TELEPORT:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				//todo: move to mechanics
 | 
	
		
			
				|  |  | -				ui8 skill = 0;
 | 
	
		
			
				|  |  | -				if (creatureCasting)
 | 
	
		
			
				|  |  | -					skill = stacksController->getActiveStack()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -					skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
 | 
	
		
			
				|  |  | -				//TODO: explicitely save power, skill
 | 
	
		
			
				|  |  | -				if (curInt->cb->battleCanTeleportTo(stacksController->getSelectedStack(), myNumber, skill))
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -					notLegal = true;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice
 | 
	
		
			
				|  |  | -				if (shere && shere != stacksController->getSelectedStack() && ourStack && shere->alive())
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -					notLegal = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::FREE_LOCATION:
 | 
	
		
			
				|  |  | -				legalAction = true;
 | 
	
		
			
				|  |  | -				if(!isCastingPossibleHere(stacksController->getActiveStack(), shere, myNumber))
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					legalAction = false;
 | 
	
		
			
				|  |  | -					notLegal = true;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::CATAPULT:
 | 
	
		
			
				|  |  | -				if (siegeController && siegeController->isCatapultAttackable(myNumber))
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::HEAL:
 | 
	
		
			
				|  |  | -				if (shere && ourStack && shere->canBeHealed())
 | 
	
		
			
				|  |  | -					legalAction = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::RISE_DEMONS:
 | 
	
		
			
				|  |  | -				if (shere && ourStack && !shere->alive())
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if (!(shere->hasBonusOfType(Bonus::UNDEAD)
 | 
	
		
			
				|  |  | -						|| shere->hasBonusOfType(Bonus::NON_LIVING)
 | 
	
		
			
				|  |  | -						|| shere->hasBonusOfType(Bonus::GARGOYLE)
 | 
	
		
			
				|  |  | -						|| shere->summoned
 | 
	
		
			
				|  |  | -						|| shere->isClone()
 | 
	
		
			
				|  |  | -						|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
 | 
	
		
			
				|  |  | -						))
 | 
	
		
			
				|  |  | -						legalAction = true;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (legalAction)
 | 
	
		
			
				|  |  | -			localActions.push_back (action);
 | 
	
		
			
				|  |  | -		else if (notLegal || forcedAction)
 | 
	
		
			
				|  |  | -			illegalActions.push_back (action);
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	illegalAction = PossiblePlayerBattleAction::INVALID; //clear it in first place
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (vstd::contains(localActions, selectedAction)) //try to use last selected action by default
 | 
	
		
			
				|  |  | -		currentAction = selectedAction;
 | 
	
		
			
				|  |  | -	else if (localActions.size()) //if not possible, select first available action (they are sorted by suggested priority)
 | 
	
		
			
				|  |  | -		currentAction = localActions.front();
 | 
	
		
			
				|  |  | -	else //no legal action possible
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		currentAction = PossiblePlayerBattleAction::INVALID; //don't allow to do anything
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		if (vstd::contains(illegalActions, selectedAction))
 | 
	
		
			
				|  |  | -			illegalAction = selectedAction;
 | 
	
		
			
				|  |  | -		else if (illegalActions.size())
 | 
	
		
			
				|  |  | -			illegalAction = illegalActions.front();
 | 
	
		
			
				|  |  | -		else if (shere && ourStack && shere->alive()) //last possibility - display info about our creature
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			currentAction = PossiblePlayerBattleAction::CREATURE_INFO;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		else
 | 
	
		
			
				|  |  | -			illegalAction = PossiblePlayerBattleAction::INVALID; //we should never be here
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	bool isCastingPossible = false;
 | 
	
		
			
				|  |  | -	bool secondaryTarget = false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (currentAction > PossiblePlayerBattleAction::INVALID)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		switch (currentAction) //display console message, realize selected action
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::CHOOSE_TACTICS_STACK:
 | 
	
		
			
				|  |  | -				consoleMsg = (boost::format(CGI->generaltexth->allTexts[481]) % shere->getName()).str(); //Select %s
 | 
	
		
			
				|  |  | -				realizeAction = [=](){ stackActivated(shere); };
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::MOVE_TACTICS:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::MOVE_STACK:
 | 
	
		
			
				|  |  | -				if (stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					cursorFrame = ECursor::COMBAT_FLY;
 | 
	
		
			
				|  |  | -					consoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % stacksController->getActiveStack()->getName()).str(); //Fly %s here
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					cursorFrame = ECursor::COMBAT_MOVE;
 | 
	
		
			
				|  |  | -					consoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % stacksController->getActiveStack()->getName()).str(); //Move %s here
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				realizeAction = [=]()
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if(stacksController->getActiveStack()->doubleWide())
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(stacksController->getActiveStack());
 | 
	
		
			
				|  |  | -						BattleHex shiftedDest = myNumber.cloneInDirection(stacksController->getActiveStack()->destShiftDir(), false);
 | 
	
		
			
				|  |  | -						if(vstd::contains(acc, myNumber))
 | 
	
		
			
				|  |  | -							giveCommand(EActionType::WALK, myNumber);
 | 
	
		
			
				|  |  | -						else if(vstd::contains(acc, shiftedDest))
 | 
	
		
			
				|  |  | -							giveCommand(EActionType::WALK, shiftedDest);
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					else
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						giveCommand(EActionType::WALK, myNumber);
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -				};
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ATTACK:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
 | 
	
		
			
				|  |  | -					setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -					bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -					realizeAction = [=]()
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
 | 
	
		
			
				|  |  | -						if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
 | 
	
		
			
				|  |  | -						{
 | 
	
		
			
				|  |  | -							auto command = new BattleAction(BattleAction::makeMeleeAttack(stacksController->getActiveStack(), myNumber, attackFromHex, returnAfterAttack));
 | 
	
		
			
				|  |  | -							sendCommand(command, stacksController->getActiveStack());
 | 
	
		
			
				|  |  | -						}
 | 
	
		
			
				|  |  | -					};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -					TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
 | 
	
		
			
				|  |  | -					std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
 | 
	
		
			
				|  |  | -					consoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SHOOT:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				if (curInt->cb->battleHasShootingPenalty(stacksController->getActiveStack(), myNumber))
 | 
	
		
			
				|  |  | -					cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -					cursorFrame = ECursor::COMBAT_SHOOT;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				realizeAction = [=](){giveCommand(EActionType::SHOOT, myNumber);};
 | 
	
		
			
				|  |  | -				TDmgRange damage = curInt->cb->battleEstimateDamage(stacksController->getActiveStack(), shere);
 | 
	
		
			
				|  |  | -				std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
 | 
	
		
			
				|  |  | -				//printing - Shoot %s (%d shots left, %s damage)
 | 
	
		
			
				|  |  | -				consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % stacksController->getActiveStack()->shots.available() % estDmgText).str();
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 | 
	
		
			
				|  |  | -				sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
 | 
	
		
			
				|  |  | -				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
 | 
	
		
			
				|  |  | -				switch (sp->id)
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					case SpellID::SACRIFICE:
 | 
	
		
			
				|  |  | -					case SpellID::TELEPORT:
 | 
	
		
			
				|  |  | -						stacksController->setSelectedStack(shere); //remember first target
 | 
	
		
			
				|  |  | -						secondaryTarget = true;
 | 
	
		
			
				|  |  | -						break;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::ANY_LOCATION:
 | 
	
		
			
				|  |  | -				sp = CGI->spellh->objects[creatureCasting ? stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
 | 
	
		
			
				|  |  | -				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
 | 
	
		
			
				|  |  | -				sp = nullptr;
 | 
	
		
			
				|  |  | -				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[301]) % shere->getName()); //Cast a spell on %
 | 
	
		
			
				|  |  | -				creatureCasting = true;
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::TELEPORT:
 | 
	
		
			
				|  |  | -				consoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_TELEPORT;
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::OBSTACLE:
 | 
	
		
			
				|  |  | -				consoleMsg = CGI->generaltexth->allTexts[550];
 | 
	
		
			
				|  |  | -				//TODO: remove obstacle cursor
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SACRIFICE:
 | 
	
		
			
				|  |  | -				consoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_SACRIFICE;
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::FREE_LOCATION:
 | 
	
		
			
				|  |  | -				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
 | 
	
		
			
				|  |  | -				isCastingPossible = true;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::HEAL:
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_HEAL;
 | 
	
		
			
				|  |  | -				consoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
 | 
	
		
			
				|  |  | -				realizeAction = [=](){ giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::RISE_DEMONS:
 | 
	
		
			
				|  |  | -				cursorType = ECursor::SPELLBOOK;
 | 
	
		
			
				|  |  | -				realizeAction = [=]()
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
 | 
	
		
			
				|  |  | -				};
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::CATAPULT:
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
 | 
	
		
			
				|  |  | -				realizeAction = [=](){ giveCommand(EActionType::CATAPULT, myNumber); };
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::CREATURE_INFO:
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_QUERY;
 | 
	
		
			
				|  |  | -				consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
 | 
	
		
			
				|  |  | -				realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else //no possible valid action, display message
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		switch (illegalAction)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_BLOCKED;
 | 
	
		
			
				|  |  | -				consoleMsg = CGI->generaltexth->allTexts[23];
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::TELEPORT:
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_BLOCKED;
 | 
	
		
			
				|  |  | -				consoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SACRIFICE:
 | 
	
		
			
				|  |  | -				consoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::FREE_LOCATION:
 | 
	
		
			
				|  |  | -				cursorFrame = ECursor::COMBAT_BLOCKED;
 | 
	
		
			
				|  |  | -				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % sp->name); //No room to place %s here
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			default:
 | 
	
		
			
				|  |  | -				if (myNumber == -1)
 | 
	
		
			
				|  |  | -					CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -					cursorFrame = ECursor::COMBAT_BLOCKED;
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (isCastingPossible) //common part
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		switch (currentAction) //don't use that with teleport / sacrifice
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::TELEPORT: //FIXME: more generic solution?
 | 
	
		
			
				|  |  | -			case PossiblePlayerBattleAction::SACRIFICE:
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			default:
 | 
	
		
			
				|  |  | -				cursorType = ECursor::SPELLBOOK;
 | 
	
		
			
				|  |  | -				cursorFrame = 0;
 | 
	
		
			
				|  |  | -				if (consoleMsg.empty() && sp)
 | 
	
		
			
				|  |  | -					consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -		realizeAction = [=]()
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			if(secondaryTarget) //select that target now
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				possibleActions.clear();
 | 
	
		
			
				|  |  | -				switch (sp->id.toEnum())
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					case SpellID::TELEPORT: //don't cast spell yet, only select target
 | 
	
		
			
				|  |  | -						spellToCast->aimToUnit(shere);
 | 
	
		
			
				|  |  | -						possibleActions.push_back(PossiblePlayerBattleAction::TELEPORT);
 | 
	
		
			
				|  |  | -						break;
 | 
	
		
			
				|  |  | -					case SpellID::SACRIFICE:
 | 
	
		
			
				|  |  | -						spellToCast->aimToHex(myNumber);
 | 
	
		
			
				|  |  | -						possibleActions.push_back(PossiblePlayerBattleAction::SACRIFICE);
 | 
	
		
			
				|  |  | -						break;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			else
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				if (creatureCasting)
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					if (sp)
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						giveCommand(EActionType::MONSTER_SPELL, myNumber, stacksController->activeStackSpellToCast());
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					else //unknown random spell
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -						giveCommand(EActionType::MONSTER_SPELL, myNumber);
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				else
 | 
	
		
			
				|  |  | -				{
 | 
	
		
			
				|  |  | -					assert(sp);
 | 
	
		
			
				|  |  | -					switch (sp->id.toEnum())
 | 
	
		
			
				|  |  | -					{
 | 
	
		
			
				|  |  | -					case SpellID::SACRIFICE:
 | 
	
		
			
				|  |  | -						spellToCast->aimToUnit(shere);//victim
 | 
	
		
			
				|  |  | -						break;
 | 
	
		
			
				|  |  | -					default:
 | 
	
		
			
				|  |  | -						spellToCast->aimToHex(myNumber);
 | 
	
		
			
				|  |  | -						break;
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					curInt->cb->battleMakeAction(spellToCast.get());
 | 
	
		
			
				|  |  | -					endCastingSpell();
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | -				stacksController->setSelectedStack(nullptr);
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -		};
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		if (eventType == MOVE)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			if (setCursor)
 | 
	
		
			
				|  |  | -				CCS->curh->changeGraphic(cursorType, cursorFrame);
 | 
	
		
			
				|  |  | -			controlPanel->console->write(consoleMsg);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		if (eventType == LCLICK && realizeAction)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			//opening creature window shouldn't affect myTurn...
 | 
	
		
			
				|  |  | -			if ((currentAction != PossiblePlayerBattleAction::CREATURE_INFO) && !secondaryTarget)
 | 
	
		
			
				|  |  | -			{
 | 
	
		
			
				|  |  | -				myTurn = false; //tends to crash with empty calls
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -			realizeAction();
 | 
	
		
			
				|  |  | -			if (!secondaryTarget) //do not replace teleport or sacrifice cursor
 | 
	
		
			
				|  |  | -				CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
 | 
	
		
			
				|  |  | -			controlPanel->console->clear();
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack *shere, BattleHex myNumber)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	creatureCasting = stacksController->activeStackSpellcaster() && !spellDestSelectMode; //TODO: allow creatures to cast aimed spells
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	bool isCastingPossible = true;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	int spellID = -1;
 | 
	
		
			
				|  |  | -	if (creatureCasting)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		if (stacksController->activeStackSpellToCast() != SpellID::NONE && (shere != sactive)) //can't cast on itself
 | 
	
		
			
				|  |  | -			spellID = stacksController->activeStackSpellToCast(); //TODO: merge with SpellTocast?
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else //hero casting
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		spellID = spellToCast->actionSubtype;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	sp = nullptr;
 | 
	
		
			
				|  |  | -	if (spellID >= 0)
 | 
	
		
			
				|  |  | -		sp = CGI->spellh->objects[spellID];
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	if (sp)
 | 
	
		
			
				|  |  | -	{
 | 
	
		
			
				|  |  | -		const spells::Caster *caster = creatureCasting ? static_cast<const spells::Caster *>(sactive) : static_cast<const spells::Caster *>(curInt->cb->battleGetMyHero());
 | 
	
		
			
				|  |  | -		if (caster == nullptr)
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			isCastingPossible = false;//just in case
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -		else
 | 
	
		
			
				|  |  | -		{
 | 
	
		
			
				|  |  | -			const spells::Mode mode = creatureCasting ? spells::Mode::CREATURE_ACTIVE : spells::Mode::HERO;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			spells::Target target;
 | 
	
		
			
				|  |  | -			target.emplace_back(myNumber);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			spells::BattleCast cast(curInt->cb.get(), caster, mode, sp);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			auto m = sp->battleMechanics(&cast);
 | 
	
		
			
				|  |  | -			spells::detail::ProblemImpl problem; //todo: display problem in status bar
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -			isCastingPossible = m->canBeCastAt(target, problem);
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	else
 | 
	
		
			
				|  |  | -		isCastingPossible = false;
 | 
	
		
			
				|  |  | -	if (!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
 | 
	
		
			
				|  |  | -			isCastingPossible = false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	return isCastingPossible;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	obstacleController->obstaclePlaced(oi);
 | 
	
	
		
			
				|  | @@ -1784,8 +1080,6 @@ void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
 | 
	
		
			
				|  |  |  	showHexEntry(objects.afterAll);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	for (auto & elem : battleEffects)
 | 
	
	
		
			
				|  | @@ -1852,3 +1146,8 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	return sorted;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void CBattleInterface::castThisSpell(SpellID spellID)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	actionsController->castThisSpell(spellID);
 | 
	
		
			
				|  |  | +}
 |