diff options
Diffstat (limited to 'src/progs/manaplus/gui')
-rw-r--r-- | src/progs/manaplus/gui/viewport.cpp | 1148 | ||||
-rw-r--r-- | src/progs/manaplus/gui/viewport.h | 249 |
2 files changed, 1397 insertions, 0 deletions
diff --git a/src/progs/manaplus/gui/viewport.cpp b/src/progs/manaplus/gui/viewport.cpp new file mode 100644 index 000000000..de7571c7c --- /dev/null +++ b/src/progs/manaplus/gui/viewport.cpp @@ -0,0 +1,1148 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2017 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "progs/manaplus/gui/viewport.h" + +#include "actormanager.h" +#include "configuration.h" +#include "game.h" +#include "settings.h" +#include "sdlshared.h" +#include "textmanager.h" + +#include "being/flooritem.h" +#include "being/localplayer.h" +#include "being/playerinfo.h" + +#include "enums/resources/map/blockmask.h" +#include "enums/resources/map/mapitemtype.h" + +#include "gui/gui.h" +#include "gui/popupmanager.h" +#include "gui/userpalette.h" + +#include "gui/fonts/font.h" + +#include "gui/popups/beingpopup.h" +#include "gui/popups/popupmenu.h" +#include "gui/popups/textpopup.h" + +#include "gui/windows/ministatuswindow.h" + +#include "input/inputmanager.h" + +#include "utils/checkutils.h" +#include "utils/foreach.h" + +#include "resources/map/map.h" +#include "resources/map/mapitem.h" +#include "resources/map/speciallayer.h" + +#include "debug.h" + +Viewport *viewport = nullptr; + +extern volatile int tick_time; + +Viewport::Viewport() : + WindowContainer(nullptr), + MouseListener(), + ConfigListener(), + mMouseX(0), + mMouseY(0), + mMap(nullptr), + mHoverBeing(nullptr), + mHoverItem(nullptr), + mHoverSign(nullptr), + mScrollRadius(config.getIntValue("ScrollRadius")), + mScrollLaziness(config.getIntValue("ScrollLaziness")), + mScrollCenterOffsetX(config.getIntValue("ScrollCenterOffsetX")), + mScrollCenterOffsetY(config.getIntValue("ScrollCenterOffsetY")), + mMousePressX(0), + mMousePressY(0), + mPixelViewX(0), + mPixelViewY(0), + mMidTileX(0), + mMidTileY(0), + mViewXmax(0), + mViewYmax(0), + mLocalWalkTime(-1), + mCameraRelativeX(0), + mCameraRelativeY(0), + mShowBeingPopup(config.getBoolValue("showBeingPopup")), + mSelfMouseHeal(config.getBoolValue("selfMouseHeal")), + mEnableLazyScrolling(config.getBoolValue("enableLazyScrolling")), + mMouseDirectionMove(config.getBoolValue("mouseDirectionMove")), + mLongMouseClick(config.getBoolValue("longmouseclick")), + mAllowMoveByMouse(config.getBoolValue("allowMoveByMouse")), + mMouseClicked(false), + mPlayerFollowMouse(false) +{ + setOpaque(Opaque_false); + addMouseListener(this); + + config.addListener("ScrollLaziness", this); + config.addListener("ScrollRadius", this); + config.addListener("showBeingPopup", this); + config.addListener("selfMouseHeal", this); + config.addListener("enableLazyScrolling", this); + config.addListener("mouseDirectionMove", this); + config.addListener("longmouseclick", this); + config.addListener("allowMoveByMouse", this); + + setFocusable(true); + updateMidVars(); +} + +Viewport::~Viewport() +{ + config.removeListeners(this); + CHECKLISTENERS +} + +void Viewport::setMap(Map *const map) +{ + if ((mMap != nullptr) && (map != nullptr)) + map->setDrawLayersFlags(mMap->getDrawLayersFlags()); + mMap = map; + updateMaxVars(); +} + +void Viewport::draw(Graphics *const graphics) +{ + BLOCK_START("Viewport::draw 1") + static int lastTick = tick_time; + + if ((mMap == nullptr) || (localPlayer == nullptr)) + { + graphics->setColor(Color(64, 64, 64)); + graphics->fillRectangle( + Rect(0, 0, getWidth(), getHeight())); + BLOCK_END("Viewport::draw 1") + return; + } + + // Avoid freaking out when tick_time overflows + if (tick_time < lastTick) + lastTick = tick_time; + + // Calculate viewpoint + + const int player_x = localPlayer->mPixelX - mMidTileX; + const int player_y = localPlayer->mPixelY - mMidTileY; + + if (mScrollLaziness < 1) + mScrollLaziness = 1; // Avoids division by zero + + if (mEnableLazyScrolling) + { + int cnt = 0; + + // Apply lazy scrolling + while (lastTick < tick_time && cnt < mapTileSize) + { + if (player_x > mPixelViewX + mScrollRadius) + { + mPixelViewX += CAST_S32( + static_cast<float>(player_x + - mPixelViewX - mScrollRadius) / + static_cast<float>(mScrollLaziness)); + } + if (player_x < mPixelViewX - mScrollRadius) + { + mPixelViewX += CAST_S32( + static_cast<float>(player_x + - mPixelViewX + mScrollRadius) / + static_cast<float>(mScrollLaziness)); + } + if (player_y > mPixelViewY + mScrollRadius) + { + mPixelViewY += CAST_S32( + static_cast<float>(player_y + - mPixelViewY - mScrollRadius) / + static_cast<float>(mScrollLaziness)); + } + if (player_y < mPixelViewY - mScrollRadius) + { + mPixelViewY += CAST_S32( + static_cast<float>(player_y + - mPixelViewY + mScrollRadius) / + static_cast<float>(mScrollLaziness)); + } + lastTick ++; + cnt ++; + } + + // Auto center when player is off screen + if (cnt > 30 || player_x - mPixelViewX + > graphics->mWidth / 2 || mPixelViewX + - player_x > graphics->mWidth / 2 || mPixelViewY + - player_y > graphics->getHeight() / 2 || player_y + - mPixelViewY > graphics->getHeight() / 2) + { + if (player_x <= 0 || player_y <= 0) + { + logger->log("incorrect player position: %d, %d, %d, %d", + player_x, player_y, mPixelViewX, mPixelViewY); + logger->log("tile position: %d, %d", + localPlayer->getTileX(), localPlayer->getTileY()); + } + mPixelViewX = player_x; + mPixelViewY = player_y; + } + } + else + { + mPixelViewX = player_x; + mPixelViewY = player_y; + } + + if (mPixelViewX < 0) + mPixelViewX = 0; + if (mPixelViewY < 0) + mPixelViewY = 0; + if (mPixelViewX > mViewXmax) + mPixelViewX = mViewXmax; + if (mPixelViewY > mViewYmax) + mPixelViewY = mViewYmax; + + // Draw tiles and sprites + mMap->draw(graphics, mPixelViewX, mPixelViewY); + + const MapTypeT drawType = settings.mapDrawType; + if (drawType != MapType::NORMAL) + { + if (drawType != MapType::SPECIAL4) + { + mMap->drawCollision(graphics, mPixelViewX, + mPixelViewY, drawType); + } + if (drawType == MapType::DEBUGTYPE) + drawDebugPath(graphics); + } + + if (localPlayer->getCheckNameSetting()) + { + localPlayer->setCheckNameSetting(false); + localPlayer->setName(localPlayer->getName()); + } + + // Draw text + if (textManager != nullptr) + textManager->draw(graphics, mPixelViewX, mPixelViewY); + + // Draw player names, speech, and emotion sprite as needed + const ActorSprites &actors = actorManager->getAll(); + FOR_EACH (ActorSpritesIterator, it, actors) + { + if ((*it)->getType() == ActorType::FloorItem) + continue; + Being *const b = static_cast<Being*>(*it); + b->drawEmotion(graphics, mPixelViewX, mPixelViewY); + b->drawSpeech(mPixelViewX, mPixelViewY); + } + + if (miniStatusWindow != nullptr) + miniStatusWindow->drawIcons(graphics); + + // Draw contained widgets + WindowContainer::draw(graphics); + BLOCK_END("Viewport::draw 1") +} + +void Viewport::safeDraw(Graphics *const graphics) +{ + Viewport::draw(graphics); +} + +void Viewport::logic() +{ + BLOCK_START("Viewport::logic") + // Make the player follow the mouse position + // if the mouse is dragged elsewhere than in a window. + Gui::getMouseState(mMouseX, mMouseY); + BLOCK_END("Viewport::logic") +} + +void Viewport::followMouse() +{ + if (gui == nullptr) + return; + const MouseStateType button = Gui::getMouseState(mMouseX, mMouseY); + // If the left button is dragged + if (mPlayerFollowMouse && ((button & SDL_BUTTON(1)) != 0)) + { + // We create a mouse event and send it to mouseDragged. + const MouseEvent event(nullptr, + MouseEventType::DRAGGED, + MouseButton::LEFT, + mMouseX, + mMouseY, + 0); + + walkByMouse(event); + } +} + +void Viewport::drawDebugPath(Graphics *const graphics) +{ + if (localPlayer == nullptr || + userPalette == nullptr || + actorManager == nullptr || + mMap == nullptr || + gui == nullptr) + { + return; + } + + Gui::getMouseState(mMouseX, mMouseY); + + static Path debugPath; + static Vector lastMouseDestination = Vector(0.0F, 0.0F); + const int mousePosX = mMouseX + mPixelViewX; + const int mousePosY = mMouseY + mPixelViewY; + Vector mouseDestination(mousePosX, mousePosY); + + if (mouseDestination.x != lastMouseDestination.x + || mouseDestination.y != lastMouseDestination.y) + { + debugPath = mMap->findPath( + CAST_S32(localPlayer->mPixelX - mapTileSize / 2) / mapTileSize, + CAST_S32(localPlayer->mPixelY - mapTileSize) / mapTileSize, + mousePosX / mapTileSize, + mousePosY / mapTileSize, + localPlayer->getBlockWalkMask(), + 500); + lastMouseDestination = mouseDestination; + } + drawPath(graphics, debugPath, userPalette->getColorWithAlpha( + UserColorId::ROAD_POINT)); + + const ActorSprites &actors = actorManager->getAll(); + FOR_EACH (ActorSpritesConstIterator, it, actors) + { + const Being *const being = dynamic_cast<const Being*>(*it); + if ((being != nullptr) && being != localPlayer) + { + const Path &beingPath = being->getPath(); + drawPath(graphics, beingPath, userPalette->getColorWithAlpha( + UserColorId::ROAD_POINT)); + } + } +} + +void Viewport::drawPath(Graphics *const graphics, + const Path &path, + const Color &color) const +{ + graphics->setColor(color); + Font *const font = getFont(); + + int cnt = 1; + FOR_EACH (Path::const_iterator, i, path) + { + const int squareX = i->x * mapTileSize - mPixelViewX + 12; + const int squareY = i->y * mapTileSize - mPixelViewY + 12; + + graphics->fillRectangle(Rect(squareX, squareY, 8, 8)); + if (mMap != nullptr) + { + const std::string str = toString(cnt); + font->drawString(graphics, + color, color, + str, + squareX + 4 - font->getWidth(str) / 2, + squareY + 12); + } + cnt ++; + } +} + +bool Viewport::openContextMenu(const MouseEvent &event) +{ + mPlayerFollowMouse = false; + const int eventX = event.getX(); + const int eventY = event.getY(); + if (popupMenu == nullptr) + return false; + if (mHoverBeing != nullptr) + { + validateSpeed(); + if (actorManager != nullptr) + { + STD_VECTOR<ActorSprite*> beings; + const int x = mMouseX + mPixelViewX; + const int y = mMouseY + mPixelViewY; + actorManager->findBeingsByPixel(beings, x, y, AllPlayers_true); + if (beings.size() > 1) + popupMenu->showPopup(eventX, eventY, beings); + else + popupMenu->showPopup(eventX, eventY, mHoverBeing); + return true; + } + } + else if (mHoverItem != nullptr) + { + validateSpeed(); + popupMenu->showPopup(eventX, eventY, mHoverItem); + return true; + } + else if (mHoverSign != nullptr) + { + validateSpeed(); + popupMenu->showPopup(eventX, eventY, mHoverSign); + return true; + } + else if (settings.cameraMode != 0u) + { + if (mMap == nullptr) + return false; + popupMenu->showMapPopup(eventX, eventY, + (mMouseX + mPixelViewX) / mMap->getTileWidth(), + (mMouseY + mPixelViewY) / mMap->getTileHeight(), + false); + return true; + } + return false; +} + +bool Viewport::leftMouseAction() +{ + const bool stopAttack = inputManager.isActionActive( + InputAction::STOP_ATTACK); + // Interact with some being + if (mHoverBeing != nullptr) + { + if (!mHoverBeing->isAlive()) + return true; + + if (mHoverBeing->canTalk()) + { + validateSpeed(); + mHoverBeing->talkTo(); + return true; + } + + const ActorTypeT type = mHoverBeing->getType(); + switch (type) + { + case ActorType::Player: + validateSpeed(); + if (actorManager != nullptr) + { +#ifdef TMWA_SUPPORT + if (localPlayer != mHoverBeing || mSelfMouseHeal) + actorManager->heal(mHoverBeing); +#endif // TMWA_SUPPORT + + if (localPlayer == mHoverBeing && + mHoverItem != nullptr) + { + localPlayer->pickUp(mHoverItem); + } + return true; + } + break; + case ActorType::Monster: + case ActorType::Npc: + case ActorType::SkillUnit: + if (!stopAttack) + { + if (localPlayer->withinAttackRange(mHoverBeing) || + inputManager.isActionActive(InputAction::ATTACK)) + { + validateSpeed(); + if (!mStatsReUpdated && localPlayer != mHoverBeing) + { + localPlayer->attack(mHoverBeing, + !inputManager.isActionActive( + InputAction::STOP_ATTACK)); + return true; + } + } + else if (!inputManager.isActionActive( + InputAction::ATTACK)) + { + validateSpeed(); + if (!mStatsReUpdated && localPlayer != mHoverBeing) + { + localPlayer->setGotoTarget(mHoverBeing); + return true; + } + } + } + break; + case ActorType::FloorItem: + case ActorType::Portal: + case ActorType::Pet: + case ActorType::Mercenary: + case ActorType::Homunculus: + case ActorType::Elemental: + break; + case ActorType::Unknown: + case ActorType::Avatar: + default: + reportAlways("Left click on unknown actor type: %d", + CAST_S32(type)); + break; + } + } + // Picks up a item if we clicked on one + if (mHoverItem != nullptr) + { + validateSpeed(); + localPlayer->pickUp(mHoverItem); + } + else if (stopAttack) + { + if (mMap != nullptr) + { + const int mouseTileX = (mMouseX + mPixelViewX) + / mMap->getTileWidth(); + const int mouseTileY = (mMouseY + mPixelViewY) + / mMap->getTileHeight(); + inputManager.executeChatCommand(InputAction::PET_MOVE, + strprintf("%d %d", mouseTileX, mouseTileY), + nullptr); + } + return true; + } + // Just walk around + else if (!inputManager.isActionActive(InputAction::ATTACK) && + localPlayer->canMove()) + { + validateSpeed(); + localPlayer->stopAttack(); + localPlayer->cancelFollow(); + mPlayerFollowMouse = mAllowMoveByMouse; + if (mPlayerFollowMouse) + { + // Make the player go to the mouse position + followMouse(); + } + } + return false; +} + +void Viewport::mousePressed(MouseEvent &event) +{ + if (event.getSource() != this || event.isConsumed()) + return; + + // Check if we are alive and kickin' + if ((mMap == nullptr) || (localPlayer == nullptr)) + return; + + // Check if we are busy + // if commented, allow context menu if npc dialog open + if (PlayerInfo::isTalking()) + { + mMouseClicked = false; + return; + } + + mMouseClicked = true; + + mMousePressX = event.getX(); + mMousePressY = event.getY(); + const MouseButtonT eventButton = event.getButton(); + const int pixelX = mMousePressX + mPixelViewX; + const int pixelY = mMousePressY + mPixelViewY; + + // Right click might open a popup + if (eventButton == MouseButton::RIGHT) + { + if (openContextMenu(event)) + return; + } + + // If a popup is active, just remove it + if (PopupManager::isPopupMenuVisible()) + { + mPlayerFollowMouse = false; + PopupManager::hidePopupMenu(); + return; + } + + // Left click can cause different actions + if (!mLongMouseClick && eventButton == MouseButton::LEFT) + { + if (leftMouseAction()) + { + mPlayerFollowMouse = false; + return; + } + } + else if (eventButton == MouseButton::MIDDLE) + { + mPlayerFollowMouse = false; + validateSpeed(); + // Find the being nearest to the clicked position + if (actorManager != nullptr) + { + Being *const target = actorManager->findNearestLivingBeing( + pixelX, pixelY, 20, ActorType::Monster, nullptr); + + if (target != nullptr) + localPlayer->setTarget(target); + } + } +} + +void Viewport::getMouseTile(int &destX, int &destY) const +{ + getMouseTile(mMouseX, mMouseY, destX, destY); +} + +void Viewport::getMouseTile(const int x, const int y, + int &destX, int &destY) const +{ + if (mMap == nullptr) + return; + const int tw = mMap->getTileWidth(); + const int th = mMap->getTileHeight(); + destX = CAST_S32(x + mPixelViewX) + / static_cast<float>(tw); + + if (mMap->isHeightsPresent()) + { + const int th2 = th / 2; + const int clickY = y + mPixelViewY - th2; + destY = y + mPixelViewY; + int newDiffY = 1000000; + const int heightTiles = mainGraphics->mHeight / th; + const int tileViewY = mPixelViewY / th; + for (int f = tileViewY; f < tileViewY + heightTiles; f ++) + { + if (!mMap->getWalk(destX, + f, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + continue; + } + + const int offset = mMap->getHeightOffset( + destX, f) * th2; + const int pixelF = f * th; + const int diff = abs(clickY + offset - pixelF); + if (diff < newDiffY) + { + destY = pixelF; + newDiffY = diff; + } + } + destY /= 32; + } + else + { + destY = CAST_S32((y + mPixelViewY) / static_cast<float>(th)); + } +} + +void Viewport::walkByMouse(const MouseEvent &event) +{ + if ((mMap == nullptr) || (localPlayer == nullptr)) + return; + if (mPlayerFollowMouse + && !inputManager.isActionActive(InputAction::STOP_ATTACK) + && !inputManager.isActionActive(InputAction::UNTARGET)) + { + if (!mMouseDirectionMove) + mPlayerFollowMouse = false; + if (mLocalWalkTime != localPlayer->getActionTime()) + { + mLocalWalkTime = cur_time; + localPlayer->unSetPickUpTarget(); + int playerX = localPlayer->getTileX(); + int playerY = localPlayer->getTileY(); + if (mMouseDirectionMove) + { + const int width = mainGraphics->mWidth / 2; + const int height = mainGraphics->mHeight / 2; + const float wh = static_cast<float>(width) + / static_cast<float>(height); + int x = event.getX() - width; + int y = event.getY() - height; + if ((x == 0) && (y == 0)) + return; + const int x2 = abs(x); + const int y2 = abs(y); + const float diff = 2; + int dx = 0; + int dy = 0; + if (x2 > y2) + { + if (y2 != 0 && + static_cast<float>(x2) / static_cast<float>(y2) / + wh > diff) + { + y = 0; + } + } + else + { + if ((x2 != 0) && y2 * wh / x2 > diff) + x = 0; + } + if (x > 0) + dx = 1; + else if (x < 0) + dx = -1; + if (y > 0) + dy = 1; + else if (y < 0) + dy = -1; + + if (mMap->getWalk(playerX + dx, + playerY + dy, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + localPlayer->navigateTo(playerX + dx, playerY + dy); + } + else + { + if ((dx != 0) && (dy != 0)) + { + // try avoid diagonal collision + if (x2 > y2) + { + if (mMap->getWalk(playerX + dx, + playerY, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dy = 0; + } + else + { + dx = 0; + } + } + else + { + if (mMap->getWalk(playerX, + playerY + dy, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dx = 0; + } + else + { + dy = 0; + } + } + } + else + { + // try avoid vertical or horisontal collision + if (dx == 0) + { + if (mMap->getWalk(playerX + 1, + playerY + dy, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dx = 1; + } + if (mMap->getWalk(playerX - 1, + playerY + dy, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dx = -1; + } + } + if (dy == 0) + { + if (mMap->getWalk(playerX + dx, + playerY + 1, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dy = 1; + } + if (mMap->getWalk(playerX + dx, + playerY - 1, + BlockMask::WALL | + BlockMask::AIR | + BlockMask::WATER | + BlockMask::PLAYERWALL)) + { + dy = -1; + } + } + } + localPlayer->navigateTo(playerX + dx, playerY + dy); + } + } + else + { + int destX; + int destY; + getMouseTile(event.getX(), event.getY(), + destX, destY); + if (playerX != destX || playerY != destY) + { + if (!localPlayer->navigateTo(destX, destY)) + { + if (playerX > destX) + playerX --; + else if (playerX < destX) + playerX ++; + if (playerY > destY) + playerY --; + else if (playerY < destY) + playerY ++; + if (mMap->getWalk(playerX, playerY, 0)) + localPlayer->navigateTo(playerX, playerY); + } + } + } + } + } +} + +void Viewport::mouseDragged(MouseEvent &event) +{ + if (event.getSource() != this || event.isConsumed()) + { + mPlayerFollowMouse = false; + return; + } + if (mAllowMoveByMouse && + mMouseClicked && + (localPlayer != nullptr) && + localPlayer->canMove()) + { + if (abs(event.getX() - mMousePressX) > 32 + || abs(event.getY() - mMousePressY) > 32) + { + mPlayerFollowMouse = true; + } + + walkByMouse(event); + } +} + +void Viewport::mouseReleased(MouseEvent &event) +{ + mPlayerFollowMouse = false; + mLocalWalkTime = -1; + if (mLongMouseClick && mMouseClicked) + { + mMouseClicked = false; + if (event.getSource() != this || event.isConsumed()) + return; + const MouseButtonT eventButton = event.getButton(); + if (eventButton == MouseButton::LEFT) + { + // long button press + if ((gui != nullptr) && gui->isLongPress()) + { + if (openContextMenu(event)) + { + gui->resetClickCount(); + return; + } + } + else + { + if (leftMouseAction()) + return; + } + walkByMouse(event); + } + } +} + +void Viewport::optionChanged(const std::string &name) +{ + if (name == "ScrollLaziness") + mScrollLaziness = config.getIntValue("ScrollLaziness"); + else if (name == "ScrollRadius") + mScrollRadius = config.getIntValue("ScrollRadius"); + else if (name == "showBeingPopup") + mShowBeingPopup = config.getBoolValue("showBeingPopup"); + else if (name == "selfMouseHeal") + mSelfMouseHeal = config.getBoolValue("selfMouseHeal"); + else if (name == "enableLazyScrolling") + mEnableLazyScrolling = config.getBoolValue("enableLazyScrolling"); + else if (name == "mouseDirectionMove") + mMouseDirectionMove = config.getBoolValue("mouseDirectionMove"); + else if (name == "longmouseclick") + mLongMouseClick = config.getBoolValue("longmouseclick"); + else if (name == "allowMoveByMouse") + mAllowMoveByMouse = config.getBoolValue("allowMoveByMouse"); +} + +void Viewport::mouseMoved(MouseEvent &event) +{ + // Check if we are on the map + if (mMap == nullptr || + localPlayer == nullptr || + actorManager == nullptr) + { + return; + } + + if (mMouseDirectionMove) + mPlayerFollowMouse = false; + + const int x = mMouseX + mPixelViewX; + const int y = mMouseY + mPixelViewY; + + ActorTypeT type = ActorType::Unknown; + mHoverBeing = actorManager->findBeingByPixel(x, y, AllPlayers_true); + if (mHoverBeing != nullptr) + type = mHoverBeing->getType(); + if ((mHoverBeing != nullptr) + && (type == ActorType::Player + || type == ActorType::Npc + || type == ActorType::Homunculus + || type == ActorType::Mercenary + || type == ActorType::Pet)) + { + PopupManager::hideTextPopup(); + if (mShowBeingPopup && (beingPopup != nullptr)) + beingPopup->show(mMouseX, mMouseY, mHoverBeing); + } + else + { + PopupManager::hideBeingPopup(); + } + + mHoverItem = actorManager->findItem(x / mMap->getTileWidth(), + y / mMap->getTileHeight()); + + if ((mHoverBeing == nullptr) && (mHoverItem == nullptr)) + { + const SpecialLayer *const specialLayer = mMap->getSpecialLayer(); + if (specialLayer != nullptr) + { + const int mouseTileX = (mMouseX + mPixelViewX) + / mMap->getTileWidth(); + const int mouseTileY = (mMouseY + mPixelViewY) + / mMap->getTileHeight(); + + mHoverSign = specialLayer->getTile(mouseTileX, mouseTileY); + if (mHoverSign != nullptr && + mHoverSign->getType() != MapItemType::EMPTY) + { + if (!mHoverSign->getComment().empty()) + { + PopupManager::hideBeingPopup(); + if (textPopup != nullptr) + { + textPopup->show(mMouseX, mMouseY, + mHoverSign->getComment()); + } + } + else + { + if (PopupManager::isTextPopupVisible()) + PopupManager::hideTextPopup(); + } + gui->setCursorType(Cursor::CURSOR_UP); + return; + } + } + } + if (!event.isConsumed() && + PopupManager::isTextPopupVisible()) + { + PopupManager::hideTextPopup(); + } + + if (mHoverBeing != nullptr) + { + switch (type) + { + case ActorType::Npc: + case ActorType::Monster: + case ActorType::Portal: + case ActorType::Pet: + case ActorType::Mercenary: + case ActorType::Homunculus: + case ActorType::SkillUnit: + case ActorType::Elemental: + gui->setCursorType(mHoverBeing->getHoverCursor()); + break; + + case ActorType::Avatar: + case ActorType::FloorItem: + case ActorType::Unknown: + case ActorType::Player: + default: + gui->setCursorType(Cursor::CURSOR_POINTER); + break; + } + } + // Item mouseover + else if (mHoverItem != nullptr) + { + gui->setCursorType(mHoverItem->getHoverCursor()); + } + else + { + gui->setCursorType(Cursor::CURSOR_POINTER); + } +} + +void Viewport::toggleMapDrawType() +{ + settings.mapDrawType = static_cast<MapTypeT>( + CAST_S32(settings.mapDrawType) + 1); + if (settings.mapDrawType > MapType::BLACKWHITE) + settings.mapDrawType = MapType::NORMAL; + if (mMap != nullptr) + mMap->setDrawLayersFlags(settings.mapDrawType); +} + +void Viewport::toggleCameraMode() +{ + settings.cameraMode ++; + if (settings.cameraMode > 1) + settings.cameraMode = 0; + if (settings.cameraMode == 0u) + { + mCameraRelativeX = 0; + mCameraRelativeY = 0; + updateMidVars(); + } + UpdateStatusListener::distributeEvent(); +} + +void Viewport::clearHover(const ActorSprite *const actor) +{ + if (mHoverBeing == actor) + mHoverBeing = nullptr; + + if (mHoverItem == actor) + mHoverItem = nullptr; +} + +void Viewport::cleanHoverItems() +{ + mHoverBeing = nullptr; + mHoverItem = nullptr; + mHoverSign = nullptr; +} + +void Viewport::moveCamera(const int dx, const int dy) +{ + mCameraRelativeX += dx; + mCameraRelativeY += dy; + updateMidVars(); +} + +void Viewport::moveCameraToActor(const BeingId actorId, + const int x, const int y) +{ + if ((localPlayer == nullptr) || (actorManager == nullptr)) + return; + + const Actor *const actor = actorManager->findBeing(actorId); + if (actor == nullptr) + return; + settings.cameraMode = 1; + mCameraRelativeX = actor->mPixelX - localPlayer->mPixelX + x; + mCameraRelativeY = actor->mPixelY - localPlayer->mPixelY + y; + updateMidVars(); +} + +void Viewport::moveCameraToPosition(const int x, const int y) +{ + if (localPlayer == nullptr) + return; + + settings.cameraMode = 1; + mCameraRelativeX = x - localPlayer->mPixelX; + mCameraRelativeY = y - localPlayer->mPixelY; + updateMidVars(); +} + +void Viewport::moveCameraRelative(const int x, const int y) +{ + settings.cameraMode = 1; + mCameraRelativeX += x; + mCameraRelativeY += y; + updateMidVars(); +} + +void Viewport::returnCamera() +{ + settings.cameraMode = 0; + mCameraRelativeX = 0; + mCameraRelativeY = 0; + updateMidVars(); +} + +void Viewport::validateSpeed() +{ + if (!inputManager.isActionActive(InputAction::TARGET_ATTACK) && + !inputManager.isActionActive(InputAction::ATTACK)) + { + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + } +} + +void Viewport::updateMidVars() +{ + mMidTileX = (mainGraphics->mWidth + mScrollCenterOffsetX) / 2 + - mCameraRelativeX; + mMidTileY = (mainGraphics->mHeight + mScrollCenterOffsetY) / 2 + - mCameraRelativeY; +} + +void Viewport::updateMaxVars() +{ + if (mMap == nullptr) + return; + mViewXmax = mMap->getWidth() * mMap->getTileWidth() + - mainGraphics->mWidth; + mViewYmax = mMap->getHeight() * mMap->getTileHeight() + - mainGraphics->mHeight; +} + +void Viewport::videoResized() +{ + updateMidVars(); + updateMaxVars(); +} diff --git a/src/progs/manaplus/gui/viewport.h b/src/progs/manaplus/gui/viewport.h new file mode 100644 index 000000000..62982fc89 --- /dev/null +++ b/src/progs/manaplus/gui/viewport.h @@ -0,0 +1,249 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2017 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PROGS_MANAPLUS_GUI_VIEWPORT_H +#define PROGS_MANAPLUS_GUI_VIEWPORT_H + +#include "position.h" + +#include "enums/simpletypes/beingid.h" + +#include "gui/widgets/windowcontainer.h" + +#include "listeners/mouselistener.h" + +class ActorSprite; +class Being; +class FloorItem; +class Graphics; +class Map; +class MapItem; + +/** + * The viewport on the map. Displays the current map and handles mouse input + * and the popup menu. + */ +class Viewport final : public WindowContainer, + public MouseListener, + public ConfigListener +{ + public: + /** + * Constructor. + */ + Viewport(); + + A_DELETE_COPY(Viewport) + + /** + * Destructor. + */ + ~Viewport(); + + /** + * Sets the map displayed by the viewport. + */ + void setMap(Map *const map); + + /** + * Draws the viewport. + */ + void draw(Graphics *const graphics) override final A_NONNULL(2); + + void safeDraw(Graphics *const graphics) override final A_NONNULL(2); + + /** + * Implements player to keep following mouse. + */ + void logic() override final; + + /** + * Toggles whether the path debug graphics are shown. normal, + * debug with all images and grid, debug with out big images + * and with out grid. + */ + void toggleMapDrawType(); + + void toggleCameraMode(); + + /** + * Handles mouse press on map. + */ + void mousePressed(MouseEvent &event) override final; + + /** + * Handles mouse move on map + */ + void mouseDragged(MouseEvent &event) override final; + + /** + * Handles mouse button release on map. + */ + void mouseReleased(MouseEvent &event) override final; + + /** + * Handles mouse move on map. + */ + void mouseMoved(MouseEvent &event) override final; + + /** + * A relevant config option changed. + */ + void optionChanged(const std::string &name) override final; + + /** + * Returns camera x offset in pixels. + */ + int getCameraX() const noexcept2 A_WARN_UNUSED + { return mPixelViewX; } + + /** + * Returns camera y offset in pixels. + */ + int getCameraY() const noexcept2 A_WARN_UNUSED + { return mPixelViewY; } + + /** + * Changes viewpoint by relative pixel coordinates. + */ + void scrollBy(const int x, const int y) + { mPixelViewX += x; mPixelViewY += y; } + + /** + * Clear all hover item, being etc + */ + void cleanHoverItems(); + + Map *getMap() const noexcept2 A_WARN_UNUSED + { return mMap; } + + void moveCamera(const int dx, const int dy); + + int getCameraRelativeX() const noexcept2 A_WARN_UNUSED + { return mCameraRelativeX; } + + int getCameraRelativeY() const noexcept2 A_WARN_UNUSED + { return mCameraRelativeY; } + + void setCameraRelativeX(const int n) + { mCameraRelativeX = n; updateMidVars(); } + + void setCameraRelativeY(const int n) + { mCameraRelativeY = n; updateMidVars(); } + + void moveCameraToActor(const BeingId actorId, + const int x = 0, + const int y = 0); + + void moveCameraToPosition(const int x, const int y); + + void moveCameraRelative(const int x, const int y); + + void returnCamera(); + + void getMouseTile(int &destX, int &destY) const; + + void videoResized(); + + int mMouseX; /**< Current mouse position in pixels. */ + int mMouseY; /**< Current mouse position in pixels. */ + + protected: + friend class ActorManager; + + /// Clears any matching hovers + void clearHover(const ActorSprite *const actor); + + void updateMidVars(); + + void updateMaxVars(); + + static void validateSpeed(); + + private: + /** + * Finds a path from the player to the mouse, and draws it. This is for + * debug purposes. + */ + void drawDebugPath(Graphics *const graphics) A_NONNULL(2); + + /** + * Draws the given path. + */ + void drawPath(Graphics *const graphics, + const Path &path, + const Color &color = Color(255, 0, 0)) + const A_NONNULL(2); + + bool leftMouseAction(); + + bool openContextMenu(const MouseEvent &event); + + void walkByMouse(const MouseEvent &event); + + void getMouseTile(const int x, + const int y, + int &destX, + int &destY) const; + + /** + * Make the player go to the mouse position. + */ + void followMouse(); + + Map *mMap; /**< The current map. */ + + Being *mHoverBeing; /**< Being mouse is currently over. */ + FloorItem *mHoverItem; /**< FloorItem mouse is currently over. */ + MapItem *mHoverSign; /**< Map sign mouse is currently over. */ + + int mScrollRadius; + int mScrollLaziness; + int mScrollCenterOffsetX; + int mScrollCenterOffsetY; + int mMousePressX; + int mMousePressY; + int mPixelViewX; /**< Current viewpoint in pixels. */ + int mPixelViewY; /**< Current viewpoint in pixels. */ + int mMidTileX; + int mMidTileY; + int mViewXmax; + int mViewYmax; + + time_t mLocalWalkTime; + + int mCameraRelativeX; + int mCameraRelativeY; + + bool mShowBeingPopup; + bool mSelfMouseHeal; + bool mEnableLazyScrolling; + bool mMouseDirectionMove; + bool mLongMouseClick; + bool mAllowMoveByMouse; + bool mMouseClicked; + bool mPlayerFollowMouse; +}; + +extern Viewport *viewport; /**< The viewport. */ + +#endif // PROGS_MANAPLUS_GUI_VIEWPORT_H |