Browse Source

Animated cursor for spell selection, removed hardcoded cursor ID's

Ivan Savenko 2 years ago
parent
commit
16c4851d3b

+ 3 - 4
client/CMT.cpp

@@ -468,10 +468,9 @@ int main(int argc, char * argv[])
 	if(!settings["session"]["headless"].Bool())
 	{
 		pomtime.getDiff();
-		CCS->curh = new CCursorHandler();
-		graphics = new Graphics(); // should be before curh->init()
+		graphics = new Graphics(); // should be before curh
 
-		CCS->curh->initCursor();
+		CCS->curh = new CCursorHandler();
 		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
 		pomtime.getDiff();
 
@@ -1581,7 +1580,7 @@ void handleQuit(bool ask)
 
 	if(CSH->client && LOCPLINT && ask)
 	{
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], [](){
 			// Workaround for assertion failure on exit:
 			// handleQuit() is alway called during SDL event processing

+ 27 - 23
client/battle/BattleActionsController.cpp

@@ -60,7 +60,7 @@ void BattleActionsController::endCastingSpell()
 
 		currentSpell = nullptr;
 		spellDestSelectMode = false;
-		CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
+		CCS->curh->set(Cursor::Combat::POINTER);
 
 		if(owner.stacksController->getActiveStack())
 		{
@@ -122,7 +122,7 @@ void BattleActionsController::enterCreatureCastingMode()
 			owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast());
 			owner.stacksController->setSelectedStack(nullptr);
 
-			CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
+			CCS->curh->set(Cursor::Combat::POINTER);
 		}
 	}
 	else
@@ -245,8 +245,8 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 	std::string newConsoleMsg;
 	//used when hovering -> tooltip message and cursor to be set
 	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?
+	bool spellcastingCursor = false;
+	auto cursorFrame = Cursor::Combat::POINTER;
 
 	//used when l-clicking -> action to be called upon the click
 	std::function<void()> realizeAction;
@@ -431,12 +431,12 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			case PossiblePlayerBattleAction::MOVE_STACK:
 				if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
 				{
-					cursorFrame = ECursor::COMBAT_FLY;
+					cursorFrame = Cursor::Combat::FLY;
 					newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
 				}
 				else
 				{
-					cursorFrame = ECursor::COMBAT_MOVE;
+					cursorFrame = Cursor::Combat::MOVE;
 					newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
 				}
 
@@ -484,9 +484,9 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			case PossiblePlayerBattleAction::SHOOT:
 			{
 				if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber))
-					cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
+					cursorFrame = Cursor::Combat::SHOOT_PENALTY;
 				else
-					cursorFrame = ECursor::COMBAT_SHOOT;
+					cursorFrame = Cursor::Combat::SHOOT;
 
 				realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
 				TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
@@ -521,7 +521,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::TELEPORT:
 				newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
-				cursorFrame = ECursor::COMBAT_TELEPORT;
+				cursorFrame = Cursor::Combat::TELEPORT;
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::OBSTACLE:
@@ -531,7 +531,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::SACRIFICE:
 				newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
-				cursorFrame = ECursor::COMBAT_SACRIFICE;
+				cursorFrame = Cursor::Combat::SACRIFICE;
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
@@ -539,24 +539,24 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::HEAL:
-				cursorFrame = ECursor::COMBAT_HEAL;
+				cursorFrame = Cursor::Combat::HEAL;
 				newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
 				realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
 				break;
 			case PossiblePlayerBattleAction::RISE_DEMONS:
-				cursorType = ECursor::SPELLBOOK;
+				spellcastingCursor = true;
 				realizeAction = [=]()
 				{
 					owner.giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
 				};
 				break;
 			case PossiblePlayerBattleAction::CATAPULT:
-				cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
+				cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
 				realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
 				break;
 			case PossiblePlayerBattleAction::CREATURE_INFO:
 			{
-				cursorFrame = ECursor::COMBAT_QUERY;
+				cursorFrame = Cursor::Combat::QUERY;
 				newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
 				realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
 				break;
@@ -569,25 +569,25 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 		{
 			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
-				cursorFrame = ECursor::COMBAT_BLOCKED;
+				cursorFrame = Cursor::Combat::BLOCKED;
 				newConsoleMsg = CGI->generaltexth->allTexts[23];
 				break;
 			case PossiblePlayerBattleAction::TELEPORT:
-				cursorFrame = ECursor::COMBAT_BLOCKED;
+				cursorFrame = Cursor::Combat::BLOCKED;
 				newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
 				break;
 			case PossiblePlayerBattleAction::SACRIFICE:
 				newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
-				cursorFrame = ECursor::COMBAT_BLOCKED;
+				cursorFrame = Cursor::Combat::BLOCKED;
 				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->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.
+					CCS->curh->set(Cursor::Combat::POINTER);
 				else
-					cursorFrame = ECursor::COMBAT_BLOCKED;
+					cursorFrame = Cursor::Combat::BLOCKED;
 				break;
 		}
 	}
@@ -600,8 +600,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			case PossiblePlayerBattleAction::SACRIFICE:
 				break;
 			default:
-				cursorType = ECursor::SPELLBOOK;
-				cursorFrame = 0;
+				spellcastingCursor = true;
 				if (newConsoleMsg.empty() && currentSpell)
 					newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
 				break;
@@ -662,7 +661,12 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 		if (eventType == CIntObject::MOVE)
 		{
 			if (setCursor)
-				CCS->curh->changeGraphic(cursorType, cursorFrame);
+			{
+				if (spellcastingCursor)
+					CCS->curh->set(Cursor::Spellcast::SPELL);
+				else
+					CCS->curh->set(cursorFrame);
+			}
 
 			if (!currentConsoleMsg.empty())
 				owner.controlPanel->console->clearIfMatching(currentConsoleMsg);
@@ -680,7 +684,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			}
 			realizeAction();
 			if (!secondaryTarget) //do not replace teleport or sacrifice cursor
-				CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
+				CCS->curh->set(Cursor::Combat::POINTER);
 			owner.controlPanel->console->clear();
 		}
 	}

+ 1 - 2
client/battle/BattleAnimationClasses.cpp

@@ -417,8 +417,6 @@ MovementAnimation::~MovementAnimation()
 {
 	assert(stack);
 
-	myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
-
 	if(owner.moveSoundHander != -1)
 	{
 		CCS->soundh->stopSound(owner.moveSoundHander);
@@ -456,6 +454,7 @@ bool MovementEndAnimation::init()
 	}
 
 	logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName());
+	myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
 
 	CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving));
 

+ 4 - 4
client/battle/BattleControlPanel.cpp

@@ -101,7 +101,7 @@ void BattleControlPanel::bOptionsf()
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+	CCS->curh->set(Cursor::Map::POINTER);
 
 	GH.pushIntT<BattleOptionsWindow>(owner);
 }
@@ -158,7 +158,7 @@ void BattleControlPanel::bFleef()
 void BattleControlPanel::reallyFlee()
 {
 	owner.giveCommand(EActionType::RETREAT);
-	CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+	CCS->curh->set(Cursor::Map::POINTER);
 }
 
 void BattleControlPanel::reallySurrender()
@@ -170,7 +170,7 @@ void BattleControlPanel::reallySurrender()
 	else
 	{
 		owner.giveCommand(EActionType::SURRENDER);
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 	}
 }
 
@@ -213,7 +213,7 @@ void BattleControlPanel::bSpellf()
 	if(!myHero)
 		return;
 
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+	CCS->curh->set(Cursor::Map::POINTER);
 
 	ESpellCastProblem::ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
 

+ 37 - 37
client/battle/BattleFieldController.cpp

@@ -336,17 +336,17 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 	const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
 	const double hexMidX = hoveredHexPos.x + hoveredHexPos.w/2.0;
 	const double hexMidY = hoveredHexPos.y + hoveredHexPos.h/2.0;
-	const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
+	const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->position().y, cursor->position().y - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
 	const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
 	const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
 
-	std::vector<int> sectorCursor; // From left to bottom left.
-	sectorCursor.push_back(8);
-	sectorCursor.push_back(9);
-	sectorCursor.push_back(10);
-	sectorCursor.push_back(11);
-	sectorCursor.push_back(12);
-	sectorCursor.push_back(7);
+	std::vector<Cursor::Combat> sectorCursor; // From left to bottom left.
+	sectorCursor.push_back(Cursor::Combat::HIT_EAST);
+	sectorCursor.push_back(Cursor::Combat::HIT_SOUTHEAST);
+	sectorCursor.push_back(Cursor::Combat::HIT_SOUTHWEST);
+	sectorCursor.push_back(Cursor::Combat::HIT_WEST);
+	sectorCursor.push_back(Cursor::Combat::HIT_NORTHWEST);
+	sectorCursor.push_back(Cursor::Combat::HIT_NORTHEAST);
 
 	const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
 	bool aboveAttackable = true, belowAttackable = true;
@@ -355,13 +355,13 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 	// Check to the left.
 	if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
 	{
-		sectorCursor[0] = -1;
+		sectorCursor[0] = Cursor::Combat::INVALID;
 	}
 	// Check top left, top right as well as above for 2-hex creatures.
 	if (myNumber/GameConstants::BFIELD_WIDTH == 0)
 	{
-			sectorCursor[1] = -1;
-			sectorCursor[2] = -1;
+			sectorCursor[1] = Cursor::Combat::INVALID;
+			sectorCursor[2] = Cursor::Combat::INVALID;
 			aboveAttackable = false;
 	}
 	else
@@ -380,30 +380,30 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 				attackRow[3] = false;
 
 			if (!(attackRow[0] && attackRow[1]))
-				sectorCursor[1] = -1;
+				sectorCursor[1] = Cursor::Combat::INVALID;
 			if (!(attackRow[1] && attackRow[2]))
 				aboveAttackable = false;
 			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[2] = -1;
+				sectorCursor[2] = Cursor::Combat::INVALID;
 		}
 		else
 		{
 			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[1] = -1;
+				sectorCursor[1] = Cursor::Combat::INVALID;
 			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[2] = -1;
+				sectorCursor[2] = Cursor::Combat::INVALID;
 		}
 	}
 	// Check to the right.
 	if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
 	{
-		sectorCursor[3] = -1;
+		sectorCursor[3] = Cursor::Combat::INVALID;
 	}
 	// Check bottom right, bottom left as well as below for 2-hex creatures.
 	if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
 	{
-		sectorCursor[4] = -1;
-		sectorCursor[5] = -1;
+		sectorCursor[4] = Cursor::Combat::INVALID;
+		sectorCursor[5] = Cursor::Combat::INVALID;
 		belowAttackable = false;
 	}
 	else
@@ -422,18 +422,18 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 				attackRow[3] = false;
 
 			if (!(attackRow[0] && attackRow[1]))
-				sectorCursor[5] = -1;
+				sectorCursor[5] = Cursor::Combat::INVALID;
 			if (!(attackRow[1] && attackRow[2]))
 				belowAttackable = false;
 			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[4] = -1;
+				sectorCursor[4] = Cursor::Combat::INVALID;
 		}
 		else
 		{
 			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[4] = -1;
+				sectorCursor[4] = Cursor::Combat::INVALID;
 			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[5] = -1;
+				sectorCursor[5] = Cursor::Combat::INVALID;
 		}
 	}
 
@@ -441,8 +441,8 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 	int cursorIndex;
 	if (doubleWide)
 	{
-		sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
-		sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
+		sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? Cursor::Combat::HIT_NORTH : Cursor::Combat::INVALID);
+		sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? Cursor::Combat::HIT_SOUTH : Cursor::Combat::INVALID);
 
 		if (sector < 1.5)
 			cursorIndex = static_cast<int>(sector);
@@ -461,7 +461,7 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 	}
 
 	// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
-	if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
+	if (!vstd::contains_if (sectorCursor, [](Cursor::Combat sc) { return sc != Cursor::Combat::INVALID; }))
 	{
 		logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
 		attackingHex = -1;
@@ -471,10 +471,10 @@ void BattleFieldController::setBattleCursor(BattleHex myNumber)
 	// Find the closest direction attackable, starting with the right one.
 	// FIXME: Is this really how the original H3 client does it?
 	int i = 0;
-	while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
+	while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == Cursor::Combat::INVALID) //Why hast thou forsaken me?
 		i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
 	int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
-	cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
+	cursor->set(sectorCursor[index]);
 	switch (index)
 	{
 		case 0:
@@ -505,9 +505,9 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 {
 	//TODO far too much repeating code
 	BattleHex destHex;
-	switch(CCS->curh->frame)
+	switch(CCS->curh->get<Cursor::Combat>())
 	{
-	case 12: //from bottom right
+	case Cursor::Combat::HIT_NORTHWEST: //from bottom right
 		{
 			bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
@@ -526,7 +526,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 7: //from bottom left
+	case Cursor::Combat::HIT_NORTHEAST: //from bottom left
 		{
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
 			if (vstd::contains(occupyableHexes, destHex))
@@ -543,7 +543,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 8: //from left
+	case Cursor::Combat::HIT_EAST: //from left
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::DEFENDER)
 			{
@@ -559,7 +559,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 9: //from top left
+	case Cursor::Combat::HIT_SOUTHEAST: //from top left
 		{
 			destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
 			if(vstd::contains(occupyableHexes, destHex))
@@ -576,7 +576,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 10: //from top right
+	case Cursor::Combat::HIT_SOUTHWEST: //from top right
 		{
 			bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
 			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
@@ -595,7 +595,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 11: //from right
+	case Cursor::Combat::HIT_WEST: //from right
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
@@ -611,7 +611,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 13: //from bottom
+	case Cursor::Combat::HIT_NORTH: //from bottom
 		{
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
 			if(vstd::contains(occupyableHexes, destHex))
@@ -628,7 +628,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			}
 			break;
 		}
-	case 14: //from top
+	case Cursor::Combat::HIT_SOUTH: //from top
 		{
 			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
 			if (vstd::contains(occupyableHexes, destHex))
@@ -646,7 +646,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			break;
 		}
 	}
-	return -1;
+	return BattleHex::INVALID;
 }
 
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const

+ 1 - 1
client/battle/BattleInterface.cpp

@@ -470,7 +470,7 @@ void BattleInterface::battleFinished(const BattleResult& br)
 
 void BattleInterface::displayBattleFinished()
 {
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+	CCS->curh->set(Cursor::Map::POINTER);
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
 	{
 		close();

+ 3 - 3
client/battle/BattleInterfaceClasses.cpp

@@ -221,9 +221,9 @@ void BattleHero::hover(bool on)
 {
 	//TODO: BROKEN CODE
 	if (on)
-		CCS->curh->changeGraphic(ECursor::COMBAT, 5);
+		CCS->curh->set(Cursor::Combat::HERO);
 	else
-		CCS->curh->changeGraphic(ECursor::COMBAT, 0);
+		CCS->curh->set(Cursor::Combat::POINTER);
 }
 
 void BattleHero::clickLeft(tribool down, bool previousState)
@@ -244,7 +244,7 @@ void BattleHero::clickLeft(tribool down, bool previousState)
 		if ( hoveredHex != BattleHex::INVALID )
 			return;
 
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 
 		GH.pushIntT<CSpellWindow>(myHero, owner.getCurrentPlayerInterface());
 	}

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -504,7 +504,7 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
 	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
 
 	// if creature can teleport, e.g Devils - skip movement animation
-	if ( !stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)) )
+	if (!stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)) )
 	{
 		addNewAnim(new MovementAnimation(owner, stack, destHex, distance));
 		owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);

+ 74 - 28
client/gui/CCursorHandler.cpp

@@ -38,13 +38,18 @@ void CCursorHandler::replaceBuffer(CIntObject * payload)
 	updateBuffer(payload);
 }
 
-void CCursorHandler::initCursor()
+CCursorHandler::CCursorHandler()
+	: needUpdate(true)
+	, buffer(nullptr)
+	, cursorLayer(nullptr)
+	, frameTime(0.f)
+	, showing(false)
 {
 	cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40);
 	SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND);
 
 	xpos = ypos = 0;
-	type = ECursor::DEFAULT;
+	type = Cursor::Type::DEFAULT;
 	dndObject = nullptr;
 
 	cursors =
@@ -55,23 +60,28 @@ void CCursorHandler::initCursor()
 		make_unique<CAnimImage>("CRSPELL",  0)
 	};
 
-	currentCursor = cursors.at(int(ECursor::DEFAULT)).get();
+	currentCursor = cursors.at(static_cast<size_t>(Cursor::Type::DEFAULT)).get();
 
 	buffer = CSDL_Ext::newSurface(40,40);
 
 	SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE);
 	SDL_ShowCursor(SDL_DISABLE);
 
-	changeGraphic(ECursor::ADVENTURE, 0);
+	set(Cursor::Map::POINTER);
 }
 
-void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
+Point CCursorHandler::position() const
+{
+	return Point(xpos, ypos);
+}
+
+void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
 {
 	if(type != this->type)
 	{
 		this->type = type;
 		this->frame = index;
-		currentCursor = cursors.at(int(type)).get();
+		currentCursor = cursors.at(static_cast<size_t>(type)).get();
 		currentCursor->setFrame(index);
 	}
 	else if(index != this->frame)
@@ -83,6 +93,27 @@ void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
 	replaceBuffer(currentCursor);
 }
 
+void CCursorHandler::set(Cursor::Default index)
+{
+	changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
+}
+
+void CCursorHandler::set(Cursor::Map index)
+{
+	changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
+}
+
+void CCursorHandler::set(Cursor::Combat index)
+{
+	changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
+}
+
+void CCursorHandler::set(Cursor::Spellcast index)
+{
+	//Note: this is animated cursor, ignore specified frame and only change type
+	changeGraphic(Cursor::Type::SPELLBOOK, frame);
+}
+
 void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
 {
 	dndObject = std::move(object);
@@ -100,54 +131,57 @@ void CCursorHandler::cursorMove(const int & x, const int & y)
 
 void CCursorHandler::shiftPos( int &x, int &y )
 {
-	if(( type == ECursor::COMBAT && frame != ECursor::COMBAT_POINTER) || type == ECursor::SPELLBOOK)
+	if(( type == Cursor::Type::COMBAT && frame != static_cast<size_t>(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK)
 	{
 		x-=16;
 		y-=16;
 
 		// Properly align the melee attack cursors.
-		if (type == ECursor::COMBAT)
+		if (type == Cursor::Type::COMBAT)
 		{
-			switch (frame)
+			switch (static_cast<Cursor::Combat>(frame))
 			{
-			case 7: // Bottom left
+			case Cursor::Combat::HIT_NORTHEAST:
 				x -= 6;
 				y += 16;
 				break;
-			case 8: // Left
+			case Cursor::Combat::HIT_EAST:
 				x -= 16;
 				y += 10;
 				break;
-			case 9: // Top left
+			case Cursor::Combat::HIT_SOUTHEAST:
 				x -= 6;
 				y -= 6;
 				break;
-			case 10: // Top right
+			case Cursor::Combat::HIT_SOUTHWEST:
 				x += 16;
 				y -= 6;
 				break;
-			case 11: // Right
+			case Cursor::Combat::HIT_WEST:
 				x += 16;
 				y += 11;
 				break;
-			case 12: // Bottom right
+			case Cursor::Combat::HIT_NORTHWEST:
 				x += 16;
 				y += 16;
 				break;
-			case 13: // Below
+			case Cursor::Combat::HIT_NORTH:
 				x += 9;
 				y += 16;
 				break;
-			case 14: // Above
+			case Cursor::Combat::HIT_SOUTH:
 				x += 9;
 				y -= 15;
 				break;
 			}
 		}
 	}
-	else if(type == ECursor::ADVENTURE)
+	else if(type == Cursor::Type::ADVENTURE)
 	{
-		if (frame == 0); //to exclude
+		if (frame == 0)
+		{
+			//no-op
+		}
 		else if(frame == 2)
 		{
 			x -= 12;
@@ -219,6 +253,27 @@ void CCursorHandler::render()
 	if(!showing)
 		return;
 
+	if (type == Cursor::Type::SPELLBOOK)
+	{
+		static const float frameDisplayDuration = 0.1f;
+
+		frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
+		size_t newFrame = frame;
+
+		while (frameTime > frameDisplayDuration)
+		{
+			frameTime -= frameDisplayDuration;
+			newFrame++;
+		}
+
+		auto & animation = cursors.at(static_cast<size_t>(type));
+
+		while (newFrame > animation->size())
+			newFrame -= animation->size();
+
+		changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
+	}
+
 	//the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads
 	updateTexture();
 
@@ -250,15 +305,6 @@ void CCursorHandler::updateTexture()
 	}
 }
 
-CCursorHandler::CCursorHandler()
-	: needUpdate(true),
-	buffer(nullptr),
-	cursorLayer(nullptr),
-	showing(false)
-{
-
-}
-
 CCursorHandler::~CCursorHandler()
 {
 	if(buffer)

+ 108 - 81
client/gui/CCursorHandler.h

@@ -12,89 +12,96 @@ class CIntObject;
 class CAnimImage;
 struct SDL_Surface;
 struct SDL_Texture;
+struct Point;
 
-namespace ECursor
+namespace Cursor
 {
-	enum ECursorTypes {
+	enum class Type {
 		ADVENTURE, // set of various cursors for adventure map
 		COMBAT,    // set of various cursors for combat
 		DEFAULT,   // default arrow and hourglass cursors
 		SPELLBOOK  // animated cursor for spellcasting
 	};
 
-	enum EDefaultCursors {
-		DEFAULT_ARROW      = 0,
-		DEFAULT_ARROW_COPY = 1, // probably unused
-		DEFAULT_HOURGLASS  = 2,
+	enum class Default {
+		POINTER      = 0,
+		//ARROW_COPY = 1, // probably unused
+		HOURGLASS  = 2,
 	};
 
-	enum EBattleCursors {
-		COMBAT_BLOCKED        = 0,
-		COMBAT_MOVE           = 1,
-		COMBAT_FLY            = 2,
-		COMBAT_SHOOT          = 3,
-		COMBAT_HERO           = 4,
-		COMBAT_QUERY          = 5,
-		COMBAT_POINTER        = 6,
-		COMBAT_HIT_NORTHEAST  = 7,
-		COMBAT_HIT_EAST       = 8,
-		COMBAT_HIT_SOUTHEAST  = 9,
-		COMBAT_HIT_SOUTHWEST  = 10,
-		COMBAT_HIT_WEST       = 11,
-		COMBAT_HIT_NORTHWEST  = 12,
-		COMBAT_HIT_NORTH      = 13,
-		COMBAT_HIT_SOUTH      = 14,
-		COMBAT_SHOOT_PENALTY  = 15,
-		COMBAT_SHOOT_CATAPULT = 16,
-		COMBAT_HEAL           = 17,
-		COMBAT_SACRIFICE      = 18,
-		COMBAT_TELEPORT       = 19
+	enum class Combat {
+		INVALID        = -1,
+
+		BLOCKED        = 0,
+		MOVE           = 1,
+		FLY            = 2,
+		SHOOT          = 3,
+		HERO           = 4,
+		QUERY          = 5,
+		POINTER        = 6,
+		HIT_NORTHEAST  = 7,
+		HIT_EAST       = 8,
+		HIT_SOUTHEAST  = 9,
+		HIT_SOUTHWEST  = 10,
+		HIT_WEST       = 11,
+		HIT_NORTHWEST  = 12,
+		HIT_NORTH      = 13,
+		HIT_SOUTH      = 14,
+		SHOOT_PENALTY  = 15,
+		SHOOT_CATAPULT = 16,
+		HEAL           = 17,
+		SACRIFICE      = 18,
+		TELEPORT       = 19
 	};
 
-	enum EAdventureCursors {
-		ADV_ARROW            =  0,
-		ADV_HOURGLASS        =  1,
-		ADV_HERO             =  2,
-		ADV_TOWN             =  3,
-		ADV_T1_MOVE          =  4,
-		ADV_T1_ATTACK        =  5,
-		ADV_T1_SAIL          =  6,
-		ADV_T1_DISEMBARK     =  7,
-		ADV_T1_EXCHANGE      =  8,
-		ADV_T1_VISIT         =  9,
-		ADV_T2_MOVE          = 10,
-		ADV_T2_ATTACK        = 11,
-		ADV_T2_SAIL          = 12,
-		ADV_T2_DISEMBARK     = 13,
-		ADV_T2_EXCHANGE      = 14,
-		ADV_T2_VISIT         = 15,
-		ADV_T3_MOVE          = 16,
-		ADV_T3_ATTACK        = 17,
-		ADV_T3_SAIL          = 18,
-		ADV_T3_DISEMBARK     = 19,
-		ADV_T3_EXCHANGE      = 20,
-		ADV_T3_VISIT         = 21,
-		ADV_T4_MOVE          = 22,
-		ADV_T4_ATTACK        = 23,
-		ADV_T4_SAIL          = 24,
-		ADV_T4_DISEMBARK     = 25,
-		ADV_T4_EXCHANGE      = 26,
-		ADV_T4_VISIT         = 27,
-		ADV_T1_SAIL_VISIT    = 28,
-		ADV_T2_SAIL_VISIT    = 29,
-		ADV_T3_SAIL_VISIT    = 30,
-		ADV_T4_SAIL_VISIT    = 31,
-		ADV_SCROLL_NORTH     = 32,
-		ADV_SCROLL_NORTHEAST = 33,
-		ADV_SCROLL_EAST      = 34,
-		ADV_SCROLL_SOUTHEAST = 35,
-		ADV_SCROLL_SOUTH     = 36,
-		ADV_SCROLL_SOUTHWEST = 37,
-		ADV_SCROLL_WEST      = 38,
-		ADV_SCROLL_NORTHWEST = 39,
-		ADV_ARROW_COPY       = 40, // probably unused
-		ADV_TELEPORT         = 41,
-		ADV_SCUTTLE_SHIP     = 42
+	enum class Map {
+		POINTER          =  0,
+		HOURGLASS        =  1,
+		HERO             =  2,
+		TOWN             =  3,
+		T1_MOVE          =  4,
+		T1_ATTACK        =  5,
+		T1_SAIL          =  6,
+		T1_DISEMBARK     =  7,
+		T1_EXCHANGE      =  8,
+		T1_VISIT         =  9,
+		T2_MOVE          = 10,
+		T2_ATTACK        = 11,
+		T2_SAIL          = 12,
+		T2_DISEMBARK     = 13,
+		T2_EXCHANGE      = 14,
+		T2_VISIT         = 15,
+		T3_MOVE          = 16,
+		T3_ATTACK        = 17,
+		T3_SAIL          = 18,
+		T3_DISEMBARK     = 19,
+		T3_EXCHANGE      = 20,
+		T3_VISIT         = 21,
+		T4_MOVE          = 22,
+		T4_ATTACK        = 23,
+		T4_SAIL          = 24,
+		T4_DISEMBARK     = 25,
+		T4_EXCHANGE      = 26,
+		T4_VISIT         = 27,
+		T1_SAIL_VISIT    = 28,
+		T2_SAIL_VISIT    = 29,
+		T3_SAIL_VISIT    = 30,
+		T4_SAIL_VISIT    = 31,
+		SCROLL_NORTH     = 32,
+		SCROLL_NORTHEAST = 33,
+		SCROLL_EAST      = 34,
+		SCROLL_SOUTHEAST = 35,
+		SCROLL_SOUTH     = 36,
+		SCROLL_SOUTHWEST = 37,
+		SCROLL_WEST      = 38,
+		SCROLL_NORTHWEST = 39,
+		//POINTER_COPY       = 40, // probably unused
+		TELEPORT         = 41,
+		SCUTTLE_BOAT     = 42
+	};
+
+	enum class Spellcast {
+		SPELL = 0,
 	};
 }
 
@@ -119,19 +126,20 @@ class CCursorHandler final
 	void shiftPos( int &x, int &y );
 
 	void updateTexture();
-public:
-	/// position of cursor
-	int xpos, ypos;
 
 	/// Current cursor
-	ECursor::ECursorTypes type;
+	Cursor::Type type;
 	size_t frame;
+	float frameTime;
 
-	/// inits cursorHandler - run only once, it's not memleak-proof (rev 1333)
-	void initCursor();
+	void changeGraphic(Cursor::Type type, size_t index);
 
-	/// changes cursor graphic for type type (0 - adventure, 1 - combat, 2 - default, 3 - spellbook) and frame index (not used for type 3)
-	void changeGraphic(ECursor::ECursorTypes type, int index);
+	/// position of cursor
+	int xpos, ypos;
+
+public:
+	CCursorHandler();
+	~CCursorHandler();
 
 	/**
 	 * Replaces the cursor with a custom image.
@@ -141,6 +149,27 @@ public:
 	 */
 	void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
 
+	/// Returns current position of the cursor
+	Point position() const;
+
+	/// Changes cursor to specified index
+	void set(Cursor::Default index);
+	void set(Cursor::Map index);
+	void set(Cursor::Combat index);
+	void set(Cursor::Spellcast index);
+
+	/// Returns current index of cursor
+	template<typename Index>
+	Index get()
+	{
+		assert((std::is_same<Index, Cursor::Default>::value   )|| type != Cursor::Type::DEFAULT );
+		assert((std::is_same<Index, Cursor::Map>::value       )|| type != Cursor::Type::ADVENTURE );
+		assert((std::is_same<Index, Cursor::Combat>::value    )|| type != Cursor::Type::COMBAT );
+		assert((std::is_same<Index, Cursor::Spellcast>::value )|| type != Cursor::Type::SPELLBOOK );
+
+		return static_cast<Index>(frame);
+	}
+
 	void render();
 
 	void hide() { showing=false; };
@@ -151,6 +180,4 @@ public:
 	/// Move cursor to screen center
 	void centerCursor();
 
-	CCursorHandler();
-	~CCursorHandler();
 };

+ 1 - 1
client/gui/CGuiHandler.cpp

@@ -121,7 +121,7 @@ void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
 	if(!listInt.empty())
 		listInt.front()->deactivate();
 	listInt.push_front(newInt);
-	CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+	CCS->curh->set(Cursor::Map::POINTER);
 	newInt->activate();
 	objsToBlit.push_back(newInt);
 	totalRedraw();

+ 47 - 37
client/windows/CAdvmapInterface.cpp

@@ -68,25 +68,25 @@ static void setScrollingCursor(ui8 direction)
 	if(direction & CAdvMapInt::RIGHT)
 	{
 		if(direction & CAdvMapInt::UP)
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 33);
+			CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
 		else if(direction & CAdvMapInt::DOWN)
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 35);
+			CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
 		else
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 34);
+			CCS->curh->set(Cursor::Map::SCROLL_EAST);
 	}
 	else if(direction & CAdvMapInt::LEFT)
 	{
 		if(direction & CAdvMapInt::UP)
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 39);
+			CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
 		else if(direction & CAdvMapInt::DOWN)
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 37);
+			CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
 		else
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 38);
+			CCS->curh->set(Cursor::Map::SCROLL_WEST);
 	}
 	else if(direction & CAdvMapInt::UP)
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 32);
+		CCS->curh->set(Cursor::Map::SCROLL_NORTH);
 	else if(direction & CAdvMapInt::DOWN)
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 36);
+		CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
 }
 
 CTerrainRect::CTerrainRect()
@@ -231,7 +231,7 @@ void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
 
 	if(tHovered != pom) //tile outside the map
 	{
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 		return;
 	}
 
@@ -247,7 +247,7 @@ void CTerrainRect::hover(bool on)
 	if (!on)
 	{
 		adventureInt->statusbar->clear();
-		CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+		CCS->curh->set(Cursor::Map::POINTER);
 	}
 	//Hoverable::hover(on);
 }
@@ -968,7 +968,7 @@ void CAdvMapInt::deactivate()
 	{
 		scrollingDir = 0;
 
-		CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+		CCS->curh->set(Cursor::Map::POINTER);
 		activeMapPanel->deactivate();
 		if (mode == EAdvMapMode::NORMAL)
 		{
@@ -1125,7 +1125,7 @@ void CAdvMapInt::handleMapScrollingUpdate()
 		}
 		else if(scrollingState)
 		{
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+			CCS->curh->set(Cursor::Map::POINTER);
 			scrollingState = false;
 		}
 	}
@@ -1138,7 +1138,7 @@ void CAdvMapInt::handleSwipeUpdate()
 		auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
 		position.x = fixedPos.x;
 		position.y = fixedPos.y;
-		CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 		updateScreen = true;
 		minimap.redraw();
 		swipeMovementRequested = false;
@@ -1656,7 +1656,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		return;
 	if(!LOCPLINT->cb->isVisible(mapPos))
 	{
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+		CCS->curh->set(Cursor::Map::POINTER);
 		statusbar->clear();
 		return;
 	}
@@ -1682,18 +1682,18 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		{
 		case SpellID::SCUTTLE_BOAT:
 			if(objAtTile && objAtTile->ID == Obj::BOAT)
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 42);
+				CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
 			else
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+				CCS->curh->set(Cursor::Map::POINTER);
 			return;
 		case SpellID::DIMENSION_DOOR:
 			{
 				const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
 				int3 hpos = selection->getSightCenter();
 				if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 41);
+					CCS->curh->set(Cursor::Map::TELEPORT);
 				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+					CCS->curh->set(Cursor::Map::POINTER);
 				return;
 			}
 		}
@@ -1704,17 +1704,25 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		if(objAtTile)
 		{
 			if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
+				CCS->curh->set(Cursor::Map::TOWN);
 			else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
+				CCS->curh->set(Cursor::Map::HERO);
 			else
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+				CCS->curh->set(Cursor::Map::POINTER);
 		}
 		else
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+			CCS->curh->set(Cursor::Map::POINTER);
 	}
 	else if(const CGHeroInstance * h = curHero())
 	{
+		std::array<Cursor::Map, 4> cursorMove      = { Cursor::Map::T1_MOVE,       Cursor::Map::T2_MOVE,       Cursor::Map::T3_MOVE,       Cursor::Map::T4_MOVE,       };
+		std::array<Cursor::Map, 4> cursorAttack    = { Cursor::Map::T1_ATTACK,     Cursor::Map::T2_ATTACK,     Cursor::Map::T3_ATTACK,     Cursor::Map::T4_ATTACK,     };
+		std::array<Cursor::Map, 4> cursorSail      = { Cursor::Map::T1_SAIL,       Cursor::Map::T2_SAIL,       Cursor::Map::T3_SAIL,       Cursor::Map::T4_SAIL,       };
+		std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK,  Cursor::Map::T2_DISEMBARK,  Cursor::Map::T3_DISEMBARK,  Cursor::Map::T4_DISEMBARK,  };
+		std::array<Cursor::Map, 4> cursorExchange  = { Cursor::Map::T1_EXCHANGE,   Cursor::Map::T2_EXCHANGE,   Cursor::Map::T3_EXCHANGE,   Cursor::Map::T4_EXCHANGE,   };
+		std::array<Cursor::Map, 4> cursorVisit     = { Cursor::Map::T1_VISIT,      Cursor::Map::T2_VISIT,      Cursor::Map::T3_VISIT,      Cursor::Map::T4_VISIT,      };
+		std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
+
 		const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
 		assert(pnode);
 
@@ -1725,9 +1733,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 		case CGPathNode::NORMAL:
 		case CGPathNode::TELEPORT_NORMAL:
 			if(pnode->layer == EPathfindingLayer::LAND)
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6);
+				CCS->curh->set(cursorMove[turns]);
 			else
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
+				CCS->curh->set(cursorSailVisit[turns]);
 			break;
 
 		case CGPathNode::VISIT:
@@ -1736,48 +1744,48 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 			if(objAtTile && objAtTile->ID == Obj::HERO)
 			{
 				if(selection == objAtTile)
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
+					CCS->curh->set(Cursor::Map::HERO);
 				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6);
+					CCS->curh->set(cursorExchange[turns]);
 			}
 			else if(pnode->layer == EPathfindingLayer::LAND)
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
+				CCS->curh->set(cursorVisit[turns]);
 			else
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
+				CCS->curh->set(cursorSailVisit[turns]);
 			break;
 
 		case CGPathNode::BATTLE:
 		case CGPathNode::TELEPORT_BATTLE:
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
+			CCS->curh->set(cursorAttack[turns]);
 			break;
 
 		case CGPathNode::EMBARK:
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6);
+			CCS->curh->set(cursorSail[turns]);
 			break;
 
 		case CGPathNode::DISEMBARK:
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6);
+			CCS->curh->set(cursorDisembark[turns]);
 			break;
 
 		default:
 			if(objAtTile && objRelations != PlayerRelations::ENEMIES)
 			{
 				if(objAtTile->ID == Obj::TOWN)
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
+					CCS->curh->set(Cursor::Map::TOWN);
 				else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
+					CCS->curh->set(Cursor::Map::HERO);
 				else
-					CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+					CCS->curh->set(Cursor::Map::POINTER);
 			}
 			else
-				CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+				CCS->curh->set(Cursor::Map::POINTER);
 			break;
 		}
 	}
 
 	if(ourInaccessibleShipyard(objAtTile))
 	{
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 6);
+		CCS->curh->set(Cursor::Map::T1_SAIL);
 	}
 }
 
@@ -1857,7 +1865,9 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob
 {
 	const IShipyard *ret = IShipyard::castFrom(obj);
 
-	if(!ret || obj->tempOwner != player || CCS->curh->type || (CCS->curh->frame != 6 && CCS->curh->frame != 0))
+	if(!ret ||
+		obj->tempOwner != player ||
+		(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
 		return nullptr;
 
 	return ret;