浏览代码

Merge pull request #1487 from IvanSavenko/battle_actions_refactoring

Battle actions refactoring
Ivan Savenko 2 年之前
父节点
当前提交
64faa13b6d

文件差异内容过多而无法显示
+ 537 - 468
client/battle/BattleActionsController.cpp


+ 59 - 33
client/battle/BattleActionsController.h

@@ -14,6 +14,10 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class BattleAction;
+namespace spells {
+class Caster;
+enum class Mode;
+}
 
 VCMI_LIB_NAMESPACE_END
 
@@ -34,40 +38,46 @@ class BattleActionsController
 	/// all actions possible to call at the moment by player
 	std::vector<PossiblePlayerBattleAction> possibleActions;
 
-	/// actions possible to take on hovered hex
-	std::vector<PossiblePlayerBattleAction> localActions;
+	/// spell for which player's hero is choosing destination
+	std::shared_ptr<BattleAction> heroSpellToCast;
 
-	/// these actions display message in case of illegal target
-	std::vector<PossiblePlayerBattleAction> illegalActions;
+	/// cached message that was set by this class in status bar
+	std::string currentConsoleMsg;
 
-	/// action that will be performed on l-click
-	PossiblePlayerBattleAction currentAction;
+	/// if true, active stack could possibly cast some target spell
+	const CSpell * creatureSpellToCast;
 
-	/// last action chosen (and saved) by player
-	PossiblePlayerBattleAction selectedAction;
+	bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
+	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
+	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
+	void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
 
-	/// if there are not possible actions to choose from, this action should be show as "illegal" in UI
-	PossiblePlayerBattleAction illegalAction;
+	bool actionIsLegal(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// if true, stack currently aims to cats a spell
-	bool creatureCasting;
+	void actionSetCursor(PossiblePlayerBattleAction action, BattleHex hoveredHex);
+	void actionSetCursorBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// if true, player is choosing destination for his spell - only for GUI / console
-	bool spellDestSelectMode;
+	std::string actionGetStatusMessage(PossiblePlayerBattleAction action, BattleHex hoveredHex);
+	std::string actionGetStatusMessageBlocked(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// spell for which player is choosing destination
-	std::shared_ptr<BattleAction> spellToCast;
+	void actionRealize(PossiblePlayerBattleAction action, BattleHex hoveredHex);
 
-	/// spell for which player is choosing destination, pointer for convenience
-	const CSpell *currentSpell;
+	PossiblePlayerBattleAction selectAction(BattleHex myNumber);
 
-	/// cached message that was set by this class in status bar
-	std::string currentConsoleMsg;
+	const CStack * getStackForHex(BattleHex myNumber) ;
 
-	bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
-	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback
-	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn
-	void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context);
+	/// attempts to initialize spellcasting action for stack
+	/// will silently return if stack is not a spellcaster
+	void tryActivateStackSpellcasting(const CStack *casterStack);
+
+	/// returns spell that is currently being cast by hero or nullptr if none
+	const CSpell * getHeroSpellToCast() const;
+
+	/// if current stack is spellcaster, returns spell being cast, or null othervice
+	const CSpell * getStackSpellToCast( ) const;
+
+	/// returns true if current stack is a spellcaster
+	bool isActiveStackSpellcaster() const;
 
 public:
 	BattleActionsController(BattleInterface & owner);
@@ -75,24 +85,40 @@ public:
 	/// initialize list of potential actions for new active stack
 	void activateStack();
 
-	/// initialize potential actions for spells that can be cast by active stack
+	/// returns true if UI is currently in target selection mode
+	bool spellcastingModeActive() const;
+
+	/// returns true if one of the following is true:
+	/// - we are casting spell by hero
+	/// - we are casting spell by creature in targeted mode (F hotkey)
+	/// - current creature is spellcaster and preferred action for current hex is spellcast
+	bool currentActionSpellcasting(BattleHex hoveredHex);
+
+	/// enter targeted spellcasting mode for creature, e.g. via "F" hotkey
 	void enterCreatureCastingMode();
 
-	/// initialize potential actions for selected spell
+	/// initialize hero spellcasting mode, e.g. on selecting spell in spellbook
 	void castThisSpell(SpellID spellID);
 
 	/// ends casting spell (eg. when spell has been cast or canceled)
 	void endCastingSpell();
 
-	/// update UI (e.g. status bar/cursor) according to new active hex
-	void handleHex(BattleHex myNumber, int eventType);
+	/// update cursor and status bar according to new active hex
+	void onHexHovered(BattleHex hoveredHex);
 
-	/// returns currently selected spell or SpellID::NONE on error
-	SpellID selectedSpell() const;
+	/// called when cursor is no longer over battlefield and cursor/battle log should be reset
+	void onHoverEnded();
+
+	/// performs action according to selected hex
+	void onHexLeftClicked(BattleHex clickedHex);
+
+	/// performs action according to selected hex
+	void onHexRightClicked(BattleHex clickedHex);
+
+	const spells::Caster * getCurrentSpellcaster() const;
+	const CSpell * getCurrentSpell() const;
+	spells::Mode getCurrentCastMode() const;
 
-	/// returns true if UI is currently in target selection mode
-	bool spellcastingModeActive() const;
-	
 	/// methods to work with array of possible actions, needed to control special creatures abilities
 	const std::vector<PossiblePlayerBattleAction> & getPossibleActions() const;
 	void removePossibleAction(PossiblePlayerBattleAction);

+ 64 - 29
client/battle/BattleFieldController.cpp

@@ -34,6 +34,8 @@
 #include "../../lib/CStack.h"
 #include "../../lib/spells/ISpellMechanics.h"
 
+#include <SDL_events.h>
+
 BattleFieldController::BattleFieldController(BattleInterface & owner):
 	owner(owner)
 {
@@ -76,20 +78,11 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 
 	backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
 
-	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
-	{
-		auto hex = std::make_shared<ClickableHex>();
-		hex->myNumber = h;
-		hex->pos = hexPositionAbsolute(h);
-		hex->myInterface = &owner;
-		bfield.push_back(hex);
-	}
-
 	auto accessibility = owner.curInt->cb->getAccesibility();
 	for(int i = 0; i < accessibility.size(); i++)
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
 
-	addUsedEvents(MOVE);
+	addUsedEvents(LCLICK | RCLICK | MOVE);
 	LOCPLINT->cingconsole->pos = this->pos;
 }
 
@@ -107,11 +100,38 @@ void BattleFieldController::createHeroes()
 
 void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event)
 {
+	if (!pos.isInside(event.x, event.y))
+	{
+		owner.actionsController->onHoverEnded();
+		return;
+	}
+
 	BattleHex selectedHex = getHoveredHex();
+	owner.actionsController->onHexHovered(selectedHex);
+}
+
+void BattleFieldController::clickLeft(tribool down, bool previousState)
+{
+	if(!down)
+	{
+		BattleHex selectedHex = getHoveredHex();
 
-	owner.actionsController->handleHex(selectedHex, MOVE);
+		if (selectedHex != BattleHex::INVALID)
+			owner.actionsController->onHexLeftClicked(selectedHex);
+	}
 }
 
+void BattleFieldController::clickRight(tribool down, bool previousState)
+{
+	if(down)
+	{
+		BattleHex selectedHex = getHoveredHex();
+
+		if (selectedHex != BattleHex::INVALID)
+			owner.actionsController->onHexRightClicked(selectedHex);
+
+	}
+}
 
 void BattleFieldController::renderBattlefield(Canvas & canvas)
 {
@@ -233,19 +253,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
 	const spells::Caster *caster = nullptr;
 	const CSpell *spell = nullptr;
 
-	spells::Mode mode = spells::Mode::HERO;
-
-	if(owner.actionsController->spellcastingModeActive())//hero casts spell
-	{
-		spell = owner.actionsController->selectedSpell().toSpell();
-		caster = owner.getActiveHero();
-	}
-	else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
-	{
-		spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
-		caster = owner.stacksController->getActiveStack();
-		mode = spells::Mode::CREATURE_ACTIVE;
-	}
+	spells::Mode mode = owner.actionsController->getCurrentCastMode();
+	spell = owner.actionsController->getCurrentSpell();
+	caster = owner.actionsController->getCurrentSpellcaster();
 
 	if(caster && spell) //when casting spell
 	{
@@ -310,7 +320,10 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 	std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
 	std::set<BattleHex> hoveredMove  = getHighlightedHexesMovementTarget();
 
-	auto const & hoveredMouse = owner.actionsController->spellcastingModeActive() ? hoveredSpell : hoveredMove;
+	if (getHoveredHex() == BattleHex::INVALID)
+		return;
+
+	auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove;
 
 	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
 	{
@@ -355,9 +368,31 @@ bool BattleFieldController::isPixelInHex(Point const & position)
 
 BattleHex BattleFieldController::getHoveredHex()
 {
-	for ( auto const & hex : bfield)
-		if (hex->hovered && hex->strictHovered)
-			return hex->myNumber;
+	Point hoverPos = GH.getCursorPosition();
+
+	if (owner.attackingHero)
+	{
+		if (owner.attackingHero->pos.isInside(hoverPos))
+			return BattleHex::HERO_ATTACKER;
+	}
+
+	if (owner.defendingHero)
+	{
+		if (owner.attackingHero->pos.isInside(hoverPos))
+			return BattleHex::HERO_DEFENDER;
+	}
+
+
+	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
+	{
+		Rect hexPosition = hexPositionAbsolute(h);
+
+		if (!hexPosition.isInside(hoverPos))
+			continue;
+
+		if (isPixelInHex(hoverPos - hexPosition.topLeft()))
+			return h;
+	}
 
 	return BattleHex::INVALID;
 }
@@ -520,7 +555,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 		}
 		default:
 			assert(0);
-			return attackTarget.cloneInDirection(BattleHex::LEFT);
+			return BattleHex::INVALID;
 		}
 	}
 }

+ 3 - 4
client/battle/BattleFieldController.h

@@ -20,7 +20,6 @@ class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-class ClickableHex;
 class BattleHero;
 class Canvas;
 class IImage;
@@ -50,8 +49,6 @@ class BattleFieldController : public CIntObject
 	/// hexes that when in front of a unit cause it's amount box to move back
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
 
-	std::vector<std::shared_ptr<ClickableHex>> bfield;
-
 	void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
 
 	std::set<BattleHex> getHighlightedHexesStackRange();
@@ -66,9 +63,11 @@ class BattleFieldController : public CIntObject
 	BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
 
 	void mouseMoved(const SDL_MouseMotionEvent &event) override;
+	void clickLeft(tribool down, bool previousState) override;
+	void clickRight(tribool down, bool previousState) override;
+
 	void showAll(SDL_Surface * to) override;
 	void show(SDL_Surface * to) override;
-
 public:
 	BattleFieldController(BattleInterface & owner);
 

+ 6 - 3
client/battle/BattleInterface.cpp

@@ -54,7 +54,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	, attackerInt(att)
 	, defenderInt(defen)
 	, curInt(att)
-	, myTurn(false)
 	, moveSoundHander(-1)
 {
 	for ( auto & event : animationEvents)
@@ -267,7 +266,6 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
 	if(!tacticsMode)
 	{
 		logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
-		myTurn = false;
 		stacksController->setActiveStack(nullptr);
 		givenCommand.setn(command);
 	}
@@ -278,6 +276,7 @@ void BattleInterface::sendCommand(BattleAction *& command, const CStack * actor)
 		stacksController->setActiveStack(nullptr);
 		//next stack will be activated when action ends
 	}
+	CCS->curh->set(Cursor::Combat::POINTER);
 }
 
 const CGHeroInstance * BattleInterface::getActiveHero()
@@ -553,7 +552,6 @@ void BattleInterface::activateStack()
 	if(!s)
 		return;
 
-	myTurn = true;
 	windowObject->updateQueue();
 	windowObject->blockUI(false);
 	fieldController->redrawBackgroundWithHexes();
@@ -561,6 +559,11 @@ void BattleInterface::activateStack()
 	GH.fakeMouseMove();
 }
 
+bool BattleInterface::makingTurn() const
+{
+	return stacksController->getActiveStack() != nullptr;
+}
+
 void BattleInterface::endAction(const BattleAction* action)
 {
 	const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);

+ 2 - 2
client/battle/BattleInterface.h

@@ -37,7 +37,6 @@ class Canvas;
 class BattleResultWindow;
 class StackQueue;
 class CPlayerInterface;
-class ClickableHex;
 class CAnimation;
 struct BattleEffect;
 class IImage;
@@ -146,7 +145,8 @@ public:
 
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
 
-	bool myTurn; //if true, interface is active (commands can be ordered)
+	bool makingTurn() const;
+
 	int moveSoundHander; // sound handler used when moving a unit
 
 	BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);

+ 4 - 90
client/battle/BattleInterfaceClasses.cpp

@@ -30,7 +30,6 @@
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
 #include "../windows/CMessage.h"
-#include "../windows/CCreatureWindow.h"
 #include "../windows/CSpellWindow.h"
 #include "../render/CAnimation.h"
 #include "../adventureMap/CInGameConsole.h"
@@ -274,50 +273,29 @@ void BattleHero::setPhase(EHeroAnimType newPhase)
 	nextPhase = EHeroAnimType::HOLDING;
 }
 
-void BattleHero::hover(bool on)
-{
-	//TODO: BROKEN CODE
-	if (on)
-		CCS->curh->set(Cursor::Combat::HERO);
-	else
-		CCS->curh->set(Cursor::Combat::POINTER);
-}
-
-void BattleHero::clickLeft(tribool down, bool previousState)
+void BattleHero::heroLeftClicked()
 {
 	if(owner.actionsController->spellcastingModeActive()) //we are casting a spell
 		return;
 
-	if(boost::logic::indeterminate(down))
-		return;
-
-	if(!hero || down || !owner.myTurn)
+	if(!hero || !owner.makingTurn())
 		return;
 
 	if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
 	{
-		BattleHex hoveredHex = owner.fieldController->getHoveredHex();
-		//do nothing when any hex is hovered - hero's animation overlaps battlefield
-		if ( hoveredHex != BattleHex::INVALID )
-			return;
-
 		CCS->curh->set(Cursor::Map::POINTER);
-
 		GH.pushIntT<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
 	}
 }
 
-void BattleHero::clickRight(tribool down, bool previousState)
+void BattleHero::heroRightClicked()
 {
-	if(boost::logic::indeterminate(down))
-		return;
-
 	Point windowPosition;
 	windowPosition.x = (!defender) ? owner.fieldController->pos.left() + 1 : owner.fieldController->pos.right() - 79;
 	windowPosition.y = owner.fieldController->pos.y + 135;
 
 	InfoAboutHero targetHero;
-	if(down && (owner.myTurn || settings["session"]["spectate"].Bool()))
+	if(owner.makingTurn() || settings["session"]["spectate"].Bool())
 	{
 		auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance;
 		targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
@@ -374,8 +352,6 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	flagAnimation->preload();
 	flagAnimation->playerColored(hero->tempOwner);
 
-	addUsedEvents(LCLICK | RCLICK | HOVER);
-
 	switchToNextPhase();
 	play();
 }
@@ -681,68 +657,6 @@ void BattleResultWindow::bExitf()
 	CCS->videoh->close();
 }
 
-void ClickableHex::hover(bool on)
-{
-	hovered = on;
-	//Hoverable::hover(on);
-	if(!on && setAlterText)
-	{
-		GH.statusbar->clear();
-		setAlterText = false;
-	}
-}
-
-ClickableHex::ClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr)
-{
-	addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
-}
-
-void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
-{
-	strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y));
-
-	if(hovered && strictHovered) //print attacked creature to console
-	{
-		const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber);
-		if( attackedStack != nullptr &&
-			attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
-			attackedStack->alive())
-		{
-			MetaString text;
-			text.addTxt(MetaString::GENERAL_TXT, 220);
-			attackedStack->addNameReplacement(text);
-			GH.statusbar->write(text.toString());
-			setAlterText = true;
-		}
-	}
-	else if(setAlterText)
-	{
-		GH.statusbar->clear();
-		setAlterText = false;
-	}
-}
-
-void ClickableHex::clickLeft(tribool down, bool previousState)
-{
-	if(!down && hovered && strictHovered) //we've been really clicked!
-	{
-		myInterface->actionsController->handleHex(myNumber, LCLICK);
-	}
-}
-
-void ClickableHex::clickRight(tribool down, bool previousState)
-{
-	const CStack * myst = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber); //stack info
-	if(hovered && strictHovered && myst!=nullptr)
-	{
-		if(!myst->alive()) return;
-		if(down)
-		{
-			GH.pushIntT<CStackWindow>(myst, true);
-		}
-	}
-}
-
 StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	: embedded(Embedded),
 	owner(owner)

+ 3 - 21
client/battle/BattleInterfaceClasses.h

@@ -122,9 +122,9 @@ public:
 	void pause();
 	void play();
 
-	void hover(bool on) override;
-	void clickLeft(tribool down, bool previousState) override; //call-in
-	void clickRight(tribool down, bool previousState) override; //call-in
+	void heroLeftClicked();
+	void heroRightClicked();
+
 	BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
 };
 
@@ -172,24 +172,6 @@ public:
 	void show(SDL_Surface * to = 0) override;
 };
 
-/// Class which stands for a single hex field on a battlefield
-class ClickableHex : public CIntObject
-{
-private:
-	bool setAlterText; //if true, this hex has set alternative text in console and will clean it
-public:
-	ui32 myNumber; //number of hex in commonly used format
-	bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
-	BattleInterface * myInterface; //interface that owns me
-
-	//for user interactions
-	void hover (bool on) override;
-	void mouseMoved (const SDL_MouseMotionEvent &sEvent) override;
-	void clickLeft(tribool down, bool previousState) override;
-	void clickRight(tribool down, bool previousState) override;
-	ClickableHex();
-};
-
 /// Shows the stack queue
 class StackQueue : public CIntObject
 {

+ 4 - 47
client/battle/BattleStacksController.cpp

@@ -73,8 +73,6 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	activeStack(nullptr),
 	stackToActivate(nullptr),
 	selectedStack(nullptr),
-	stackCanCastSpell(false),
-	creatureSpellToCast(uint32_t(-1)),
 	animIDhelper(0)
 {
 	//preparing graphics for displaying amounts of creatures
@@ -738,25 +736,6 @@ void BattleStacksController::activateStack()
 	const CStack * s = getActiveStack();
 	if(!s)
 		return;
-
-	//set casting flag to true if creature can use it to not check it every time
-	const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
-	const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
-	if(s->canCast() && (spellcaster || randomSpellcaster))
-	{
-		stackCanCastSpell = true;
-		if(randomSpellcaster)
-			creatureSpellToCast = -1; //spell will be set later on cast
-		else
-			creatureSpellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
-		//TODO: what if creature can cast BOTH random genie spell and aimed spell?
-		//TODO: faerie dragon type spell should be selected by server
-	}
-	else
-	{
-		stackCanCastSpell = false;
-		creatureSpellToCast = -1;
-	}
 }
 
 void BattleStacksController::setSelectedStack(const CStack *stack)
@@ -779,18 +758,6 @@ bool BattleStacksController::facingRight(const CStack * stack) const
 	return stackFacingRight.at(stack->ID);
 }
 
-bool BattleStacksController::activeStackSpellcaster()
-{
-	return stackCanCastSpell;
-}
-
-SpellID BattleStacksController::activeStackSpellToCast()
-{
-	if (!stackCanCastSpell)
-		return SpellID::NONE;
-	return SpellID(creatureSpellToCast);
-}
-
 Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CStack * stack) const
 {
 	Point ret(-500, -500); //returned value
@@ -910,21 +877,11 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 	const spells::Caster *caster = nullptr;
 	const CSpell *spell = nullptr;
 
-	spells::Mode mode = spells::Mode::HERO;
-
-	if(owner.actionsController->spellcastingModeActive())//hero casts spell
-	{
-		spell = owner.actionsController->selectedSpell().toSpell();
-		caster = owner.getActiveHero();
-	}
-	else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
-	{
-		spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
-		caster = owner.stacksController->getActiveStack();
-		mode = spells::Mode::CREATURE_ACTIVE;
-	}
+	spells::Mode mode = owner.actionsController->getCurrentCastMode();
+	spell = owner.actionsController->getCurrentSpell();
+	caster = owner.actionsController->getCurrentSpellcaster();
 
-	if(caster && spell) //when casting spell
+	if(caster && spell && owner.actionsController->currentActionSpellcasting(hoveredHex) ) //when casting spell
 	{
 		spells::Target target;
 		target.emplace_back(hoveredHex);

+ 0 - 7
client/battle/BattleStacksController.h

@@ -79,10 +79,6 @@ class BattleStacksController
 	/// stack that was selected for multi-target spells - Teleport / Sacrifice
 	const CStack *selectedStack;
 
-	/// if true, active stack could possibly cast some target spell
-	bool stackCanCastSpell;
-	si32 creatureSpellToCast;
-
 	/// for giving IDs for animations
 	ui32 animIDhelper;
 
@@ -123,9 +119,6 @@ public:
 	void startAction(const BattleAction* action);
 	void endAction(const BattleAction* action);
 
-	bool activeStackSpellcaster();
-	SpellID activeStackSpellToCast();
-
 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 
 	void setActiveStack(const CStack *stack);

+ 1 - 1
client/battle/BattleWindow.cpp

@@ -391,7 +391,7 @@ void BattleWindow::bSpellf()
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 
-	if (!owner.myTurn)
+	if (!owner.makingTurn())
 		return;
 
 	auto myHero = owner.currentHero();

+ 1 - 4
client/battle/CreatureAnimation.cpp

@@ -78,10 +78,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 		return baseSpeed;
 
 	case ECreatureAnimType::HOLDING:
-			if ( creature->animation.idleAnimationTime > 0.01)
-				return speed / creature->animation.idleAnimationTime;
-			else
-				return 0.f; // this animation is disabled for current creature
+			return creature->animation.idleAnimationTime;
 
 	case ECreatureAnimType::SHOOT_UP:
 	case ECreatureAnimType::SHOOT_FRONT:

+ 4 - 0
lib/battle/BattleHex.h

@@ -39,6 +39,10 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 	static const si16 CASTLE_BOTTOM_TOWER = -3;
 	static const si16 CASTLE_UPPER_TOWER = -4;
 
+	// hexes for interaction with heroes
+	static const si16 HERO_ATTACKER = 0;
+	static const si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1;
+
 	// helpers for rendering
 	static const si16 HEX_BEFORE_ALL = std::numeric_limits<si16>::min();
 	static const si16 HEX_AFTER_ALL = std::numeric_limits<si16>::max();

+ 22 - 7
lib/battle/CBattleInfoCallback.h

@@ -44,13 +44,28 @@ struct DLL_LINKAGE AttackableTiles
 
 enum class PossiblePlayerBattleAction // actions performed at l-click
 {
-	INVALID = -1, CREATURE_INFO,
-	MOVE_TACTICS, CHOOSE_TACTICS_STACK,
-	MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
-	NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
-	FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
-	CATAPULT, HEAL,
-	AIMED_SPELL_CREATURE
+	INVALID = -1,
+	CREATURE_INFO,
+	HERO_INFO,
+	MOVE_TACTICS,
+	CHOOSE_TACTICS_STACK,
+
+	MOVE_STACK,
+	ATTACK,
+	WALK_AND_ATTACK,
+	ATTACK_AND_RETURN,
+	SHOOT,
+	CATAPULT,
+	HEAL,
+
+	NO_LOCATION,          // massive spells that affect every possible target, automatic casts
+	ANY_LOCATION,
+	OBSTACLE,
+	TELEPORT,
+	SACRIFICE,
+	RANDOM_GENIE_SPELL,   // random spell on a friendly creature
+	FREE_LOCATION,        // used with Force Field and Fire Wall - all tiles affected by spell must be free
+	AIMED_SPELL_CREATURE, // spell targeted at creature
 };
 
 struct DLL_LINKAGE BattleClientInterfaceData

+ 1 - 1
server/CGameHandler.cpp

@@ -4448,7 +4448,7 @@ void CGameHandler::updateGateState()
 	bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty();
 	bool hasStackAtGateInner   = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr;
 	bool hasStackAtGateOuter   = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr;
-	bool hasStackAtGateBridge  = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr;
+	bool hasStackAtGateBridge  = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr;
 	bool hasLongBridge         = gs->curB->town->subID == ETownType::FORTRESS;
 
 	BattleUpdateGateState db;

部分文件因为文件数量过多而无法显示