/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * * This file is part of The Mana 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 "gui/viewport.h" #include "actorsprite.h" #include "actorspritemanager.h" #include "client.h" #include "configuration.h" #include "graphics.h" #include "itemshortcut.h" #include "keyboardconfig.h" #include "localplayer.h" #include "map.h" #include "textmanager.h" #include "gui/beingpopup.h" #include "gui/chat.h" #include "gui/gui.h" #include "gui/ministatus.h" #include "gui/popupmenu.h" #include "gui/statuspopup.h" #include "gui/textpopup.h" #include "gui/widgets/button.h" #include "gui/widgets/chattab.h" #include "net/net.h" #include "resources/resourcemanager.h" #include "utils/stringutils.h" extern volatile int tick_time; Viewport::Viewport(): mMap(0), mMouseX(0), mMouseY(0), mPixelViewX(0.0f), mPixelViewY(0.0f), // mTileViewX(0), // mTileViewY(0), mShowDebugPath(false), mCameraMode(0), mPlayerFollowMouse(false), mLocalWalkTime(-1), mHoverBeing(0), mHoverItem(0), mHoverSign(0), mCameraRelativeX(0), mCameraRelativeY(0) { setOpaque(false); addMouseListener(this); mScrollLaziness = config.getIntValue("ScrollLaziness"); mScrollRadius = config.getIntValue("ScrollRadius"); mScrollCenterOffsetX = config.getIntValue("ScrollCenterOffsetX"); mScrollCenterOffsetY = config.getIntValue("ScrollCenterOffsetY"); config.addListener("ScrollLaziness", this); config.addListener("ScrollRadius", this); mPopupMenu = new PopupMenu; mBeingPopup = new BeingPopup; mTextPopup = new TextPopup; setFocusable(true); } Viewport::~Viewport() { config.removeListener("ScrollLaziness", this); config.removeListener("ScrollRadius", this); delete mPopupMenu; mPopupMenu = 0; delete mBeingPopup; mBeingPopup = 0; delete mTextPopup; mTextPopup = 0; } void Viewport::setMap(Map *map) { if (mMap && map) map->setDebugFlags(mMap->getDebugFlags()); mMap = map; } extern MiniStatusWindow *miniStatusWindow; void Viewport::draw(gcn::Graphics *gcnGraphics) { static int lastTick = tick_time; if (!mMap || !player_node) { gcnGraphics->setColor(gcn::Color(64, 64, 64)); gcnGraphics->fillRectangle( gcn::Rectangle(0, 0, getWidth(), getHeight())); return; } Graphics *graphics = static_cast<Graphics*>(gcnGraphics); // Avoid freaking out when tick_time overflows if (tick_time < lastTick) lastTick = tick_time; // Calculate viewpoint int midTileX = (graphics->getWidth() + mScrollCenterOffsetX) / 2; int midTileY = (graphics->getHeight() + mScrollCenterOffsetX) / 2; const Vector &playerPos = player_node->getPosition(); const int player_x = static_cast<int>(playerPos.x) - midTileX + mCameraRelativeX; const int player_y = static_cast<int>(playerPos.y) - midTileY + mCameraRelativeY; if (mScrollLaziness < 1) mScrollLaziness = 1; // Avoids division by zero // Apply lazy scrolling while (lastTick < tick_time) { if (player_x > static_cast<int>(mPixelViewX) + mScrollRadius) { mPixelViewX += static_cast<float>(player_x - static_cast<int>(mPixelViewX) - mScrollRadius) / static_cast<float>(mScrollLaziness); } if (player_x < static_cast<int>(mPixelViewX) - mScrollRadius) { mPixelViewX += static_cast<float>(player_x - static_cast<int>(mPixelViewX) + mScrollRadius) / static_cast<float>(mScrollLaziness); } if (player_y > static_cast<int>(mPixelViewY) + mScrollRadius) { mPixelViewY += static_cast<float>(player_y - static_cast<int>(mPixelViewY) - mScrollRadius) / static_cast<float>(mScrollLaziness); } if (player_y < static_cast<int>(mPixelViewY) - mScrollRadius) { mPixelViewY += static_cast<float>(player_y - static_cast<int>(mPixelViewY) + mScrollRadius) / static_cast<float>(mScrollLaziness); } lastTick++; } // Auto center when player is off screen if (player_x - static_cast<int>(mPixelViewX) > graphics->getWidth() / 2 || static_cast<int>(mPixelViewX) - player_x > graphics->getWidth() / 2 || static_cast<int>(mPixelViewY) - player_y > graphics->getHeight() / 2 || player_y - static_cast<int>(mPixelViewY) > graphics->getHeight() / 2) { mPixelViewX = static_cast<float>(player_x); mPixelViewY = static_cast<float>(player_y); }; // Don't move camera so that the end of the map is on screen const int viewXmax = mMap->getWidth() * mMap->getTileWidth() - graphics->getWidth(); const int viewYmax = mMap->getHeight() * mMap->getTileHeight() - graphics->getHeight(); if (mMap) { if (mPixelViewX < 0) mPixelViewX = 0; if (mPixelViewY < 0) mPixelViewY = 0; if (mPixelViewX > viewXmax) mPixelViewX = static_cast<float>(viewXmax); if (mPixelViewY > viewYmax) mPixelViewY = static_cast<float>(viewYmax); } // Draw tiles and sprites if (mMap) { mMap->draw(graphics, static_cast<int>(mPixelViewX), static_cast<int>(mPixelViewY)); if (mShowDebugPath) { mMap->drawCollision(graphics, static_cast<int>(mPixelViewX), static_cast<int>(mPixelViewY), mShowDebugPath); if (mShowDebugPath == Map::MAP_DEBUG) _drawDebugPath(graphics); } } if (player_node->getCheckNameSetting()) { player_node->setCheckNameSetting(false); player_node->setName(player_node->getName()); } // Draw text if (textManager) textManager->draw(graphics, static_cast<int>(mPixelViewX), static_cast<int>(mPixelViewY)); // Draw player names, speech, and emotion sprite as needed const ActorSprites &actors = actorSpriteManager->getAll(); for (ActorSpritesConstIterator it = actors.begin(), it_end = actors.end(); it != it_end; it++) { if ((*it)->getType() == ActorSprite::FLOOR_ITEM) continue; Being *b = static_cast<Being*>(*it); b->drawSpeech(static_cast<int>(mPixelViewX), static_cast<int>(mPixelViewY)); b->drawEmotion(graphics, static_cast<int>(mPixelViewX), static_cast<int>(mPixelViewY)); } if (miniStatusWindow) miniStatusWindow->drawIcons(graphics); // Draw contained widgets WindowContainer::draw(gcnGraphics); } void Viewport::logic() { WindowContainer::logic(); // Make the player follow the mouse position // if the mouse is dragged elsewhere than in a window. _followMouse(); } void Viewport::_followMouse() { Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY); // If the left button is dragged if (mPlayerFollowMouse && button & SDL_BUTTON(1)) { // We create a mouse event and send it to mouseDragged. Uint8 *keys = SDL_GetKeyState(NULL); gcn::MouseEvent mouseEvent(NULL, (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]), false, false, false, gcn::MouseEvent::DRAGGED, gcn::MouseEvent::LEFT, mMouseX, mMouseY, 0); mouseDragged(mouseEvent); } } void Viewport::_drawDebugPath(Graphics *graphics) { // Get the current mouse position SDL_GetMouseState(&mMouseX, &mMouseY); Path debugPath; if (Net::getNetworkType() == ServerInfo::TMWATHENA) { const int mouseTileX = (mMouseX + static_cast<int>(mPixelViewX)) / 32; const int mouseTileY = (mMouseY + static_cast<int>(mPixelViewY)) / 32; const Vector &playerPos = player_node->getPosition(); debugPath = mMap->findPath( static_cast<int>(playerPos.x - 16) / 32, static_cast<int>(playerPos.y - 32) / 32, mouseTileX, mouseTileY, 0, 500); _drawPath(graphics, debugPath); } else if (Net::getNetworkType() == ServerInfo::MANASERV) { const Vector &playerPos = player_node->getPosition(); const int playerRadius = player_node->getCollisionRadius(); // Draw player collision rectangle graphics->setColor(gcn::Color(128, 128, 0, 120)); graphics->fillRectangle( gcn::Rectangle(static_cast<int>(playerPos.x) - static_cast<int>(mPixelViewX) - playerRadius, static_cast<int>(playerPos.y) - static_cast<int>(mPixelViewY) - playerRadius, playerRadius * 2, playerRadius * 2)); debugPath = mMap->findPixelPath( static_cast<int>(playerPos.x), static_cast<int>(playerPos.y), mMouseX + static_cast<int>(mPixelViewX), mMouseY + static_cast<int>(mPixelViewY), playerRadius, 0xFF); // We draw the path proposed by mouse _drawPath(graphics, debugPath, gcn::Color(128, 0, 128)); // But also the one currently walked on. _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255)); } } void Viewport::_drawPath(Graphics *graphics, const Path &path, gcn::Color color) { graphics->setColor(color); if (Net::getNetworkType() == ServerInfo::TMWATHENA) { for (Path::const_iterator i = path.begin(); i != path.end(); ++i) { int squareX = i->x * 32 - static_cast<int>(mPixelViewX) + 12; int squareY = i->y * 32 - static_cast<int>(mPixelViewY) + 12; graphics->fillRectangle(gcn::Rectangle(squareX, squareY, 8, 8)); if (mMap) { graphics->drawText( toString(mMap->getMetaTile(i->x, i->y)->Gcost), squareX + 4, squareY + 12, gcn::Graphics::CENTER); } } } else if (Net::getNetworkType() == ServerInfo::MANASERV) { for (Path::const_iterator i = path.begin(); i != path.end(); ++i) { int squareX = i->x - static_cast<int>(mPixelViewX); int squareY = i->y - static_cast<int>(mPixelViewY); graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4, 8, 8)); if (mMap) { graphics->drawText( toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost), squareX + 4, squareY + 12, gcn::Graphics::CENTER); } } } } void Viewport::mousePressed(gcn::MouseEvent &event) { if (event.getSource() != this) return; // Check if we are alive and kickin' // if (!mMap || !player_node || !player_node->isAlive()) if (!mMap || !player_node) return; // Check if we are busy // if commented, allow context menu if npc dialog open if (Being::isTalking()) return; mPlayerFollowMouse = false; const int pixelX = event.getX() + static_cast<int>(mPixelViewX); const int pixelY = event.getY() + static_cast<int>(mPixelViewY); // Right click might open a popup if (event.getButton() == gcn::MouseEvent::RIGHT) { if (mHoverBeing) { if (actorSpriteManager) { std::list<Being*> beings; const int x = getMouseX() + static_cast<int>(mPixelViewX); const int y = getMouseY() + static_cast<int>(mPixelViewY); actorSpriteManager->findBeingsByPixel(beings, x, y, true); if (beings.size() > 1) { mPopupMenu->showPopup(event.getX(), event.getY(), beings); return; } else { mPopupMenu->showPopup(event.getX(), event.getY(), mHoverBeing); return; } } } else if (mHoverItem) { mPopupMenu->showPopup(event.getX(), event.getY(), mHoverItem); return; } else if (mHoverSign) { mPopupMenu->showPopup(event.getX(), event.getY(), mHoverSign); return; } } // If a popup is active, just remove it if (mPopupMenu->isVisible()) { mPopupMenu->setVisible(false); return; } // Left click can cause different actions if (event.getButton() == gcn::MouseEvent::LEFT) { // Interact with some being if (mHoverBeing) { if (!mHoverBeing->isAlive()) return; if (mHoverBeing->canTalk()) { mHoverBeing->talkTo(); } else { if (mHoverBeing->getType() == ActorSprite::PLAYER) { if (actorSpriteManager) actorSpriteManager->heal(player_node, mHoverBeing); } else if (player_node->withinAttackRange(mHoverBeing) || keyboard.isKeyActive(keyboard.KEY_ATTACK)) { player_node->attack(mHoverBeing, !keyboard.isKeyActive(keyboard.KEY_TARGET)); } else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) { player_node->setGotoTarget(mHoverBeing); } } // Picks up a item if we clicked on one } else if (mHoverItem) { player_node->pickUp(mHoverItem); } // Just walk around else if (!keyboard.isKeyActive(keyboard.KEY_ATTACK)) { player_node->stopAttack(); player_node->cancelFollow(); mPlayerFollowMouse = true; // Make the player go to the mouse position _followMouse(); } } else if (event.getButton() == gcn::MouseEvent::MIDDLE) { // Find the being nearest to the clicked position if (actorSpriteManager) { Being *target = actorSpriteManager->findNearestLivingBeing( pixelX, pixelY, 20, ActorSprite::MONSTER); if (target) player_node->setTarget(target); } } } void Viewport::mouseDragged(gcn::MouseEvent &event) { if (!mMap || !player_node) return; if (mPlayerFollowMouse && !event.isShiftPressed()) { if (Net::getNetworkType() == ServerInfo::MANASERV) { if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay) { mLocalWalkTime = tick_time; player_node->setDestination(event.getX() + static_cast<int>(mPixelViewX), event.getY() + static_cast<int>(mPixelViewY)); player_node->pathSetByMouse(); } } else { if (mLocalWalkTime != player_node->getActionTime()) { mLocalWalkTime = player_node->getActionTime(); int destX = static_cast<int>((static_cast<float>(event.getX()) + mPixelViewX) / static_cast<float>(mMap->getTileWidth())); int destY = static_cast<int>((static_cast<float>(event.getY()) + mPixelViewY) / static_cast<float>(mMap->getTileHeight())); player_node->setDestination(destX, destY); } } } } void Viewport::mouseReleased(gcn::MouseEvent &event _UNUSED_) { mPlayerFollowMouse = false; // Only useful for eAthena but doesn't hurt under ManaServ mLocalWalkTime = -1; } void Viewport::showPopup(Window *parent, int x, int y, Item *item, bool isInventory) { mPopupMenu->showPopup(parent, x, y, item, isInventory); } void Viewport::showPopup(MapItem *item) { mPopupMenu->showPopup(getMouseX(), getMouseY(), item); } void Viewport::showPopup(Window *parent, Item *item, bool isInventory) { mPopupMenu->showPopup(parent, getMouseX(), getMouseY(), item, isInventory); } void Viewport::showItemPopup(Item *item) { mPopupMenu->showItemPopup(getMouseX(), getMouseY(), item); } void Viewport::showItemPopup(int itemId) { mPopupMenu->showItemPopup(getMouseX(), getMouseY(), itemId); } void Viewport::showDropPopup(Item *item) { mPopupMenu->showDropPopup(getMouseX(), getMouseY(), item); } void Viewport::showOutfitsPopup(int x, int y) { mPopupMenu->showOutfitsPopup(x, y); } void Viewport::showOutfitsPopup() { mPopupMenu->showOutfitsPopup(getMouseX(), getMouseY()); } void Viewport::showSpellPopup(TextCommand *cmd) { mPopupMenu->showSpellPopup(getMouseX(), getMouseY(), cmd); } void Viewport::showChatPopup(int x, int y, ChatTab *tab) { mPopupMenu->showChatPopup(x, y, tab); } void Viewport::showChatPopup(ChatTab *tab) { mPopupMenu->showChatPopup(getMouseX(), getMouseY(), tab); } void Viewport::showPopup(int x, int y, Being *being) { mPopupMenu->showPopup(x, y, being); } void Viewport::showPlayerPopup(std::string nick) { mPopupMenu->showPlayerPopup(getMouseX(), getMouseY(), nick); } void Viewport::showPopup(int x, int y, Button *button) { mPopupMenu->showPopup(x, y, button); } void Viewport::closePopupMenu() { if (mPopupMenu) mPopupMenu->handleLink("cancel", 0); } void Viewport::optionChanged(const std::string &name _UNUSED_) { mScrollLaziness = config.getIntValue("ScrollLaziness"); mScrollRadius = config.getIntValue("ScrollRadius"); } void Viewport::mouseMoved(gcn::MouseEvent &event _UNUSED_) { // Check if we are on the map if (!mMap || !player_node || !actorSpriteManager) return; const int x = getMouseX() + static_cast<int>(mPixelViewX); const int y = getMouseY() + static_cast<int>(mPixelViewY); mHoverBeing = actorSpriteManager->findBeingByPixel(x, y, true); if (mHoverBeing && mHoverBeing->getType() == Being::PLAYER) { mTextPopup->setVisible(false); mBeingPopup->show(getMouseX(), getMouseY(), mHoverBeing); } else { mBeingPopup->setVisible(false); } mHoverItem = 0; if (!mHoverBeing && actorSpriteManager) { mHoverItem = actorSpriteManager->findItem(x / mMap->getTileWidth(), y / mMap->getTileHeight()); } if (!mHoverBeing && !mHoverItem) { SpecialLayer *specialLayer = mMap->getSpecialLayer(); if (specialLayer) { int mouseTileX = (getMouseX() + getCameraX()) / mMap->getTileWidth(); int mouseTileY = (getMouseY() + getCameraY()) / mMap->getTileHeight(); mHoverSign = specialLayer->getTile(mouseTileX, mouseTileY); if (mHoverSign && mHoverSign->getType() != MapItem::EMPTY) { if (!mHoverSign->getComment().empty()) { if (mBeingPopup) mBeingPopup->setVisible(false); mTextPopup->show(getMouseX(), getMouseY(), mHoverSign->getComment()); } else { if (mTextPopup->isVisible()) mTextPopup->setVisible(false); } return; } } } if (mTextPopup->isVisible()) mTextPopup->setVisible(false); if (mHoverBeing) { switch (mHoverBeing->getType()) { // NPCs case ActorSprite::NPC: gui->setCursorType(Gui::CURSOR_TALK); break; // Monsters case ActorSprite::MONSTER: gui->setCursorType(Gui::CURSOR_FIGHT); break; default: gui->setCursorType(Gui::CURSOR_POINTER); break; } } // Item mouseover else if (mHoverItem) { gui->setCursorType(Gui::CURSOR_PICKUP); } else { gui->setCursorType(Gui::CURSOR_POINTER); } } void Viewport::toggleDebugPath() { mShowDebugPath++; if (mShowDebugPath > Map::MAP_BLACKWHITE) mShowDebugPath = Map::MAP_NORMAL; if (mMap) mMap->setDebugFlags(mShowDebugPath); } void Viewport::toggleCameraMode() { mCameraMode++; if (mCameraMode > 1) mCameraMode = 0; if (!mCameraMode) { mCameraRelativeX = 0; mCameraRelativeY = 0; } if (miniStatusWindow) miniStatusWindow->updateStatus(); } void Viewport::hideBeingPopup() { if (mBeingPopup) mBeingPopup->setVisible(false); if (mTextPopup) mTextPopup->setVisible(false); } void Viewport::clearHover(ActorSprite *actor) { if (mHoverBeing == actor) mHoverBeing = 0; if (mHoverItem == actor) mHoverItem = 0; } void Viewport::cleanHoverItems() { mHoverBeing = 0; mHoverItem = 0; mHoverSign = 0; } void Viewport::moveCamera(int dx, int dy) { mCameraRelativeX += dx; mCameraRelativeY += dy; }