1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

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

This commit is contained in:
Ivan Savenko 2022-12-18 22:32:07 +02:00
parent 53f6b7bd32
commit 16c4851d3b
12 changed files with 309 additions and 224 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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());
}

View File

@ -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);

View File

@ -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)

View File

@ -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:
/// Current cursor
Cursor::Type type;
size_t frame;
float frameTime;
void changeGraphic(Cursor::Type type, size_t index);
/// position of cursor
int xpos, ypos;
/// Current cursor
ECursor::ECursorTypes type;
size_t frame;
/// inits cursorHandler - run only once, it's not memleak-proof (rev 1333)
void initCursor();
/// 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);
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();
};

View File

@ -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();

View File

@ -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;