2023-10-19 16:19:09 +02:00
|
|
|
/*
|
|
|
|
|
* CCursorHandler.cpp, part of VCMI engine
|
|
|
|
|
*
|
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
|
*
|
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "StdInc.h"
|
|
|
|
|
#include "CursorHandler.h"
|
|
|
|
|
|
2025-02-10 21:49:23 +00:00
|
|
|
#include "GameEngine.h"
|
2023-10-19 16:19:09 +02:00
|
|
|
#include "FramerateManager.h"
|
|
|
|
|
#include "../renderSDL/CursorSoftware.h"
|
|
|
|
|
#include "../renderSDL/CursorHardware.h"
|
|
|
|
|
#include "../render/CAnimation.h"
|
|
|
|
|
#include "../render/IImage.h"
|
2024-09-12 21:22:29 +00:00
|
|
|
#include "../render/IScreenHandler.h"
|
2023-10-19 16:19:09 +02:00
|
|
|
#include "../render/IRenderHandler.h"
|
|
|
|
|
|
|
|
|
|
#include "../../lib/CConfigHandler.h"
|
2025-07-11 15:33:48 +03:00
|
|
|
#include "../../lib/json/JsonUtils.h"
|
2023-10-19 16:19:09 +02:00
|
|
|
|
|
|
|
|
std::unique_ptr<ICursor> CursorHandler::createCursor()
|
|
|
|
|
{
|
2025-02-16 22:29:07 +01:00
|
|
|
#if defined(VCMI_MOBILE) || defined(VCMI_PORTMASTER)
|
2023-10-19 16:19:09 +02:00
|
|
|
if (settings["general"]["userRelativePointer"].Bool())
|
|
|
|
|
return std::make_unique<CursorSoftware>();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (settings["video"]["cursor"].String() == "hardware")
|
|
|
|
|
return std::make_unique<CursorHardware>();
|
|
|
|
|
|
|
|
|
|
assert(settings["video"]["cursor"].String() == "software");
|
|
|
|
|
return std::make_unique<CursorSoftware>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CursorHandler::CursorHandler()
|
|
|
|
|
: cursor(createCursor())
|
|
|
|
|
, frameTime(0.f)
|
|
|
|
|
, showing(false)
|
|
|
|
|
, pos(0,0)
|
2025-02-11 15:23:33 +00:00
|
|
|
, dndObject(nullptr)
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
2025-02-11 15:23:33 +00:00
|
|
|
showType = dynamic_cast<CursorSoftware *>(cursor.get()) ? Cursor::ShowType::SOFTWARE : Cursor::ShowType::HARDWARE;
|
|
|
|
|
}
|
2023-10-19 16:19:09 +02:00
|
|
|
|
2025-02-11 15:23:33 +00:00
|
|
|
CursorHandler::~CursorHandler() = default;
|
2023-10-19 16:19:09 +02:00
|
|
|
|
2025-02-11 15:23:33 +00:00
|
|
|
void CursorHandler::init()
|
|
|
|
|
{
|
2025-07-11 15:33:48 +03:00
|
|
|
JsonNode cursorConfig = JsonUtils::assembleFromFiles("config/cursors.json");
|
|
|
|
|
std::vector<AnimationPath> animations;
|
|
|
|
|
|
|
|
|
|
for (const auto & cursorEntry : cursorConfig.Struct())
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
2025-07-11 15:33:48 +03:00
|
|
|
CursorParameters parameters;
|
|
|
|
|
parameters.cursorID = cursorEntry.first;
|
|
|
|
|
parameters.image = ImagePath::fromJson(cursorEntry.second["image"]);
|
|
|
|
|
parameters.animation = AnimationPath::fromJson(cursorEntry.second["animation"]);
|
|
|
|
|
parameters.animationFrameIndex = cursorEntry.second["frame"].Integer();
|
|
|
|
|
parameters.isAnimated = cursorEntry.second["animated"].Bool();
|
|
|
|
|
parameters.pivot.x = cursorEntry.second["pivotX"].Integer();
|
|
|
|
|
parameters.pivot.y = cursorEntry.second["pivotY"].Integer();
|
|
|
|
|
|
|
|
|
|
cursors.push_back(parameters);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-19 16:19:09 +02:00
|
|
|
set(Cursor::Map::POINTER);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
void CursorHandler::set(const std::string & index)
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
|
|
|
|
assert(dndObject == nullptr);
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
if (index == currentCursorID)
|
2023-10-19 16:19:09 +02:00
|
|
|
return;
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
currentCursorID = index;
|
|
|
|
|
currentCursorIndex = 0;
|
|
|
|
|
frameTime = 0;
|
|
|
|
|
for (size_t i = 0; i < cursors.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (cursors[i].cursorID == index)
|
|
|
|
|
{
|
|
|
|
|
currentCursorIndex = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto & currentCursor = cursors.at(currentCursorIndex);
|
|
|
|
|
|
|
|
|
|
if (currentCursor.image.empty())
|
|
|
|
|
{
|
|
|
|
|
if (!loadedAnimations.count(currentCursor.animation))
|
|
|
|
|
loadedAnimations[currentCursor.animation] = ENGINE->renderHandler().loadAnimation(currentCursor.animation, EImageBlitMode::COLORKEY);
|
|
|
|
|
|
|
|
|
|
if (currentCursor.isAnimated)
|
|
|
|
|
cursorImage = loadedAnimations[currentCursor.animation]->getImage(0);
|
|
|
|
|
else
|
|
|
|
|
cursorImage = loadedAnimations[currentCursor.animation]->getImage(currentCursor.animationFrameIndex);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!loadedImages.count(currentCursor.image))
|
|
|
|
|
loadedImages[currentCursor.image] = ENGINE->renderHandler().loadImage(currentCursor.image, EImageBlitMode::COLORKEY);
|
|
|
|
|
cursorImage = loadedImages[currentCursor.image];
|
|
|
|
|
}
|
2023-10-19 16:19:09 +02:00
|
|
|
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::set(Cursor::Map index)
|
|
|
|
|
{
|
2025-07-11 15:33:48 +03:00
|
|
|
constexpr std::array mapCursorNames =
|
|
|
|
|
{
|
|
|
|
|
"mapPointer",
|
|
|
|
|
"mapHourglass",
|
|
|
|
|
"mapHero",
|
|
|
|
|
"mapTown",
|
|
|
|
|
"mapTurn1Move",
|
|
|
|
|
"mapTurn1Attack",
|
|
|
|
|
"mapTurn1Sail",
|
|
|
|
|
"mapTurn1Disembark",
|
|
|
|
|
"mapTurn1Exchange",
|
|
|
|
|
"mapTurn1Visit",
|
|
|
|
|
"mapTurn2Move",
|
|
|
|
|
"mapTurn2Attack",
|
|
|
|
|
"mapTurn2Sail",
|
|
|
|
|
"mapTurn2Disembark",
|
|
|
|
|
"mapTurn2Exchange",
|
|
|
|
|
"mapTurn2Visit",
|
|
|
|
|
"mapTurn3Move",
|
|
|
|
|
"mapTurn3Attack",
|
|
|
|
|
"mapTurn3Sail",
|
|
|
|
|
"mapTurn3Disembark",
|
|
|
|
|
"mapTurn3Exchange",
|
|
|
|
|
"mapTurn3Visit",
|
|
|
|
|
"mapTurn4Move",
|
|
|
|
|
"mapTurn4Attack",
|
|
|
|
|
"mapTurn4Sail",
|
|
|
|
|
"mapTurn4Disembark",
|
|
|
|
|
"mapTurn4Exchange",
|
|
|
|
|
"mapTurn4Visit",
|
|
|
|
|
"mapTurn1SailVisit",
|
|
|
|
|
"mapTurn2SailVisit",
|
|
|
|
|
"mapTurn3SailVisit",
|
|
|
|
|
"mapTurn4SailVisit",
|
|
|
|
|
"mapScrollNorth",
|
|
|
|
|
"mapScrollNorthEast",
|
|
|
|
|
"mapScrollEast",
|
|
|
|
|
"mapScrollSouthEast",
|
|
|
|
|
"mapScrollSouth",
|
|
|
|
|
"mapScrollSouthWest",
|
|
|
|
|
"mapScrollWest",
|
|
|
|
|
"mapScrollNorthWest",
|
|
|
|
|
"UNUSED",
|
|
|
|
|
"mapDimensionDoor",
|
|
|
|
|
"mapScuttleBoat"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
set(mapCursorNames.at(static_cast<int>(index)));
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::set(Cursor::Combat index)
|
|
|
|
|
{
|
2025-07-11 15:33:48 +03:00
|
|
|
constexpr std::array combatCursorNames =
|
|
|
|
|
{
|
|
|
|
|
"combatBlocked",
|
|
|
|
|
"combatMove",
|
|
|
|
|
"combatFly",
|
|
|
|
|
"combatShoot",
|
|
|
|
|
"combatHero",
|
|
|
|
|
"combatQuery",
|
|
|
|
|
"combatPointer",
|
|
|
|
|
"combatHitNorthEast",
|
|
|
|
|
"combatHitEast",
|
|
|
|
|
"combatHitSouthEast",
|
|
|
|
|
"combatHitSouthWest",
|
|
|
|
|
"combatHitWest",
|
|
|
|
|
"combatHitNorthWest",
|
|
|
|
|
"combatHitNorth",
|
|
|
|
|
"combatHitSouth",
|
|
|
|
|
"combatShootPenalty",
|
|
|
|
|
"combatShootCatapult",
|
|
|
|
|
"combatHeal",
|
|
|
|
|
"combatSacrifice",
|
|
|
|
|
"combatTeleport"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
set(combatCursorNames.at(static_cast<int>(index)));
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::set(Cursor::Spellcast index)
|
|
|
|
|
{
|
2025-07-11 15:33:48 +03:00
|
|
|
//Note: this is animated cursor, ignore requested frame and only change type
|
|
|
|
|
set("castSpell");
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
|
|
|
|
|
{
|
|
|
|
|
dndObject = image;
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::dragAndDropCursor (const AnimationPath & path, size_t index)
|
|
|
|
|
{
|
2025-02-10 21:49:23 +00:00
|
|
|
auto anim = ENGINE->renderHandler().loadAnimation(path, EImageBlitMode::COLORKEY);
|
2023-10-19 16:19:09 +02:00
|
|
|
dragAndDropCursor(anim->getImage(index));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::cursorMove(const int & x, const int & y)
|
|
|
|
|
{
|
|
|
|
|
pos.x = x;
|
|
|
|
|
pos.y = y;
|
|
|
|
|
|
|
|
|
|
cursor->setCursorPosition(pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Point CursorHandler::getPivotOffset()
|
|
|
|
|
{
|
|
|
|
|
if (dndObject)
|
|
|
|
|
return dndObject->dimensions() / 2;
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
return cursors.at(currentCursorIndex).pivot;
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<IImage> CursorHandler::getCurrentImage()
|
|
|
|
|
{
|
|
|
|
|
if (dndObject)
|
|
|
|
|
return dndObject;
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
return cursorImage;
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
void CursorHandler::updateAnimatedCursor()
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
|
|
|
|
static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
|
|
|
|
|
|
2025-02-10 21:49:23 +00:00
|
|
|
frameTime += ENGINE->framerate().getElapsedMilliseconds() / 1000.f;
|
2025-07-11 15:33:48 +03:00
|
|
|
int32_t newFrame = currentFrame;
|
|
|
|
|
const auto & animationName = cursors.at(currentCursorIndex).animation;
|
|
|
|
|
const auto & animation = loadedAnimations.at(animationName);
|
2023-10-19 16:19:09 +02:00
|
|
|
|
|
|
|
|
while (frameTime >= frameDisplayDuration)
|
|
|
|
|
{
|
|
|
|
|
frameTime -= frameDisplayDuration;
|
|
|
|
|
newFrame++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (newFrame >= animation->size())
|
|
|
|
|
newFrame -= animation->size();
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
currentFrame = newFrame;
|
|
|
|
|
cursorImage = animation->getImage(currentFrame);
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::render()
|
2025-02-10 17:23:20 +00:00
|
|
|
{
|
|
|
|
|
cursor->render();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::update()
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
|
|
|
|
if(!showing)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-07-11 15:33:48 +03:00
|
|
|
if (cursors.at(currentCursorIndex).isAnimated)
|
|
|
|
|
updateAnimatedCursor();
|
2023-10-19 16:19:09 +02:00
|
|
|
|
2025-02-10 17:23:20 +00:00
|
|
|
cursor->update();
|
2023-10-19 16:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::hide()
|
|
|
|
|
{
|
|
|
|
|
if (!showing)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
showing = false;
|
|
|
|
|
cursor->setVisible(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CursorHandler::show()
|
|
|
|
|
{
|
|
|
|
|
if (showing)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
showing = true;
|
|
|
|
|
cursor->setVisible(true);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 16:22:35 +03:00
|
|
|
Cursor::ShowType CursorHandler::getShowType() const
|
2024-04-21 11:40:24 +08:00
|
|
|
{
|
2024-04-30 11:38:13 +03:00
|
|
|
return showType;
|
2024-04-21 11:40:24 +08:00
|
|
|
}
|
|
|
|
|
|
2024-04-30 16:22:35 +03:00
|
|
|
void CursorHandler::changeCursor(Cursor::ShowType newShowType)
|
2024-04-21 11:40:24 +08:00
|
|
|
{
|
2024-04-30 16:22:35 +03:00
|
|
|
if(newShowType == showType)
|
2024-04-30 11:38:13 +03:00
|
|
|
return;
|
|
|
|
|
|
2024-04-30 16:22:35 +03:00
|
|
|
switch(newShowType)
|
2024-04-30 11:38:13 +03:00
|
|
|
{
|
|
|
|
|
case Cursor::ShowType::SOFTWARE:
|
|
|
|
|
cursor.reset(new CursorSoftware());
|
|
|
|
|
showType = Cursor::ShowType::SOFTWARE;
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
|
|
|
|
break;
|
|
|
|
|
case Cursor::ShowType::HARDWARE:
|
|
|
|
|
cursor.reset(new CursorHardware());
|
|
|
|
|
showType = Cursor::ShowType::HARDWARE;
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-04-21 11:40:24 +08:00
|
|
|
}
|
2024-10-01 19:40:12 +00:00
|
|
|
|
|
|
|
|
void CursorHandler::onScreenResize()
|
|
|
|
|
{
|
|
|
|
|
cursor->setImage(getCurrentImage(), getPivotOffset());
|
|
|
|
|
}
|