diff options
Diffstat (limited to 'src/progs')
-rw-r--r-- | src/progs/dyecmd/client.cpp | 2 | ||||
-rw-r--r-- | src/progs/manaplus/actions/actions.cpp | 1914 | ||||
-rw-r--r-- | src/progs/manaplus/actions/chat.cpp | 808 | ||||
-rw-r--r-- | src/progs/manaplus/actions/commands.cpp | 2156 | ||||
-rw-r--r-- | src/progs/manaplus/actions/move.cpp | 280 | ||||
-rw-r--r-- | src/progs/manaplus/actions/pets.cpp | 248 | ||||
-rw-r--r-- | src/progs/manaplus/actions/statusbar.cpp | 200 | ||||
-rw-r--r-- | src/progs/manaplus/actions/tabs.cpp | 106 | ||||
-rw-r--r-- | src/progs/manaplus/actions/target.cpp | 90 | ||||
-rw-r--r-- | src/progs/manaplus/actions/windows.cpp | 383 | ||||
-rw-r--r-- | src/progs/manaplus/client.cpp | 1981 | ||||
-rw-r--r-- | src/progs/manaplus/client.h | 167 | ||||
-rw-r--r-- | src/progs/manaplus/gui/viewport.cpp | 1148 | ||||
-rw-r--r-- | src/progs/manaplus/gui/viewport.h | 249 |
14 files changed, 9731 insertions, 1 deletions
diff --git a/src/progs/dyecmd/client.cpp b/src/progs/dyecmd/client.cpp index 42b570a6b..7f75f4cd5 100644 --- a/src/progs/dyecmd/client.cpp +++ b/src/progs/dyecmd/client.cpp @@ -20,7 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "client.h" +#include "progs/dyecmd/client.h" #include "configmanager.h" #include "dirs.h" diff --git a/src/progs/manaplus/actions/actions.cpp b/src/progs/manaplus/actions/actions.cpp new file mode 100644 index 000000000..b958f8f62 --- /dev/null +++ b/src/progs/manaplus/actions/actions.cpp @@ -0,0 +1,1914 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/actions.h" + +#include "actormanager.h" +#include "configuration.h" +#include "game.h" +#ifdef USE_OPENGL +#include "graphicsmanager.h" +#endif // USE_OPENGL +#include "main.h" +#include "spellmanager.h" + +#include "actions/actiondef.h" + +#include "being/localplayer.h" +#include "being/playerinfo.h" + +#include "const/spells.h" + +#include "const/resources/skill.h" + +#include "fs/files.h" + +#include "gui/gui.h" +#include "gui/popupmanager.h" +#include "gui/sdlinput.h" +#include "gui/windowmanager.h" + +#include "gui/shortcut/dropshortcut.h" +#include "gui/shortcut/emoteshortcut.h" +#include "gui/shortcut/itemshortcut.h" + +#include "gui/popups/popupmenu.h" + +#include "gui/windows/buydialog.h" +#include "gui/windows/okdialog.h" +#include "gui/windows/tradewindow.h" +#include "gui/windows/quitdialog.h" +#include "gui/windows/buyselldialog.h" +#include "gui/windows/chatwindow.h" +#include "gui/windows/helpwindow.h" +#include "gui/windows/inventorywindow.h" +#include "gui/windows/itemamountwindow.h" +#include "gui/windows/npcdialog.h" +#include "gui/windows/outfitwindow.h" +#include "gui/windows/setupwindow.h" +#include "gui/windows/shopwindow.h" +#include "gui/windows/shortcutwindow.h" +#include "gui/windows/skilldialog.h" +#include "gui/windows/whoisonline.h" + +#include "gui/widgets/createwidget.h" + +#include "gui/widgets/tabs/chat/chattab.h" + +#include "input/inputactionoperators.h" + +#if defined USE_OPENGL +#include "render/normalopenglgraphics.h" +#endif // USE_OPENGL + +#include "net/adminhandler.h" +#include "net/beinghandler.h" +#include "net/buyingstorehandler.h" +#include "net/buysellhandler.h" +#include "net/chathandler.h" +#include "net/download.h" +#include "net/homunculushandler.h" +#include "net/gamehandler.h" +#include "net/inventoryhandler.h" +#include "net/ipc.h" +#include "net/mercenaryhandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/playerhandler.h" +#include "net/serverfeatures.h" +#include "net/uploadcharinfo.h" +#include "net/tradehandler.h" +#include "net/vendinghandler.h" + +#include "resources/iteminfo.h" +#include "resources/memorymanager.h" + +#include "resources/resourcemanager/resourcemanager.h" + +#include "utils/chatutils.h" +#include "utils/delete2.h" +#include "utils/foreach.h" +#include "utils/gettext.h" +#include "utils/mathutils.h" +#include "utils/parameters.h" +#include "utils/timer.h" + +PRAGMA48(GCC diagnostic push) +PRAGMA48(GCC diagnostic ignored "-Wshadow") +#ifdef ANDROID +#ifndef USE_SDL2 +#include <SDL_screenkeyboard.h> +#endif // USE_OPENGL +#endif // ANDROID +PRAGMA48(GCC diagnostic pop) + +#include <sstream> + +#include "debug.h" + +extern std::string tradePartnerName; +extern QuitDialog *quitDialog; +extern time_t start_time; +extern char **environ; + +namespace Actions +{ + +static int uploadUpdate(void *ptr, + const DownloadStatusT status, + size_t total A_UNUSED, + const size_t remaining A_UNUSED) A_NONNULL(1); + +static int uploadUpdate(void *ptr, + const DownloadStatusT status, + size_t total A_UNUSED, + const size_t remaining A_UNUSED) +{ + if (status == DownloadStatus::Idle || status == DownloadStatus::Starting) + return 0; + + UploadChatInfo *const info = reinterpret_cast<UploadChatInfo*>(ptr); + if (info == nullptr) + return 0; + + if (status == DownloadStatus::Complete) + { + std::string str = Net::Download::getUploadResponse(); + const size_t sz = str.size(); + if (sz > 0) + { + if (str[sz - 1] == '\n') + str = str.substr(0, sz - 1); + str.append(info->addStr); + ChatTab *const tab = info->tab; + if (chatWindow != nullptr && + (tab == nullptr || chatWindow->isTabPresent(tab))) + { + str = strprintf("%s [@@%s |%s@@]", + info->text.c_str(), str.c_str(), str.c_str()); + outStringNormal(tab, str, str); + } + else + { + CREATEWIDGET(OkDialog, + // TRANSLATORS: file uploaded message + _("File uploaded"), + str, + // TRANSLATORS: ok dialog button + _("OK"), + DialogType::OK, + Modal_true, + ShowCenter_false, + nullptr, + 260); + } + } + } + delete2(info->upload); + delete info; + return 0; +} + +static void uploadFile(const std::string &str, + const std::string &fileName, + const std::string &addStr, + ChatTab *const tab) +{ + UploadChatInfo *const info = new UploadChatInfo; + Net::Download *const upload = new Net::Download(info, + "http://sprunge.us", + &uploadUpdate, + false, true, false); + info->upload = upload; + info->text = str; + info->addStr = addStr; + info->tab = tab; + upload->setFile(fileName); + upload->start(); +} + +static Being *findBeing(const std::string &name, const bool npc) +{ + if ((localPlayer == nullptr) || (actorManager == nullptr)) + return nullptr; + + Being *being = nullptr; + + if (name.empty()) + { + being = localPlayer->getTarget(); + } + else + { + being = actorManager->findBeingByName( + name, ActorType::Unknown); + } + if ((being == nullptr) && npc) + { + being = actorManager->findNearestLivingBeing( + localPlayer, 1, ActorType::Npc, AllowSort_true); + if (being != nullptr) + { + if (abs(being->getTileX() - localPlayer->getTileX()) > 1 + || abs(being->getTileY() - localPlayer->getTileY()) > 1) + { + being = nullptr; + } + } + } + if ((being == nullptr) && npc) + { + being = actorManager->findNearestLivingBeing( + localPlayer, 1, ActorType::Player, AllowSort_true); + if (being != nullptr) + { + if (abs(being->getTileX() - localPlayer->getTileX()) > 1 + || abs(being->getTileY() - localPlayer->getTileY()) > 1) + { + being = nullptr; + } + } + } + return being; +} + +static Item *getItemByInvIndex(const int index, + const InventoryTypeT invType) +{ + const Inventory *inv = nullptr; + switch (invType) + { + case InventoryType::Storage: + inv = PlayerInfo::getStorageInventory(); + break; + + case InventoryType::Inventory: + inv = PlayerInfo::getInventory(); + break; + case InventoryType::Trade: + case InventoryType::Npc: + case InventoryType::Cart: + case InventoryType::Vending: + case InventoryType::MailEdit: + case InventoryType::MailView: + case InventoryType::Craft: + case InventoryType::TypeEnd: + default: + break; + } + if (inv != nullptr) + return inv->getItem(index); + return nullptr; +} + +static int getAmountFromEvent(const InputEvent &event, + Item *&item0, + const InventoryTypeT invType) +{ + Item *const item = getItemByInvIndex(atoi(event.args.c_str()), + invType); + item0 = item; + if (item == nullptr) + return 0; + + std::string str = event.args; + removeToken(str, " "); + + if (str.empty()) + return 0; + + int amount = 0; + if (str[0] == '-') + { + if (str.size() > 1) + { + amount = item->getQuantity() - atoi(str.substr(1).c_str()); + if (amount <= 0 || amount > item->getQuantity()) + amount = item->getQuantity(); + } + } + else if (str == "/") + { + amount = item->getQuantity() / 2; + } + else if (str == "all") + { + amount = item->getQuantity(); + } + else + { + amount = atoi(str.c_str()); + } + return amount; +} + +impHandler(emote) +{ + const int emotion = 1 + (event.action - InputAction::EMOTE_1); + if (emotion > 0) + { + if (emoteShortcut != nullptr) + emoteShortcut->useEmotePlayer(emotion); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + + return false; +} + +impHandler(outfit) +{ + if (inputManager.isActionActive(InputAction::WEAR_OUTFIT)) + { + const int num = event.action - InputAction::OUTFIT_1; + if ((outfitWindow != nullptr) && num >= 0) + { + outfitWindow->wearOutfit(num); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + } + else if (inputManager.isActionActive(InputAction::COPY_OUTFIT)) + { + const int num = event.action - InputAction::OUTFIT_1; + if ((outfitWindow != nullptr) && num >= 0) + { + outfitWindow->copyOutfit(num); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + } + + return false; +} + +impHandler0(mouseClick) +{ + if ((guiInput == nullptr) || (gui == nullptr)) + return false; + + int mouseX, mouseY; + Gui::getMouseState(mouseX, mouseY); + guiInput->simulateMouseClick(mouseX, mouseY, MouseButton::RIGHT); + return true; +} + +impHandler0(ok) +{ + // Close the Browser if opened + if ((helpWindow != nullptr) && helpWindow->isWindowVisible()) + { + helpWindow->setVisible(Visible_false); + return true; + } + // Close the config window, cancelling changes if opened + else if ((setupWindow != nullptr) && setupWindow->isWindowVisible()) + { + setupWindow->action(ActionEvent(nullptr, "cancel")); + return true; + } + else if (NpcDialog *const dialog = NpcDialog::getActive()) + { + dialog->action(ActionEvent(nullptr, "ok")); + return true; + } + else if (popupMenu->isPopupVisible()) + { + popupMenu->select(); + } + return false; +} + +impHandler(shortcut) +{ + if (itemShortcutWindow != nullptr) + { + const int num = itemShortcutWindow->getTabIndex(); + if (num >= 0 && num < CAST_S32(SHORTCUT_TABS)) + { + if (itemShortcut[num] != nullptr) + { + itemShortcut[num]->useItem(event.action + - InputAction::SHORTCUT_1); + } + } + return true; + } + return false; +} + +impHandler0(quit) +{ + if (Game::instance() == nullptr) + return false; + if (PopupManager::isPopupMenuVisible()) + { + PopupManager::closePopupMenu(); + return true; + } + else if (quitDialog == nullptr) + { + CREATEWIDGETV(quitDialog, QuitDialog, + &quitDialog); + quitDialog->requestMoveToTop(); + return true; + } + return false; +} + +impHandler0(dropItem0) +{ + if (dropShortcut != nullptr) + { + dropShortcut->dropFirst(); + return true; + } + return false; +} + +impHandler0(dropItem) +{ + if (dropShortcut != nullptr) + { + dropShortcut->dropItems(); + return true; + } + return false; +} + +impHandler(dropItemId) +{ + const Inventory *const inv = PlayerInfo::getInventory(); + if (inv == nullptr) + return false; + + // +++ ignoring item color for now + Item *const item = inv->findItem(atoi(event.args.c_str()), + ItemColor_one); + + if ((item != nullptr) && !PlayerInfo::isItemProtected(item->getId())) + { + ItemAmountWindow::showWindow(ItemAmountWindowUsage::ItemDrop, + inventoryWindow, item); + } + return true; +} + +impHandler(dropItemInv) +{ + Item *const item = getItemByInvIndex(atoi(event.args.c_str()), + InventoryType::Inventory); + if ((item != nullptr) && !PlayerInfo::isItemProtected(item->getId())) + { + ItemAmountWindow::showWindow(ItemAmountWindowUsage::ItemDrop, + inventoryWindow, item); + } + return true; +} + +impHandler(dropItemIdAll) +{ + const Inventory *const inv = PlayerInfo::getInventory(); + if (inv == nullptr) + return false; + + // +++ ignoring item color for now + Item *const item = inv->findItem(atoi(event.args.c_str()), + ItemColor_one); + + if ((item != nullptr) && !PlayerInfo::isItemProtected(item->getId())) + PlayerInfo::dropItem(item, item->getQuantity(), Sfx_true); + return true; +} + +impHandler(dropItemInvAll) +{ + Item *const item = getItemByInvIndex(atoi(event.args.c_str()), + InventoryType::Inventory); + if ((item != nullptr) && !PlayerInfo::isItemProtected(item->getId())) + PlayerInfo::dropItem(item, item->getQuantity(), Sfx_true); + return true; +} + +#ifdef TMWA_SUPPORT +impHandler(heal) +{ + if (Net::getNetworkType() != ServerType::TMWATHENA) + return false; + if (actorManager != nullptr && + localPlayer != nullptr) + { + std::string args = event.args; + + if (!args.empty()) + { + const Being *being = nullptr; + if (args[0] == ':') + { + being = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + if (being != nullptr && being->getType() == ActorType::Monster) + being = nullptr; + } + else + { + being = actorManager->findBeingByName(args, ActorType::Player); + } + if (being != nullptr) + actorManager->heal(being); + } + else + { + Being *target = localPlayer->getTarget(); + if (inputManager.isActionActive(InputAction::STOP_ATTACK)) + { + if (target == nullptr || + target->getType() != ActorType::Player) + { + target = actorManager->findNearestLivingBeing( + localPlayer, 10, ActorType::Player, AllowSort_true); + } + } + else + { + if (target == nullptr) + target = localPlayer; + } + actorManager->heal(target); + } + + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + return false; +} +#else // TMWA_SUPPORT + +impHandler0(heal) +{ + return false; +} +#endif // TMWA_SUPPORT + +impHandler0(healmd) +{ +#ifdef TMWA_SUPPORT + if (Net::getNetworkType() != ServerType::TMWATHENA) + return false; + if (actorManager != nullptr) + { + const int matk = PlayerInfo::getStatEffective(Attributes::PLAYER_MATK); + int maxHealingRadius; + + // magic levels < 2 + if (PlayerInfo::getSkillLevel(340) < 2 + || PlayerInfo::getSkillLevel(341) < 2) + { + maxHealingRadius = matk / 100 + 1; + } + else + { + maxHealingRadius = (12 * fastSqrtInt(matk) + matk) / 100 + 1; + } + + Being *target = actorManager->findMostDamagedPlayer(maxHealingRadius); + if (target != nullptr) + actorManager->heal(target); + + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } +#endif // TMWA_SUPPORT + + return false; +} + +impHandler0(itenplz) +{ +#ifdef TMWA_SUPPORT + if (Net::getNetworkType() != ServerType::TMWATHENA) + return false; + if (actorManager != nullptr) + { + if (playerHandler != nullptr && + playerHandler->canUseMagic() && + PlayerInfo::getAttribute(Attributes::PLAYER_MP) >= 3) + { + actorManager->itenplz(); + } + return true; + } +#endif // TMWA_SUPPORT + + return false; +} + +impHandler0(setHome) +{ + if (localPlayer != nullptr) + { + localPlayer->setHome(); + return true; + } + return false; +} + +impHandler0(magicAttack) +{ +#ifdef TMWA_SUPPORT + if (Net::getNetworkType() != ServerType::TMWATHENA) + return false; + if (localPlayer != nullptr) + { + localPlayer->magicAttack(); + return true; + } +#endif // TMWA_SUPPORT + + return false; +} + +impHandler0(copyEquippedToOutfit) +{ + if (outfitWindow != nullptr) + { + outfitWindow->copyFromEquiped(); + return true; + } + return false; +} + +impHandler(pickup) +{ + if (localPlayer == nullptr) + return false; + + const std::string args = event.args; + if (args.empty()) + { + localPlayer->pickUpItems(); + } + else + { + FloorItem *const item = actorManager->findItem(fromInt( + atoi(args.c_str()), BeingId)); + if (item != nullptr) + localPlayer->pickUp(item); + } + return true; +} + +static void doSit() +{ + if (inputManager.isActionActive(InputAction::EMOTE)) + localPlayer->updateSit(); + else + localPlayer->toggleSit(); +} + +impHandler0(sit) +{ + if (localPlayer != nullptr) + { + doSit(); + return true; + } + return false; +} + +impHandler(screenshot) +{ + Game::createScreenshot(event.args); + return true; +} + +impHandler0(ignoreInput) +{ + return true; +} + +impHandler(buy) +{ + if (serverFeatures == nullptr) + return false; + const std::string args = event.args; + Being *being = findBeing(args, false); + if ((being == nullptr) && Net::getNetworkType() == ServerType::TMWATHENA) + { + if (whoIsOnline != nullptr) + { + const std::set<std::string> &players = + whoIsOnline->getOnlineNicks(); + if (players.find(args) != players.end()) + { + if (buySellHandler != nullptr) + buySellHandler->requestSellList(args); + return true; + } + } + return false; + } + + if (being == nullptr) + being = findBeing(args, true); + + if (being == nullptr) + return false; + + if (being->getType() == ActorType::Npc) + { + if (npcHandler != nullptr) + npcHandler->buy(being); + return true; + } + else if (being->getType() == ActorType::Player) + { + if (vendingHandler != nullptr && + Net::getNetworkType() != ServerType::TMWATHENA) + { + vendingHandler->open(being); + } + else if (buySellHandler != nullptr) + { + buySellHandler->requestSellList(being->getName()); + } + return true; + } + return false; +} + +impHandler(sell) +{ + if (serverFeatures == nullptr) + return false; + + const std::string args = event.args; + Being *being = findBeing(args, false); + if (being == nullptr && + Net::getNetworkType() == ServerType::TMWATHENA) + { + if (whoIsOnline != nullptr) + { + const std::set<std::string> &players = + whoIsOnline->getOnlineNicks(); + if (players.find(args) != players.end()) + { + if (buySellHandler != nullptr) + buySellHandler->requestBuyList(args); + return true; + } + } + return false; + } + + if (being == nullptr) + being = findBeing(args, true); + + if (being == nullptr) + return false; + + if (being->getType() == ActorType::Npc) + { + if (npcHandler != nullptr) + npcHandler->sell(being->getId()); + return true; + } + else if (being->getType() == ActorType::Player) + { + if ((buyingStoreHandler != nullptr) && + Net::getNetworkType() != ServerType::TMWATHENA) + { + buyingStoreHandler->open(being); + } + else if (buySellHandler != nullptr) + { + buySellHandler->requestBuyList(being->getName()); + } + return true; + } + return false; +} + +impHandler(talk) +{ + Being *being = findBeing(event.args, true); + if (being == nullptr) + return false; + + if (being->canTalk()) + { + being->talkTo(); + } + else if (being->getType() == ActorType::Player) + { + CREATEWIDGET(BuySellDialog, + being->getName()); + } + return true; +} + +impHandler0(stopAttack) +{ + if (localPlayer != nullptr) + { + localPlayer->stopAttack(); + // not consume if target attack key pressed + if (inputManager.isActionActive(InputAction::TARGET_ATTACK)) + return false; + return true; + } + return false; +} + +impHandler0(untarget) +{ + if (localPlayer != nullptr) + { + localPlayer->untarget(); + return true; + } + return false; +} + +impHandler(attack) +{ + if ((localPlayer == nullptr) || (actorManager == nullptr)) + return false; + + Being *target = nullptr; + std::string args = event.args; + if (!args.empty()) + { + if (args[0] != ':') + { + target = actorManager->findNearestByName(args); + } + else + { + target = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + if (target != nullptr && + target->getType() != ActorType::Monster) + { + target = nullptr; + } + } + } + if (target == nullptr) + target = localPlayer->getTarget(); + else + localPlayer->setTarget(target); + if (target != nullptr) + localPlayer->attack(target, true); + return true; +} + +impHandler(targetAttack) +{ + if ((localPlayer != nullptr) && (actorManager != nullptr)) + { + Being *target = nullptr; + std::string args = event.args; + const bool newTarget = !inputManager.isActionActive( + InputAction::STOP_ATTACK); + + if (!args.empty()) + { + if (args[0] != ':') + { + target = actorManager->findNearestByName(args); + } + else + { + target = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + if (target != nullptr && + target->getType() != ActorType::Monster) + { + target = nullptr; + } + } + } + + if ((target == nullptr) && (settings.targetingType == 0u)) + target = localPlayer->getTarget(); + + if (target == nullptr) + { + target = actorManager->findNearestLivingBeing( + localPlayer, 90, ActorType::Monster, AllowSort_true); + } + + localPlayer->attack2(target, newTarget); + return true; + } + return false; +} + +impHandler0(attackHuman) +{ + if ((actorManager == nullptr) || (localPlayer == nullptr)) + return false; + + Being *const target = actorManager->findNearestPvpPlayer(); + if (target != nullptr) + { + localPlayer->setTarget(target); + localPlayer->attack2(target, true); + } + return true; +} + +impHandler0(safeVideoMode) +{ + WindowManager::setFullScreen(false); + + return true; +} + +impHandler0(stopSit) +{ + if (localPlayer != nullptr) + { + localPlayer->stopAttack(); + // not consume if target attack key pressed + if (inputManager.isActionActive(InputAction::TARGET_ATTACK)) + return false; + if (localPlayer->getTarget() == nullptr) + { + doSit(); + return true; + } + return true; + } + return false; +} + +impHandler0(showKeyboard) +{ +#ifdef ANDROID +#ifdef USE_SDL2 + if (SDL_IsTextInputActive()) + SDL_StopTextInput(); + else + SDL_StartTextInput(); +#else // USE_SDL2 + + SDL_ANDROID_ToggleScreenKeyboardTextInput(nullptr); +#endif // USE_SDL2 + + return true; +#else // ANDROID + + return false; +#endif // ANDROID +} + +impHandler0(showWindows) +{ + if (popupMenu != nullptr) + { + popupMenu->showWindowsPopup(); + return true; + } + return false; +} + +impHandler0(openTrade) +{ + const Being *const being = localPlayer->getTarget(); + if ((being != nullptr) && being->getType() == ActorType::Player) + { + if (tradeHandler != nullptr) + tradeHandler->request(being); + tradePartnerName = being->getName(); + if (tradeWindow != nullptr) + tradeWindow->clear(); + return true; + } + return false; +} + +impHandler0(ipcToggle) +{ + if (ipc != nullptr) + { + IPC::stop(); + if (ipc == nullptr) + { + debugChatTab->chatLog("IPC service stopped.", + ChatMsgType::BY_SERVER); + } + else + { + debugChatTab->chatLog("Unable to stop IPC service.", + ChatMsgType::BY_SERVER); + } + } + else + { + IPC::start(); + if (ipc != nullptr) + { + debugChatTab->chatLog( + strprintf("IPC service available on port %d", ipc->getPort()), + ChatMsgType::BY_SERVER); + } + else + { + debugChatTab->chatLog("Unable to start IPC service", + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(where) +{ + ChatTab *const tab = event.tab != nullptr ? event.tab : debugChatTab; + if (tab == nullptr) + return false; + std::ostringstream where; + where << Game::instance()->getCurrentMapName() << ", coordinates: " + << ((localPlayer->getPixelX() - mapTileSize / 2) / mapTileSize) + << ", " << ((localPlayer->getPixelY() - mapTileSize) / mapTileSize); + tab->chatLog(where.str(), ChatMsgType::BY_SERVER); + return true; +} + +impHandler0(who) +{ + if (chatHandler != nullptr) + chatHandler->who(); + return true; +} + +impHandler0(cleanGraphics) +{ + ResourceManager::clearCache(); + + if (debugChatTab != nullptr) + { + // TRANSLATORS: clear graphics command message + debugChatTab->chatLog(_("Cache cleaned"), + ChatMsgType::BY_SERVER); + } + return true; +} + +impHandler0(cleanFonts) +{ + if (gui != nullptr) + gui->clearFonts(); + if (debugChatTab != nullptr) + { + // TRANSLATORS: clear fonts cache message + debugChatTab->chatLog(_("Cache cleaned"), + ChatMsgType::BY_SERVER); + } + return true; +} + +impHandler(trade) +{ + if (actorManager == nullptr) + return false; + + const Being *being = actorManager->findBeingByName( + event.args, ActorType::Player); + if (being == nullptr) + being = localPlayer->getTarget(); + if (being != nullptr) + { + if (tradeHandler != nullptr) + tradeHandler->request(being); + tradePartnerName = being->getName(); + if (tradeWindow != nullptr) + tradeWindow->clear(); + } + return true; +} + +impHandler0(priceLoad) +{ + if (shopWindow != nullptr) + { + shopWindow->loadList(); + return true; + } + return false; +} + +impHandler0(priceSave) +{ + if (shopWindow != nullptr) + { + shopWindow->saveList(); + return true; + } + return false; +} + +impHandler0(cacheInfo) +{ + if ((chatWindow == nullptr) || (debugChatTab == nullptr)) + return false; + +/* + Font *const font = chatWindow->getFont(); + if (!font) + return; + + const TextChunkList *const cache = font->getCache(); + if (!cache) + return; + + unsigned int all = 0; + // TRANSLATORS: chat fonts message + debugChatTab->chatLog(_("font cache size"), ChatMsgType::BY_SERVER); + std::string str; + for (int f = 0; f < 256; f ++) + { + if (!cache[f].size) + { + const unsigned int sz = CAST_S32(cache[f].size); + all += sz; + str.append(strprintf("%d: %u, ", f, sz)); + } + } + debugChatTab->chatLog(str, ChatMsgType::BY_SERVER); + // TRANSLATORS: chat fonts message + debugChatTab->chatLog(strprintf("%s %d", _("Cache size:"), all), + ChatMsgType::BY_SERVER); +#ifdef DEBUG_FONT_COUNTERS + debugChatTab->chatLog("", ChatMsgType::BY_SERVER); + debugChatTab->chatLog(strprintf("%s %d", + // TRANSLATORS: chat fonts message + _("Created:"), font->getCreateCounter()), + ChatMsgType::BY_SERVER); + debugChatTab->chatLog(strprintf("%s %d", + // TRANSLATORS: chat fonts message + _("Deleted:"), font->getDeleteCounter()), + ChatMsgType::BY_SERVER); +#endif +*/ + return true; +} + +impHandler0(disconnect) +{ + if (gameHandler != nullptr) + gameHandler->disconnect2(); + return true; +} + +impHandler(undress) +{ + if ((actorManager == nullptr) || (localPlayer == nullptr)) + return false; + + const std::string args = event.args; + StringVect pars; + if (!splitParameters(pars, args, " ,", '\"')) + return false; + Being *target = nullptr; + const size_t sz = pars.size(); + if (sz == 0) + { + target = localPlayer->getTarget(); + } + else + { + if (pars[0][0] == ':') + { + target = actorManager->findBeing(fromInt(atoi( + pars[0].substr(1).c_str()), BeingId)); + if ((target != nullptr) && target->getType() == ActorType::Monster) + target = nullptr; + } + else + { + target = actorManager->findNearestByName(args); + } + } + + if (sz == 2) + { + const int itemId = atoi(pars[1].c_str()); + if (target != nullptr) + target->undressItemById(itemId); + } + else + { + if ((target != nullptr) && (beingHandler != nullptr)) + beingHandler->undress(target); + } + + return true; +} + +impHandler0(dirs) +{ + if (debugChatTab == nullptr) + return false; + + debugChatTab->chatLog("config directory: " + + settings.configDir, + ChatMsgType::BY_SERVER); + debugChatTab->chatLog("logs directory: " + + settings.localDataDir, + ChatMsgType::BY_SERVER); + debugChatTab->chatLog("screenshots directory: " + + settings.screenshotDir, + ChatMsgType::BY_SERVER); + debugChatTab->chatLog("temp directory: " + + settings.tempDir, + ChatMsgType::BY_SERVER); + return true; +} + +impHandler0(uptime) +{ + if (debugChatTab == nullptr) + return false; + + if (cur_time < start_time) + { + // TRANSLATORS: uptime command + debugChatTab->chatLog(strprintf(_("Client uptime: %s"), "unknown"), + ChatMsgType::BY_SERVER); + } + else + { + // TRANSLATORS: uptime command + debugChatTab->chatLog(strprintf(_("Client uptime: %s"), + timeDiffToString(CAST_S32(cur_time - start_time)).c_str()), + ChatMsgType::BY_SERVER); + } + return true; +} + +#ifdef DEBUG_DUMP_LEAKS1 +static void showRes(std::string str, ResourceManager::Resources *res) +{ + if (!res) + return; + + str.append(toString(res->size())); + if (debugChatTab) + debugChatTab->chatLog(str, ChatMsgType::BY_SERVER); + logger->log(str); + ResourceManager::ResourceIterator iter = res->begin(); + const ResourceManager::ResourceIterator iter_end = res->end(); + while (iter != iter_end) + { + if (iter->second && iter->second->mRefCount) + { + char type = ' '; + char isNew = 'N'; + if (iter->second->getDumped()) + isNew = 'O'; + else + iter->second->setDumped(true); + + SubImage *const subImage = dynamic_cast<SubImage *>( + iter->second); + Image *const image = dynamic_cast<Image *>(iter->second); + int id = 0; + if (subImage) + type = 'S'; + else if (image) + type = 'I'; + if (image) + id = image->getGLImage(); + logger->log("Resource %c%c: %s (%d) id=%d", type, + isNew, iter->second->getIdPath().c_str(), + iter->second->mRefCount, id); + } + ++ iter; + } +} + +impHandler(dump) +{ + if (!debugChatTab) + return false; + + if (!event.args.empty()) + { + ResourceManager::Resources *res = ResourceManager::getResources(); + // TRANSLATORS: dump command + showRes(_("Resource images:"), res); + res = ResourceManager::getOrphanedResources(); + // TRANSLATORS: dump command + showRes(_("Resource orphaned images:"), res); + } + else + { + ResourceManager::Resources *res = ResourceManager::getResources(); + // TRANSLATORS: dump command + debugChatTab->chatLog(_("Resource images:") + toString(res->size()), + ChatMsgType::BY_SERVER); + res = ResourceManager::getOrphanedResources(); + // TRANSLATORS: dump command + debugChatTab->chatLog(_("Resource orphaned images:") + + toString(res->size()), + ChatMsgType::BY_SERVER); + } + return true; +} + +#elif defined ENABLE_MEM_DEBUG +impHandler0(dump) +{ + nvwa::check_leaks(); + return true; +} +#else // DEBUG_DUMP_LEAKS1 + +impHandler0(dump) +{ + return true; +} +#endif // DEBUG_DUMP_LEAKS1 + +impHandler0(serverIgnoreAll) +{ + if (chatHandler != nullptr) + chatHandler->ignoreAll(); + return true; +} + +impHandler0(serverUnIgnoreAll) +{ + if (chatHandler != nullptr) + chatHandler->unIgnoreAll(); + return true; +} + +PRAGMA6(GCC diagnostic push) +PRAGMA6(GCC diagnostic ignored "-Wnull-dereference") +impHandler0(error) +{ + int *const ptr = nullptr; + *(ptr + 1) = 20; +// logger->log("test %d", *ptr); + exit(1); +} +PRAGMA6(GCC diagnostic pop) + +impHandler(dumpGraphics) +{ + std::string str = strprintf("%s,%s,%dX%dX%d,", PACKAGE_OS, SMALL_VERSION, + mainGraphics->getWidth(), mainGraphics->getHeight(), + mainGraphics->getBpp()); + + if (mainGraphics->getFullScreen()) + str.append("F"); + else + str.append("W"); + if (mainGraphics->getHWAccel()) + str.append("H"); + else + str.append("S"); + + if (mainGraphics->getDoubleBuffer()) + str.append("D"); + else + str.append("_"); + +#if defined USE_OPENGL + str.append(strprintf(",%d", mainGraphics->getOpenGL())); +#else // defined USE_OPENGL + + str.append(",0"); +#endif // defined USE_OPENGL + + str.append(strprintf(",%f,", static_cast<double>(settings.guiAlpha))) + .append(config.getBoolValue("adjustPerfomance") ? "1" : "0") + .append(config.getBoolValue("alphaCache") ? "1" : "0") + .append(config.getBoolValue("enableMapReduce") ? "1" : "0") + .append(config.getBoolValue("beingopacity") ? "1" : "0") + .append(",") + .append(config.getBoolValue("enableAlphaFix") ? "1" : "0") + .append(config.getBoolValue("disableAdvBeingCaching") ? "1" : "0") + .append(config.getBoolValue("disableBeingCaching") ? "1" : "0") + .append(config.getBoolValue("particleeffects") ? "1" : "0") + .append(strprintf(",%d-%d", fps, config.getIntValue("fpslimit"))); + outStringNormal(event.tab, str, str); + return true; +} + +impHandler0(dumpEnvironment) +{ + logger->log1("Start environment variables"); + for (char **env = environ; *env != nullptr; ++ env) + logger->log1(*env); + logger->log1("End environment variables"); + if (debugChatTab != nullptr) + { + // TRANSLATORS: dump environment command + debugChatTab->chatLog(_("Environment variables dumped"), + ChatMsgType::BY_SERVER); + } + return true; +} + +impHandler(dumpTests) +{ + const std::string str = config.getStringValue("testInfo"); + outStringNormal(event.tab, str, str); + return true; +} + +impHandler0(dumpOGL) +{ +#if defined(USE_OPENGL) && !defined(ANDROID) && !defined(__native_client__) + NormalOpenGLGraphics::dumpSettings(); +#endif // defined(USE_OPENGL) && !defined(ANDROID) && + // !defined(__native_client__) + + return true; +} + +#ifdef USE_OPENGL +impHandler(dumpGL) +{ + std::string str = graphicsManager.getGLVersion(); + outStringNormal(event.tab, str, str); + return true; +} +#else // USE_OPENGL + +impHandler0(dumpGL) +{ + return true; +} +#endif // USE_OPENGL + +impHandler(dumpMods) +{ + std::string str = "enabled mods: " + serverConfig.getValue("mods", ""); + outStringNormal(event.tab, str, str); + return true; +} + +#if defined USE_OPENGL && defined DEBUG_SDLFONT +impHandler0(testSdlFont) +{ + Font *font = new Font("fonts/dejavusans.ttf", 18); + timespec time1; + timespec time2; + NullOpenGLGraphics *nullGraphics = new NullOpenGLGraphics; + STD_VECTOR<std::string> data; + volatile int width = 0; + + for (int f = 0; f < 300; f ++) + data.push_back("test " + toString(f) + "string"); + nullGraphics->beginDraw(); + + clock_gettime(CLOCK_MONOTONIC, &time1); + Color color(0, 0, 0, 255); + + for (int f = 0; f < 500; f ++) + { + FOR_EACH (STD_VECTOR<std::string>::const_iterator, it, data) + { + width += font->getWidth(*it); + font->drawString(nullGraphics, color, color, *it, 10, 10); + } + FOR_EACH (STD_VECTOR<std::string>::const_iterator, it, data) + font->drawString(nullGraphics, color, color, *it, 10, 10); + + font->doClean(); + } + + clock_gettime(CLOCK_MONOTONIC, &time2); + + delete nullGraphics; + delete font; + + int64_t diff = (static_cast<long long int>( + time2.tv_sec) * 1000000000LL + static_cast<long long int>( + time2.tv_nsec)) / 100000 - (static_cast<long long int>( + time1.tv_sec) * 1000000000LL + static_cast<long long int>( + time1.tv_nsec)) / 100000; + if (debugChatTab) + { + debugChatTab->chatLog("sdlfont time: " + toString(diff), + ChatMsgType::BY_SERVER); + } + return true; +} +#endif // defined USE_OPENGL && defined DEBUG_SDLFONT + +impHandler0(createItems) +{ + BuyDialog *const dialog = CREATEWIDGETR0(BuyDialog); + const ItemDB::ItemInfos &items = ItemDB::getItemInfos(); + FOR_EACH (ItemDB::ItemInfos::const_iterator, it, items) + { + const ItemInfo *const info = (*it).second; + if (info == nullptr) + continue; + const int id = info->getId(); + if (id <= 500) + continue; + + dialog->addItem(id, + ItemType::Unknown, + ItemColor_one, + 100, + 0); + } + dialog->sort(); + return true; +} + +impHandler(createItem) +{ + int id = 0; + int amount = 0; + + if (adminHandler == nullptr) + return false; + + if (parse2Int(event.args, id, amount)) + adminHandler->createItems(id, ItemColor_one, amount); + else + adminHandler->createItems(atoi(event.args.c_str()), ItemColor_one, 1); + return true; +} + +impHandler(uploadConfig) +{ + // TRANSLATORS: upload config chat message + uploadFile(_("Uploaded config into:"), + config.getFileName(), + "?xml", + event.tab); + return true; +} + +impHandler(uploadServerConfig) +{ + // TRANSLATORS: upload config chat message + uploadFile(_("Uploaded server config into:"), + serverConfig.getFileName(), + "?xml", + event.tab); + return true; +} + +impHandler(uploadLog) +{ + // TRANSLATORS: upload log chat message + uploadFile(_("Uploaded log into:"), + settings.logFileName, + "?txt", + event.tab); + return true; +} + +impHandler0(mercenaryFire) +{ + if (mercenaryHandler != nullptr) + mercenaryHandler->fire(); + return true; +} + +impHandler0(mercenaryToMaster) +{ + if (mercenaryHandler != nullptr) + mercenaryHandler->moveToMaster(); + return true; +} + +impHandler0(homunculusToMaster) +{ + if (homunculusHandler != nullptr) + homunculusHandler->moveToMaster(); + return true; +} + +impHandler0(homunculusFeed) +{ + if (homunculusHandler != nullptr) + homunculusHandler->feed(); + return true; +} + +impHandler(useItem) +{ + const int itemId = atoi(event.args.c_str()); + + if (itemId < SPELL_MIN_ID) + { + const Inventory *const inv = PlayerInfo::getInventory(); + if (inv != nullptr) + { + // +++ ignoring item color for now + const Item *const item = inv->findItem(itemId, + ItemColor_one); + PlayerInfo::useEquipItem(item, 0, Sfx_true); + } + } + else if (itemId < SKILL_MIN_ID && (spellManager != nullptr)) + { + spellManager->useItem(itemId); + } + else if (skillDialog != nullptr) + { + // +++ probably need get data parameter from args + skillDialog->useItem(itemId, + fromBool(config.getBoolValue("skillAutotarget"), AutoTarget), + 0, + std::string()); + } + return true; +} + +impHandler(useItemInv) +{ + int param1 = 0; + int param2 = 0; + const std::string args = event.args; + if (parse2Int(args, param1, param2)) + { + Item *const item = getItemByInvIndex(param1, + InventoryType::Inventory); + PlayerInfo::useEquipItem(item, CAST_S16(param2), Sfx_true); + } + else + { + Item *const item = getItemByInvIndex(atoi(event.args.c_str()), + InventoryType::Inventory); + PlayerInfo::useEquipItem(item, 0, Sfx_true); + } + return true; +} + +impHandler(invToStorage) +{ + Item *item = nullptr; + const int amount = getAmountFromEvent(event, item, + InventoryType::Inventory); + if (item == nullptr) + return true; + if (amount != 0) + { + if (inventoryHandler != nullptr) + { + inventoryHandler->moveItem2(InventoryType::Inventory, + item->getInvIndex(), + amount, + InventoryType::Storage); + } + } + else + { + ItemAmountWindow::showWindow(ItemAmountWindowUsage::StoreAdd, + inventoryWindow, item); + } + return true; +} + +impHandler(tradeAdd) +{ + Item *item = nullptr; + const int amount = getAmountFromEvent(event, item, + InventoryType::Inventory); + if ((item == nullptr) || PlayerInfo::isItemProtected(item->getId())) + return true; + + if (amount != 0) + { + if (tradeWindow != nullptr) + tradeWindow->tradeItem(item, amount, true); + } + else + { + ItemAmountWindow::showWindow(ItemAmountWindowUsage::TradeAdd, + tradeWindow, item); + } + return true; +} + +impHandler(storageToInv) +{ + Item *item = nullptr; + const int amount = getAmountFromEvent(event, item, InventoryType::Storage); + if (amount != 0) + { + if ((inventoryHandler != nullptr) && (item != nullptr)) + { + inventoryHandler->moveItem2(InventoryType::Storage, + item->getInvIndex(), + amount, + InventoryType::Inventory); + } + } + else + { + ItemAmountWindow::showWindow(ItemAmountWindowUsage::StoreRemove, + storageWindow, item); + } + return true; +} + +impHandler(protectItem) +{ + const int id = atoi(event.args.c_str()); + if (id > 0) + PlayerInfo::protectItem(id); + return true; +} + +impHandler(unprotectItem) +{ + const int id = atoi(event.args.c_str()); + if (id > 0) + PlayerInfo::unprotectItem(id); + return true; +} + +impHandler(kick) +{ + if ((localPlayer == nullptr) || (actorManager == nullptr)) + return false; + + Being *target = nullptr; + std::string args = event.args; + if (!args.empty()) + { + if (args[0] != ':') + { + target = actorManager->findNearestByName(args); + } + else + { + target = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + } + } + if (target == nullptr) + target = localPlayer->getTarget(); + if ((target != nullptr) && (adminHandler != nullptr)) + adminHandler->kick(target->getId()); + return true; +} + +impHandler0(clearDrop) +{ + if (dropShortcut != nullptr) + dropShortcut->clear(); + return true; +} + +impHandler0(testInfo) +{ + if (actorManager != nullptr) + { + logger->log("actors count: %d", CAST_S32( + actorManager->size())); + return true; + } + return false; +} + +impHandler(craftKey) +{ + const int slot = (event.action - InputAction::CRAFT_1); + if (slot >= 0 && slot < 9) + { + if (inventoryWindow != nullptr) + inventoryWindow->moveItemToCraft(slot); + return true; + } + return false; +} + +impHandler0(resetGameModifiers) +{ + GameModifiers::resetModifiers(); + return true; +} + +impHandler(barToChat) +{ + if (chatWindow != nullptr) + { + chatWindow->addInputText(event.args); + return true; + } + return false; +} + +impHandler(seen) +{ + if (actorManager == nullptr) + return false; + + ChatTab *tab = event.tab; + if (tab == nullptr) + tab = localChatTab; + if (tab == nullptr) + return false; + + if (config.getBoolValue("enableIdCollecting") == false) + { + // TRANSLATORS: last seen disabled warning + tab->chatLog(_("Last seen disabled. " + "Enable in players / collect players id and seen log."), + ChatMsgType::BY_SERVER); + return true; + } + + const std::string name = event.args; + if (name.empty()) + return false; + + std::string dir = settings.usersDir; + dir.append(stringToHexPath(name)).append("/seen.txt"); + if (Files::existsLocal(dir)) + { + StringVect lines; + Files::loadTextFileLocal(dir, lines); + if (lines.size() < 3) + { + // TRANSLATORS: last seen error + tab->chatLog(_("You not saw this nick."), + ChatMsgType::BY_SERVER); + return true; + } + const std::string message = strprintf( + // TRANSLATORS: last seen message + _("Last seen for %s: %s"), + name.c_str(), + lines[2].c_str()); + tab->chatLog(message, ChatMsgType::BY_SERVER); + } + else + { + // TRANSLATORS: last seen error + tab->chatLog(_("You not saw this nick."), + ChatMsgType::BY_SERVER); + } + + return true; +} + +impHandler(dumpMemoryUsage) +{ + if (event.tab != nullptr) + memoryManager.printAllMemory(event.tab); + else + memoryManager.printAllMemory(localChatTab); + return true; +} + +impHandler(setEmoteType) +{ + const std::string &args = event.args; + if (args == "player" || args.empty()) + { + settings.emoteType = EmoteType::Player; + } + else if (args == "pet") + { + settings.emoteType = EmoteType::Pet; + } + else if (args == "homun" || args == "homunculus") + { + settings.emoteType = EmoteType::Homunculus; + } + else if (args == "merc" || args == "mercenary") + { + settings.emoteType = EmoteType::Mercenary; + } + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/chat.cpp b/src/progs/manaplus/actions/chat.cpp new file mode 100644 index 000000000..307e4556e --- /dev/null +++ b/src/progs/manaplus/actions/chat.cpp @@ -0,0 +1,808 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/chat.h" + +#include "configuration.h" + +#include "actions/actiondef.h" + +#include "const/gui/chat.h" + +#include "being/localplayer.h" + +#include "gui/sdlinput.h" + +#include "gui/windows/chatwindow.h" + +#include "listeners/inputactionreplaylistener.h" + +#include "net/charserverhandler.h" +#include "net/chathandler.h" +#include "net/guildhandler.h" +#include "net/net.h" +#include "net/partyhandler.h" + +#ifdef TMWA_SUPPORT +#include "net/tmwa/guildmanager.h" +#endif // TMWA_SUPPORT + +#include "resources/iteminfo.h" + +#include "resources/db/itemdb.h" + +#include "utils/booleanoptions.h" +#include "utils/chatutils.h" +#include "utils/parameters.h" + +#include "utils/translation/podict.h" + +#include "debug.h" + +const int DEFAULT_CHAT_WINDOW_SCROLL = 7; + +namespace Actions +{ + +static void outString(ChatTab *const tab, + const std::string &str, + const std::string &def) +{ + if (tab == nullptr) + { + if (chatHandler != nullptr) + chatHandler->talk(def, GENERAL_CHANNEL); + return; + } + + switch (tab->getType()) + { + case ChatTabType::PARTY: + { + if (partyHandler != nullptr) + partyHandler->chat(str); + break; + } + case ChatTabType::GUILD: + { + if ((guildHandler == nullptr) || (localPlayer == nullptr)) + return; + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + { +#ifdef TMWA_SUPPORT + if (guild->getServerGuild()) + { + if (Net::getNetworkType() == ServerType::TMWATHENA) + return; + guildHandler->chat(str); + } + else if (guildManager != nullptr) + { + guildManager->chat(str); + } +#else // TMWA_SUPPORT + + if (guild->getServerGuild()) + guildHandler->chat(str); +#endif // TMWA_SUPPORT + } + break; + } + case ChatTabType::CHANNEL: + case ChatTabType::GM: + case ChatTabType::TRADE: + tab->chatInput(str); + break; + default: + case ChatTabType::UNKNOWN: + case ChatTabType::INPUT: + case ChatTabType::WHISPER: + case ChatTabType::DEBUG: + case ChatTabType::BATTLE: + case ChatTabType::LANG: + if (chatHandler != nullptr) + chatHandler->talk(str, GENERAL_CHANNEL); + break; + } +} + +impHandler0(toggleChat) +{ + return chatWindow != nullptr ? chatWindow->requestChatFocus() : false; +} + +impHandler0(prevChatTab) +{ + if (chatWindow != nullptr) + { + chatWindow->prevTab(); + return true; + } + return false; +} + +impHandler0(nextChatTab) +{ + if (chatWindow != nullptr) + { + chatWindow->nextTab(); + return true; + } + return false; +} + +impHandler0(closeChatTab) +{ + if (chatWindow != nullptr) + { + chatWindow->closeTab(); + return true; + } + return false; +} + +impHandler0(closeAllChatTabs) +{ + if (chatWindow != nullptr) + { + chatWindow->removeAllWhispers(); + chatWindow->saveState(); + return true; + } + return false; +} + +impHandler0(ignoreAllWhispers) +{ + if (chatWindow != nullptr) + { + chatWindow->ignoreAllWhispers(); + chatWindow->saveState(); + return true; + } + return false; +} + +impHandler0(scrollChatUp) +{ + if ((chatWindow != nullptr) && chatWindow->isWindowVisible()) + { + chatWindow->scroll(-DEFAULT_CHAT_WINDOW_SCROLL); + return true; + } + return false; +} + +impHandler0(scrollChatDown) +{ + if ((chatWindow != nullptr) && chatWindow->isWindowVisible()) + { + chatWindow->scroll(DEFAULT_CHAT_WINDOW_SCROLL); + return true; + } + return false; +} + +static bool splitWhisper(const std::string &args, + std::string &recvnick, + std::string &message) +{ + if (args.substr(0, 1) == "\"") + { + const size_t pos = args.find('"', 1); + if (pos != std::string::npos) + { + recvnick = args.substr(1, pos - 1); + if (pos + 2 < args.length()) + message = args.substr(pos + 2, args.length()); + } + } + else + { + const size_t pos = args.find(' '); + if (pos != std::string::npos) + { + recvnick = args.substr(0, pos); + if (pos + 1 < args.length()) + message = args.substr(pos + 1, args.length()); + } + else + { + recvnick = std::string(args); + message.clear(); + } + } + + trim(message); + + if (message.length() > 0) + { + std::string playerName = localPlayer->getName(); + std::string tempNick = recvnick; + + toLower(playerName); + toLower(tempNick); + + if (tempNick == playerName || args.empty()) + return false; + + return true; + } + return false; +} + +impHandler(msg) +{ + std::string recvnick; + std::string message; + + if (splitWhisper(event.args, recvnick, message)) + { + if (chatWindow == nullptr) + return false; + ChatTab *const tab = chatWindow->addChatTab(recvnick, false, true); + if (tab != nullptr) + { + chatWindow->saveState(); + tab->chatInput(message); + } + } + else + { + if (event.tab != nullptr) + { + event.tab->chatLog( + // TRANSLATORS: whisper send + _("Cannot send empty whisper or channel message!"), + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(msgText) +{ + if (chatWindow == nullptr) + return false; + + if (config.getBoolValue("whispertab")) + { + chatWindow->localChatInput("/q " + event.args); + } + else + { + chatWindow->addInputText(std::string("/w \"").append( + event.args).append("\" ")); + } + return true; +} + +impHandler(msg2) +{ + std::string recvnick; + std::string message; + + if (chatHandler != nullptr && + splitWhisper(event.args, recvnick, message)) + { + chatHandler->privateMessage(recvnick, message); + } + return true; +} + +impHandler(query) +{ + const std::string &args = event.args; + if (chatWindow != nullptr) + { + if (chatWindow->addChatTab(args, true, true) != nullptr) + { + chatWindow->saveState(); + return true; + } + } + + if (event.tab != nullptr) + { + // TRANSLATORS: new whisper or channel query + event.tab->chatLog(strprintf(_("Cannot create a whisper tab " + "\"%s\"! It probably already exists."), + args.c_str()), ChatMsgType::BY_SERVER); + } + return true; +} + +impHandler0(clearChatTab) +{ + if (chatWindow != nullptr) + { + chatWindow->clearTab(); + return true; + } + return false; +} + +impHandler(createParty) +{ + if (partyHandler == nullptr) + return false; + + if (event.args.empty()) + { + // TRANSLATORS: dialog header + inputActionReplayListener.openDialog(_("Create party"), + "", + InputAction::CREATE_PARTY); + } + else + { + partyHandler->create(event.args); + } + return true; +} + +impHandler(createGuild) +{ + if ((guildHandler == nullptr) || + Net::getNetworkType() == ServerType::TMWATHENA) + { + return false; + } + + if (event.args.empty()) + { + // TRANSLATORS: dialog header + inputActionReplayListener.openDialog(_("Create guild"), + "", + InputAction::CREATE_GUILD); + } + else + { + guildHandler->create(event.args); + } + return true; +} + +impHandler(party) +{ + if (!event.args.empty()) + { + if (partyHandler != nullptr) + partyHandler->invite(event.args); + } + else + { + if (event.tab != nullptr) + { + // TRANSLATORS: party invite message + event.tab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(guild) +{ + if ((guildHandler == nullptr) || (localPlayer == nullptr)) + return false; + + const std::string args = event.args; + if (!args.empty()) + { + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + { +#ifdef TMWA_SUPPORT + if (guild->getServerGuild()) + guildHandler->invite(args); + else if (guildManager != nullptr) + GuildManager::invite(args); +#else // TMWA_SUPPORT + + guildHandler->invite(args); +#endif // TMWA_SUPPORT + } + } + else + { + if (event.tab != nullptr) + { + // TRANSLATORS: guild invite message + event.tab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + } + else if (localChatTab != nullptr) + { + // TRANSLATORS: guild invite message + localChatTab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(me) +{ + outString(event.tab, textToMe(event.args), event.args); + return true; +} + +impHandler(toggle) +{ + if (event.args.empty()) + { + if ((chatWindow != nullptr) && (event.tab != nullptr)) + { + event.tab->chatLog(chatWindow->getReturnTogglesChat() ? + // TRANSLATORS: message from toggle chat command + _("Return toggles chat.") : _("Message closes chat."), + ChatMsgType::BY_SERVER); + } + return true; + } + + switch (parseBoolean(event.args)) + { + case 1: + if (event.tab != nullptr) + { + // TRANSLATORS: message from toggle chat command + event.tab->chatLog(_("Return now toggles chat."), + ChatMsgType::BY_SERVER); + } + if (chatWindow != nullptr) + chatWindow->setReturnTogglesChat(true); + return true; + case 0: + if (event.tab != nullptr) + { + // TRANSLATORS: message from toggle chat command + event.tab->chatLog(_("Message now closes chat."), + ChatMsgType::BY_SERVER); + } + if (chatWindow != nullptr) + chatWindow->setReturnTogglesChat(false); + return true; + case -1: + if (event.tab != nullptr) + { + event.tab->chatLog(strprintf(BOOLEAN_OPTIONS, "toggle"), + ChatMsgType::BY_SERVER); + } + return true; + default: + return true; + } +} + +impHandler(kickParty) +{ + if (!event.args.empty()) + { + if (partyHandler != nullptr) + partyHandler->kick(event.args); + } + else + { + if (event.tab != nullptr) + { + // TRANSLATORS: party kick message + event.tab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(kickGuild) +{ + if (!event.args.empty()) + { + if (localPlayer != nullptr) + { + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + { + if (guild->getServerGuild()) + { + if (guildHandler != nullptr) + guildHandler->kick(guild->getMember(event.args), ""); + } +#ifdef TMWA_SUPPORT + else if (guildManager != nullptr) + { + GuildManager::kick(event.args); + } +#endif // TMWA_SUPPORT + } + } + } + else + { + if (event.tab != nullptr) + { + // TRANSLATORS: party kick message + event.tab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + } + } + return true; +} + +impHandler(addText) +{ + if (chatWindow != nullptr) + chatWindow->addInputText(event.args); + return true; +} + +impHandler0(clearChat) +{ + if (chatWindow != nullptr) + chatWindow->clearTab(); + return true; +} + +impHandler0(chatGeneralTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::INPUT); + return true; +} + +impHandler0(chatDebugTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::DEBUG); + return true; +} + +impHandler0(chatBattleTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::BATTLE); + return true; +} + +impHandler0(chatTradeTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::TRADE); + return true; +} + +impHandler0(chatLangTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::LANG); + return true; +} + +impHandler0(chatGmTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::GM); + return true; +} + +impHandler0(chatPartyTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::PARTY); + return true; +} + +impHandler0(chatGuildTab) +{ + if (chatWindow != nullptr) + chatWindow->selectTabByType(ChatTabType::GUILD); + return true; +} + +impHandler(hat) +{ + if ((localPlayer == nullptr) || (charServerHandler == nullptr)) + return false; + + const int sprite = localPlayer->getSpriteID( + charServerHandler->hatSprite()); + std::string str; + if (sprite == 0) + { + // TRANSLATORS: equipped hat chat message + str = strprintf(_("no hat equipped.")); + } + else + { + const ItemInfo &info = ItemDB::get(sprite); + // TRANSLATORS: equipped hat chat message + str = strprintf(_("equipped hat %s."), + info.getName().c_str()); + } + outString(event.tab, str, str); + return true; +} + +impHandler(chatClipboard) +{ + int x = 0; + int y = 0; + + if ((chatWindow != nullptr) && parse2Int(event.args, x, y)) + { + chatWindow->copyToClipboard(x, y); + return true; + } + return false; +} + +impHandler(guildNotice) +{ + if (localPlayer == nullptr) + return false; + const std::string args = event.args; + if (args.empty()) + { + // TRANSLATORS: dialog header + inputActionReplayListener.openDialog(_("Guild notice"), + "", + InputAction::GUILD_NOTICE); + return true; + } + + std::string str2; + if (args.size() > 60) + str2 = args.substr(60); + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + { + guildHandler->changeNotice(guild->getId(), + args.substr(0, 60), + str2); + } + return true; +} + +impHandler(translate) +{ + if (reverseDictionary == nullptr || + localPlayer == nullptr || + event.args.empty()) + { + return false; + } + + ChatTab *const tab = event.tab; + if (tab == nullptr) + return false; + + std::string srcStr = event.args; + std::string enStr; + toLower(srcStr); + if (localPlayer->getLanguageId() > 0) + { + if (reverseDictionary->haveStr(srcStr)) + enStr = reverseDictionary->getStr(srcStr); + else if (dictionary->haveStr(srcStr)) + enStr = srcStr; + } + else + { + if (dictionary->haveStr(srcStr)) + enStr = srcStr; + } + + if (enStr.empty()) + { + tab->chatLog( + // TRANSLATORS: translation error message + strprintf(_("No translation found for string: %s"), + srcStr.c_str()), + ChatMsgType::BY_SERVER); + return true; + } + + tab->chatInput(enStr); + return true; +} + +impHandler(sendGuiKey) +{ + if (guiInput == nullptr) + return false; + + const std::string args = event.args; + if (args.empty()) + return false; + StringVect pars; + if (!splitParameters(pars, args, " ,", '\"')) + return false; + const int sz = CAST_S32(pars.size()); + if (sz < 1) + return false; + + int keyValue = atoi(pars[0].c_str()); + if (keyValue == 0 && + pars[0].size() == 1) + { + keyValue = CAST_S32(pars[0][0]); + } + if (sz == 2) + { + const InputActionT actionId = inputManager.getActionByConfigField( + pars[1]); + guiInput->simulateKey(keyValue, actionId); + } + else + { + guiInput->simulateKey(keyValue, InputAction::NO_VALUE); + } + return true; +} + +impHandler(sendMouseKey) +{ + if (guiInput == nullptr) + return false; + const std::string args = event.args; + if (args.empty()) + return false; + StringVect pars; + if (!splitParameters(pars, args, " ,", '\"')) + return false; + const int sz = CAST_S32(pars.size()); + if (sz != 3) + return false; + + const int x = atoi(pars[0].c_str()); + const int y = atoi(pars[1].c_str()); + const int key1 = CAST_S32(MouseButton::LEFT); + const int key2 = CAST_S32(MouseButton::MIDDLE); + const int key = atoi(pars[2].c_str()); + if (key < key1 || key > key2) + return false; + guiInput->simulateMouseClick(x, + y, + static_cast<MouseButtonT>(key)); + return true; +} + +impHandler(sendChars) +{ + if (guiInput == nullptr) + return false; + + const std::string args = event.args; + if (args.empty()) + return false; + + const size_t sz = args.size(); + for (size_t f = 0; f < sz; f ++) + { + guiInput->simulateKey(CAST_S32(args[f]), + InputAction::NO_VALUE); + } + + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/commands.cpp b/src/progs/manaplus/actions/commands.cpp new file mode 100644 index 000000000..f2d49b13b --- /dev/null +++ b/src/progs/manaplus/actions/commands.cpp @@ -0,0 +1,2156 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/commands.h" + +#include "actormanager.h" +#include "configuration.h" +#include "game.h" +#include "party.h" + +#include "actions/actiondef.h" + +#include "being/flooritem.h" +#include "being/localplayer.h" +#include "being/playerrelations.h" +#include "being/homunculusinfo.h" +#include "being/playerinfo.h" + +#include "const/resources/skill.h" + +#include "gui/viewport.h" + +#include "gui/popups/popupmenu.h" + +#include "gui/shortcut/emoteshortcut.h" +#include "gui/shortcut/itemshortcut.h" + +#include "gui/windows/mailwindow.h" + +#include "gui/windows/chatwindow.h" +#include "gui/windows/inventorywindow.h" +#include "gui/windows/npcdialog.h" +#include "gui/windows/outfitwindow.h" +#include "gui/windows/shortcutwindow.h" +#include "gui/windows/skilldialog.h" +#include "gui/windows/socialwindow.h" + +#include "gui/widgets/tabs/chat/whispertab.h" + +#include "input/inputactionoperators.h" + +#include "listeners/inputactionreplaylistener.h" + +#include "net/adminhandler.h" +#include "net/chathandler.h" +#include "net/guildhandler.h" +#include "net/familyhandler.h" +#include "net/homunculushandler.h" +#include "net/mail2handler.h" +#include "net/mailhandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/partyhandler.h" +#include "net/serverfeatures.h" + +#include "resources/chatobject.h" + +#include "resources/db/itemdb.h" + +#include "resources/map/map.h" + +#include "resources/skill/skillinfo.h" + +#include "utils/booleanoptions.h" +#include "utils/chatutils.h" +#include "utils/copynpaste.h" +#include "utils/gmfunctions.h" +#include "utils/parameters.h" +#include "utils/process.h" + +#include "debug.h" + +namespace Actions +{ + +static std::string getNick(const InputEvent &event) +{ + std::string args = event.args; + if (args.empty()) + { + if (event.tab == nullptr || + event.tab->getType() != ChatTabType::WHISPER) + { + return std::string(); + } + + WhisperTab *const whisper = static_cast<WhisperTab *>(event.tab); + if (whisper->getNick().empty()) + { + // TRANSLATORS: change relation + event.tab->chatLog(_("Please specify a name."), + ChatMsgType::BY_SERVER); + return std::string(); + } + args = whisper->getNick(); + } + return args; +} + +static void reportRelation(const InputEvent &event, + const RelationT &rel, + const std::string &str1, + const std::string &str2) +{ + if (event.tab != nullptr) + { + if (playerRelations.getRelation(event.args) == rel) + { + // TRANSLATORS: unignore command + event.tab->chatLog(str1, ChatMsgType::BY_SERVER); + } + else + { + // TRANSLATORS: unignore command + event.tab->chatLog(str2, ChatMsgType::BY_SERVER); + } + } +} + +static void changeRelation(const InputEvent &event, + const RelationT relation, + const std::string &relationText) +{ + std::string args = getNick(event); + if (args.empty()) + return; + + if (playerRelations.getRelation(args) == relation) + { + if (event.tab != nullptr) + { + // TRANSLATORS: change relation + event.tab->chatLog(strprintf(_("Player already %s!"), + relationText.c_str()), ChatMsgType::BY_SERVER); + return; + } + } + else + { + playerRelations.setRelation(args, relation); + } + + reportRelation(event, + relation, + // TRANSLATORS: change relation + strprintf(_("Player successfully %s!"), relationText.c_str()), + // TRANSLATORS: change relation + strprintf(_("Player could not be %s!"), relationText.c_str())); +} + +impHandler(chatAnnounce) +{ + if (adminHandler != nullptr) + { + adminHandler->announce(event.args); + return true; + } + return false; +} + +impHandler(chatIgnore) +{ + changeRelation(event, Relation::IGNORED, "ignored"); + return true; +} + +impHandler(chatUnignore) +{ + std::string args = getNick(event); + if (args.empty()) + return false; + + const RelationT rel = playerRelations.getRelation(args); + if (rel != Relation::NEUTRAL && rel != Relation::FRIEND) + { + playerRelations.setRelation(args, Relation::NEUTRAL); + } + else + { + if (event.tab != nullptr) + { + // TRANSLATORS: unignore command + event.tab->chatLog(_("Player wasn't ignored!"), + ChatMsgType::BY_SERVER); + } + return true; + } + + reportRelation(event, + Relation::NEUTRAL, + // TRANSLATORS: unignore command + _("Player no longer ignored!"), + // TRANSLATORS: unignore command + _("Player could not be unignored!")); + return true; +} + +impHandler(chatErase) +{ + std::string args = getNick(event); + if (args.empty()) + return false; + + if (playerRelations.getRelation(args) == Relation::ERASED) + { + if (event.tab != nullptr) + { + // TRANSLATORS: erase command + event.tab->chatLog(_("Player already erased!"), + ChatMsgType::BY_SERVER); + } + return true; + } + playerRelations.setRelation(args, Relation::ERASED); + + reportRelation(event, + Relation::ERASED, + // TRANSLATORS: erase command + _("Player no longer erased!"), + // TRANSLATORS: erase command + _("Player could not be erased!")); + return true; +} + +impHandler(chatFriend) +{ + // TRANSLATORS: adding friend command + changeRelation(event, Relation::FRIEND, _("friend")); + return true; +} + +impHandler(chatDisregard) +{ + // TRANSLATORS: disregard command + changeRelation(event, Relation::DISREGARDED, _("disregarded")); + return true; +} + +impHandler(chatNeutral) +{ + // TRANSLATORS: neutral command + changeRelation(event, Relation::NEUTRAL, _("neutral")); + return true; +} + +impHandler(chatBlackList) +{ + // TRANSLATORS: blacklist command + changeRelation(event, Relation::BLACKLISTED, _("blacklisted")); + return true; +} + +impHandler(chatEnemy) +{ + // TRANSLATORS: enemy command + changeRelation(event, Relation::ENEMY2, _("enemy")); + return true; +} + +impHandler(chatNuke) +{ + if (actorManager == nullptr) + return false; + + const std::string nick = getNick(event); + Being *const being = actorManager->findBeingByName( + nick, ActorType::Player); + if (being == nullptr) + return true; + + actorManager->addBlock(being->getId()); + actorManager->destroy(being); + return true; +} + +impHandler(chatAdd) +{ + if (chatWindow == nullptr) + return false; + + if (event.args.empty()) + return true; + + STD_VECTOR<int> str; + splitToIntVector(str, event.args, ','); + if (str.empty()) + return true; + + int id = str[0]; + if (id == 0) + return true; + + if (ItemDB::exists(id)) + { + const std::string names = ItemDB::getNamesStr(str); + if (!names.empty()) + chatWindow->addItemText(names); + return true; + } + + const FloorItem *const floorItem = actorManager->findItem( + fromInt(id, BeingId)); + + if (floorItem != nullptr) + { + str[0] = floorItem->getItemId(); + const std::string names = ItemDB::getNamesStr(str); + chatWindow->addItemText(names); + } + return true; +} + +impHandler0(present) +{ + if (chatWindow != nullptr) + { + chatWindow->doPresent(); + return true; + } + return false; +} + +impHandler0(printAll) +{ + if (actorManager != nullptr) + { + actorManager->printAllToChat(); + return true; + } + return false; +} + +impHandler(move) +{ + int x = 0; + int y = 0; + + if ((localPlayer != nullptr) && parse2Int(event.args, x, y)) + { + localPlayer->setDestination(x, y); + return true; + } + return false; +} + +impHandler(setTarget) +{ + if ((actorManager == nullptr) || (localPlayer == nullptr)) + return false; + + Being *const target = actorManager->findNearestByName(event.args); + if (target != nullptr) + localPlayer->setTarget(target); + return true; +} + +impHandler(commandOutfit) +{ + if (outfitWindow != nullptr) + { + if (!event.args.empty()) + { + const std::string op = event.args.substr(0, 1); + if (op == "n") + { + outfitWindow->wearNextOutfit(true); + } + else if (op == "p") + { + outfitWindow->wearPreviousOutfit(true); + } + else + { + outfitWindow->wearOutfit(atoi(event.args.c_str()) - 1, + false, true); + } + } + else + { + outfitWindow->wearOutfit(atoi(event.args.c_str()) - 1, + false, true); + } + return true; + } + return false; +} + +impHandler(commandEmote) +{ + LocalPlayer::emote(CAST_U8(atoi(event.args.c_str()))); + return true; +} + +impHandler(awayMessage) +{ + if (localPlayer != nullptr) + { + localPlayer->setAway(event.args); + return true; + } + return false; +} + +impHandler(pseudoAway) +{ + if (localPlayer != nullptr) + { + LocalPlayer::setPseudoAway(event.args); + localPlayer->updateStatus(); + return true; + } + return false; +} + +impHandler(follow) +{ + if (localPlayer == nullptr) + return false; + + if (!features.getBoolValue("allowFollow")) + return false; + + if (!event.args.empty()) + { + localPlayer->setFollow(event.args); + } + else if (event.tab != nullptr && + event.tab->getType() == ChatTabType::WHISPER) + { + localPlayer->setFollow(static_cast<WhisperTab*>(event.tab)->getNick()); + } + else + { + const Being *const being = localPlayer->getTarget(); + if (being != nullptr) + localPlayer->setFollow(being->getName()); + } + return true; +} + +impHandler(navigate) +{ + if ((localPlayer == nullptr) || + !localPlayer->canMove()) + { + return false; + } + + int x = 0; + int y = 0; + + if (parse2Int(event.args, x, y)) + localPlayer->navigateTo(x, y); + else + localPlayer->navigateClean(); + return true; +} + +impHandler(navigateTo) +{ + if ((localPlayer == nullptr) || + !localPlayer->canMove()) + { + return false; + } + + const std::string args = event.args; + if (args.empty()) + return true; + + Being *const being = actorManager->findBeingByName(args); + if (being != nullptr) + { + localPlayer->navigateTo(being->getTileX(), being->getTileY()); + } + else if (localPlayer->isInParty()) + { + const Party *const party = localPlayer->getParty(); + if (party != nullptr) + { + const PartyMember *const m = party->getMember(args); + const PartyMember *const o = party->getMember( + localPlayer->getName()); + if (m != nullptr && + o != nullptr && + m->getMap() == o->getMap()) + { + localPlayer->navigateTo(m->getX(), m->getY()); + } + } + } + return true; +} + +impHandler(moveCamera) +{ + int x = 0; + int y = 0; + + if (viewport == nullptr) + return false; + + if (parse2Int(event.args, x, y)) + viewport->moveCameraToPosition(x * mapTileSize, y * mapTileSize); + return true; +} + +impHandler0(restoreCamera) +{ + if (viewport == nullptr) + return false; + + viewport->returnCamera(); + return true; +} + +impHandler(imitation) +{ + if (localPlayer == nullptr) + return false; + + if (!event.args.empty()) + { + localPlayer->setImitate(event.args); + } + else if (event.tab != nullptr && + event.tab->getType() == ChatTabType::WHISPER) + { + localPlayer->setImitate(static_cast<WhisperTab*>( + event.tab)->getNick()); + } + else + { + localPlayer->setImitate(""); + } + return true; +} + +impHandler(sendMail) +{ +#ifdef TMWA_SUPPORT + const ServerTypeT type = Net::getNetworkType(); + if (type == ServerType::EATHENA || type == ServerType::EVOL2) +#endif // TMWA_SUPPORT + { + std::string name; + std::string text; + + if (parse2Str(event.args, name, text)) + { + if (settings.enableNewMailSystem) + { + mail2Handler->queueCheckName(MailQueueType::SendMail, + name, + // TRANSLATORS: quick mail message caption + _("Quick message"), + text, + 0); + } + else + { + // TRANSLATORS: quick mail message caption + mailHandler->send(name, _("Quick message"), text); + } + } + } +#ifdef TMWA_SUPPORT + else if (serverConfig.getBoolValue("enableManaMarketBot")) + { + chatHandler->privateMessage("ManaMarket", "!mail " + event.args); + return true; + } +#endif // TMWA_SUPPORT + + return false; +} + +impHandler(info) +{ + if (event.tab == nullptr || + localPlayer == nullptr || + Net::getNetworkType() == ServerType::TMWATHENA) + { + return false; + } + + if (guildHandler != nullptr && + event.tab->getType() == ChatTabType::GUILD) + { + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + guildHandler->info(); + } + return true; +} + +impHandler(wait) +{ + if (localPlayer != nullptr) + { + localPlayer->waitFor(event.args); + return true; + } + return false; +} + +impHandler(addPriorityAttack) +{ + if ((actorManager == nullptr) || + actorManager->isInPriorityAttackList(event.args)) + { + return false; + } + + actorManager->removeAttackMob(event.args); + actorManager->addPriorityAttackMob(event.args); + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; +} + +impHandler(addAttack) +{ + if (actorManager == nullptr) + return false; + + actorManager->removeAttackMob(event.args); + actorManager->addAttackMob(event.args); + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; +} + +impHandler(removeAttack) +{ + if (actorManager == nullptr) + return false; + + if (event.args.empty()) + { + if (actorManager->isInAttackList(event.args)) + { + actorManager->removeAttackMob(event.args); + actorManager->addIgnoreAttackMob(event.args); + } + else + { + actorManager->removeAttackMob(event.args); + actorManager->addAttackMob(event.args); + } + } + else + { + actorManager->removeAttackMob(event.args); + } + + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; +} + +impHandler(addIgnoreAttack) +{ + if (actorManager == nullptr) + return false; + + actorManager->removeAttackMob(event.args); + actorManager->addIgnoreAttackMob(event.args); + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; +} + +impHandler(setDrop) +{ + GameModifiers::setQuickDropCounter(atoi(event.args.c_str())); + return true; +} + +impHandler(url) +{ + if (event.tab != nullptr) + { + std::string url1 = event.args; + if (!strStartWith(url1, "http") && !strStartWith(url1, "?")) + url1 = "http://" + url1; + std::string str(strprintf("[@@%s |%s@@]", + url1.c_str(), event.args.c_str())); + outStringNormal(event.tab, str, str); + return true; + } + return false; +} + +impHandler(openUrl) +{ + std::string url = event.args; + if (!strStartWith(url, "http")) + url = "http://" + url; + openBrowser(url); + return true; +} + +impHandler(execute) +{ + const size_t idx = event.args.find(' '); + std::string name; + std::string params; + if (idx == std::string::npos) + { + name = event.args; + } + else + { + name = event.args.substr(0, idx); + params = event.args.substr(idx + 1); + } + execFile(name, name, params, ""); + return true; +} + +impHandler(enableHighlight) +{ + if (event.tab != nullptr) + { + event.tab->setAllowHighlight(true); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(disableHighlight) +{ + if (event.tab != nullptr) + { + event.tab->setAllowHighlight(false); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(dontRemoveName) +{ + if (event.tab != nullptr) + { + event.tab->setRemoveNames(false); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(removeName) +{ + if (event.tab != nullptr) + { + event.tab->setRemoveNames(true); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(disableAway) +{ + if (event.tab != nullptr) + { + event.tab->setNoAway(true); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(enableAway) +{ + if (event.tab != nullptr) + { + event.tab->setNoAway(false); + if (chatWindow != nullptr) + { + chatWindow->saveState(); + return true; + } + } + return false; +} + +impHandler(testParticle) +{ + if (localPlayer != nullptr) + { + localPlayer->setTestParticle(event.args); + return true; + } + return false; +} + +impHandler(talkRaw) +{ + if (chatHandler != nullptr) + { + chatHandler->talkRaw(event.args); + return true; + } + return false; +} + +impHandler(gm) +{ + if (chatHandler != nullptr) + { + Gm::runCommand("wgm", event.args); + return true; + } + return false; +} + +impHandler(hack) +{ + if (chatHandler != nullptr) + { + chatHandler->sendRaw(event.args); + return true; + } + return false; +} + +impHandler(debugSpawn) +{ + if (localPlayer == nullptr) + return false; + int cnt = atoi(event.args.c_str()); + if (cnt < 1) + cnt = 1; + const int half = cnt / 2; + const Map *const map = localPlayer->getMap(); + int x1 = -half; + if (x1 < 0) + x1 = 0; + int y1 = x1; + int x2 = cnt - half; + if (x2 > map->getWidth()) + x2 = map->getWidth(); + int y2 = x2; + + for (int x = x1; x < x2; x ++) + { + for (int y = y1; y < y2; y ++) + ActorManager::cloneBeing(localPlayer, x, y, cnt); + } + return true; +} + +impHandler(serverIgnoreWhisper) +{ + std::string args = getNick(event); + if (args.empty()) + return false; + + if (chatHandler != nullptr) + { + chatHandler->ignore(args); + return true; + } + return false; +} + +impHandler(serverUnIgnoreWhisper) +{ + std::string args = getNick(event); + if (args.empty()) + return false; + + if (chatHandler != nullptr) + { + chatHandler->unIgnore(args); + return true; + } + return false; +} + +impHandler(setHomunculusName) +{ + const std::string args = event.args; + if (args.empty()) + { + const HomunculusInfo *const info = PlayerInfo::getHomunculus(); + if (info != nullptr) + { + // TRANSLATORS: dialog header + inputActionReplayListener.openDialog(_("Rename your homun"), + info->name, + InputAction::HOMUNCULUS_SET_NAME); + } + return false; + } + + if (homunculusHandler != nullptr) + { + homunculusHandler->setName(args); + return true; + } + return false; +} + +impHandler0(fireHomunculus) +{ + if (homunculusHandler != nullptr) + { + homunculusHandler->fire(); + return true; + } + return false; +} + +impHandler0(leaveParty) +{ + if (partyHandler != nullptr) + { + partyHandler->leave(); + return true; + } + return false; +} + +impHandler0(leaveGuild) +{ + if ((guildHandler != nullptr) && (localPlayer != nullptr)) + { + const Guild *const guild = localPlayer->getGuild(); + if (guild != nullptr) + guildHandler->leave(guild->getId()); + return true; + } + return false; +} + +impHandler(warp) +{ + int x = 0; + int y = 0; + + if ((adminHandler != nullptr) && + (Game::instance() != nullptr) && + parse2Int(event.args, x, y)) + { + adminHandler->warp(Game::instance()->getCurrentMapName(), + x, y); + return true; + } + return false; +} + +impHandler(homunTalk) +{ + if ((serverFeatures == nullptr) || !serverFeatures->haveTalkPet()) + return false; + + std::string args = event.args; + if (findCutFirst(args, "/me ")) + args = textToMe(args); + if (homunculusHandler != nullptr) + { + homunculusHandler->talk(args); + return true; + } + return false; +} + +impHandler(homunEmote) +{ + if ((serverFeatures == nullptr) || !serverFeatures->haveTalkPet()) + return false; + + if ((homunculusHandler != nullptr) && + event.action >= InputAction::HOMUN_EMOTE_1 && + event.action <= InputAction::HOMUN_EMOTE_48) + { + const int emotion = event.action - InputAction::HOMUN_EMOTE_1; + if (emoteShortcut != nullptr) + homunculusHandler->emote(emoteShortcut->getEmote(emotion)); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + + return false; +} + +impHandler(commandHomunEmote) +{ + if ((serverFeatures == nullptr) || !serverFeatures->haveTalkPet()) + return false; + + if (homunculusHandler != nullptr) + { + homunculusHandler->emote(CAST_U8( + atoi(event.args.c_str()))); + return true; + } + return false; +} + +impHandler(createPublicChatRoom) +{ + if ((chatHandler == nullptr) || event.args.empty()) + return false; + chatHandler->createChatRoom(event.args, "", 100, true); + return true; +} + +impHandler(joinChatRoom) +{ + if (chatHandler == nullptr) + return false; + const std::string args = event.args; + if (args.empty()) + return false; + ChatObject *const chat = ChatObject::findByName(args); + if (chat == nullptr) + return false; + chatHandler->joinChat(chat, ""); + return true; +} + +impHandler0(leaveChatRoom) +{ + if (chatHandler != nullptr) + { + chatHandler->leaveChatRoom(); + return true; + } + return false; +} + +impHandler(confSet) +{ + std::string name; + std::string val; + + if (parse2Str(event.args, name, val)) + { + config.setValue(name, val); + return true; + } + return false; +} + +impHandler(serverConfSet) +{ + std::string name; + std::string val; + + if (parse2Str(event.args, name, val)) + { + serverConfig.setValue(name, val); + return true; + } + return false; +} + +impHandler(confGet) +{ + const std::string args = event.args; + if (args.empty()) + return false; + + // TRANSLATORS: result from command /confget + const std::string str = strprintf(_("Config value: %s"), + config.getStringValue(args).c_str()); + outStringNormal(event.tab, str, str); + return true; +} + +impHandler(serverConfGet) +{ + const std::string args = event.args; + if (args.empty()) + return false; + + // TRANSLATORS: result from command /serverconfget + const std::string str = strprintf(_("Server config value: %s"), + serverConfig.getStringValue(args).c_str()); + outStringNormal(event.tab, str, str); + return true; +} + +impHandler(slide) +{ + int x = 0; + int y = 0; + + if ((adminHandler != nullptr) && parse2Int(event.args, x, y)) + { + adminHandler->slide(x, y); + return true; + } + return false; +} + +impHandler(selectSkillLevel) +{ + int skill = 0; + int level = 0; + + if ((skillDialog != nullptr) && parse2Int(event.args, skill, level)) + { + skillDialog->selectSkillLevel(skill, level); + return true; + } + return false; +} + +impHandler(skill) +{ + StringVect vect; + splitToStringVector(vect, event.args, ' '); + const int sz = CAST_S32(vect.size()); + if (sz < 1) + return true; + const int skillId = atoi(vect[0].c_str()); + int level = 0; + std::string text; + if (sz > 1) + { + level = atoi(vect[1].c_str()); + if (sz > 2) + text = vect[2]; + } + // +++ add here also cast type and offsets + if (text.empty()) + { + SkillDialog::useSkill(skillId, + AutoTarget_true, + level, + false, + "", + CastType::Default, + 0, + 0); + } + else + { + SkillDialog::useSkill(skillId, + AutoTarget_true, + level, + true, + text, + CastType::Default, + 0, + 0); + } + return true; +} + +impHandler(craft) +{ + const std::string args = event.args; + if (args.empty() || (inventoryWindow == nullptr)) + return false; + + inventoryWindow->moveItemToCraft(atoi(args.c_str())); + return true; +} + +impHandler(npcClipboard) +{ + if (npcHandler != nullptr) + { + int x = 0; + int y = 0; + + NpcDialog *const dialog = npcHandler->getCurrentNpcDialog(); + + if ((dialog != nullptr) && parse2Int(event.args, x, y)) + { + dialog->copyToClipboard(x, y); + return true; + } + } + return false; +} + +impHandler(clipboardCopy) +{ + const std::string args = event.args; + if (args.empty()) + return false; + sendBuffer(args); + return true; +} + +impHandler(addPickup) +{ + if (actorManager != nullptr) + { + actorManager->removePickupItem(event.args); + actorManager->addPickupItem(event.args); + if (socialWindow != nullptr) + socialWindow->updatePickupFilter(); + return true; + } + return false; +} + +impHandler(removePickup) +{ + if (actorManager != nullptr) + { + if (event.args.empty()) + { // default pickup manipulation + if (actorManager->checkDefaultPickup()) + { + actorManager->removePickupItem(event.args); + actorManager->addIgnorePickupItem(event.args); + } + else + { + actorManager->removePickupItem(event.args); + actorManager->addPickupItem(event.args); + } + } + else + { // any other pickups + actorManager->removePickupItem(event.args); + } + if (socialWindow != nullptr) + socialWindow->updatePickupFilter(); + return true; + } + return false; +} + +impHandler(ignorePickup) +{ + if (actorManager != nullptr) + { + actorManager->removePickupItem(event.args); + actorManager->addIgnorePickupItem(event.args); + if (socialWindow != nullptr) + socialWindow->updatePickupFilter(); + return true; + } + return false; +} + +impHandler(monsterInfo) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->monsterInfo(args); + return true; +} + +impHandler(itemInfo) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->itemInfo(args); + return true; +} + +impHandler(whoDrops) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->whoDrops(args); + return true; +} + +impHandler(mobSearch) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->mobSearch(args); + return true; +} + +impHandler(mobSpawnSearch) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->mobSpawnSearch(args); + return true; +} + +impHandler(playerGmCommands) +{ + adminHandler->playerGmCommands(event.args); + return true; +} + +impHandler(playerCharGmCommands) +{ + adminHandler->playerCharGmCommands(event.args); + return true; +} + +impHandler(commandShowLevel) +{ + adminHandler->showLevel(event.args); + return true; +} + +impHandler(commandShowStats) +{ + adminHandler->showStats(event.args); + return true; +} + +impHandler(commandShowStorage) +{ + adminHandler->showStorageList(event.args); + return true; +} + +impHandler(commandShowCart) +{ + adminHandler->showCartList(event.args); + return true; +} + +impHandler(commandShowInventory) +{ + adminHandler->showInventoryList(event.args); + return true; +} + +impHandler(locatePlayer) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->locatePlayer(args); + return true; +} + +impHandler(commandShowAccountInfo) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->showAccountInfo(args); + return true; +} + +impHandler(commandSpawn) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->spawn(args); + return true; +} + +impHandler(commandSpawnSlave) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->spawnSlave(args); + return true; +} + +impHandler(commandSpawnClone) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->spawnClone(args); + return true; +} + +impHandler(commandSpawnSlaveClone) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->spawnSlaveClone(args); + return true; +} + +impHandler(commandSpawnEvilClone) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->spawnEvilClone(args); + return true; +} + +impHandler(commandSavePosition) +{ + adminHandler->savePosition(event.args); + return true; +} + +impHandler(commandLoadPosition) +{ + adminHandler->loadPosition(event.args); + return true; +} + +impHandler(commandRandomWarp) +{ + adminHandler->randomWarp(event.args); + return true; +} + +impHandler(commandGotoNpc) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->gotoNpc(args); + return true; +} + +impHandler(commandGotoPc) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->gotoName(args); + return true; +} + +impHandler(commandRecallPc) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->recallName(args); + return true; +} + +impHandler(commandIpCheck) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->ipcheckName(args); + return true; +} + +impHandler(commandKiller) +{ + adminHandler->killer(event.args); + return true; +} + +impHandler(commandKillable) +{ + adminHandler->killable(event.args); + return true; +} + +impHandler(commandHeal) +{ + adminHandler->heal(event.args); + return true; +} + +impHandler(commandAlive) +{ + adminHandler->alive(event.args); + return true; +} + +impHandler(commandDisguise) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->disguise(args); + return true; +} + +impHandler(commandImmortal) +{ + adminHandler->immortal(event.args); + return true; +} + +impHandler(commandHide) +{ + adminHandler->hide(event.args); + return true; +} + +impHandler(commandNuke) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->nuke(args); + return true; +} + +impHandler(commandKill) +{ + adminHandler->kill(event.args); + return true; +} + +impHandler(commandJail) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->jail(args); + return true; +} + +impHandler(commandUnjail) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->unjail(args); + return true; +} + +impHandler(commandNpcMove) +{ + const std::string args = event.args; + if (args.empty()) + return false; + StringVect pars; + if (!splitParameters(pars, args, " ,", '\"')) + return false; + + if (pars.size() != 3) + return false; + + adminHandler->npcMove(pars[0], + atoi(pars[1].c_str()), + atoi(pars[2].c_str())); + return true; +} + +impHandler(commandNpcHide) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->hideNpc(args); + return true; +} + +impHandler(commandNpcShow) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->showNpc(args); + return true; +} + +impHandler(commandChangePartyLeader) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->changePartyLeader(args); + return true; +} + +impHandler(commandPartyRecall) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->partyRecall(args); + return true; +} + +impHandler(commandBreakGuild) +{ + adminHandler->breakGuild(event.args); + return true; +} + +impHandler(commandGuildRecall) +{ + const std::string args = event.args; + if (args.empty()) + return false; + adminHandler->guildRecall(args); + return true; +} + +impHandler(mailTo) +{ + if (mailWindow == nullptr) + return false; + const std::string args = event.args; + if (settings.enableNewMailSystem) + { + mail2Handler->queueCheckName(MailQueueType::EditMail, + args, + std::string(), + std::string(), + 0); + } + else + { + mailWindow->createMail(args); + } + return true; +} + +impHandler(adoptChild) +{ + const std::string nick = getNick(event); + Being *const being = actorManager->findBeingByName( + nick, ActorType::Player); + if (being == nullptr) + return true; + familyHandler->askForChild(being); + return true; +} + +impHandler(showSkillLevels) +{ + const std::string args = event.args; + if (args.empty()) + return false; + const SkillInfo *restrict const skill = skillDialog->getSkill( + atoi(args.c_str())); + if (skill == nullptr) + return false; + popupMenu->showSkillLevelPopup(skill); + return true; +} + +impHandler(showSkillType) +{ + const std::string args = event.args; + if (args.empty()) + return false; + const SkillInfo *restrict const skill = skillDialog->getSkill( + atoi(args.c_str())); + if (skill == nullptr) + return false; + popupMenu->showSkillTypePopup(skill); + return true; +} + +impHandler(selectSkillType) +{ + int skill = 0; + int type = 0; + + if ((skillDialog != nullptr) && parse2Int(event.args, skill, type)) + { + skillDialog->selectSkillCastType(skill, + static_cast<CastTypeT>(type)); + return true; + } + return false; +} + +impHandler(showSkillOffsetX) +{ + const std::string args = event.args; + if (args.empty()) + return false; + const SkillInfo *restrict const skill = skillDialog->getSkill( + atoi(args.c_str())); + if (skill == nullptr) + return false; + popupMenu->showSkillOffsetPopup(skill, true); + return true; +} + +impHandler(showSkillOffsetY) +{ + const std::string args = event.args; + if (args.empty()) + return false; + const SkillInfo *restrict const skill = skillDialog->getSkill( + atoi(args.c_str())); + if (skill == nullptr) + return false; + popupMenu->showSkillOffsetPopup(skill, false); + return true; +} + +impHandler(setSkillOffsetX) +{ + int skill = 0; + int offset = 0; + + if ((skillDialog != nullptr) && parse2Int(event.args, skill, offset)) + { + skillDialog->setSkillOffsetX(skill, offset); + return true; + } + return false; +} + +impHandler(setSkillOffsetY) +{ + int skill = 0; + int offset = 0; + + if ((skillDialog != nullptr) && parse2Int(event.args, skill, offset)) + { + skillDialog->setSkillOffsetY(skill, offset); + return true; + } + return false; +} + +impHandler(partyItemShare) +{ + if (localPlayer == nullptr) + return false; + + if (localPlayer->isInParty() == false) + return true; + + ChatTab *tab = event.tab; + if (tab == nullptr) + tab = localChatTab; + if (tab == nullptr) + return true; + + const std::string args = event.args; + if (args.empty()) + { + switch (partyHandler->getShareItems()) + { + case PartyShare::YES: + // TRANSLATORS: chat message + tab->chatLog(_("Item sharing enabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NO: + // TRANSLATORS: chat message + tab->chatLog(_("Item sharing disabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NOT_POSSIBLE: + // TRANSLATORS: chat message + tab->chatLog(_("Item sharing not possible."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::UNKNOWN: + // TRANSLATORS: chat message + tab->chatLog(_("Item sharing unknown."), + ChatMsgType::BY_SERVER); + return true; + default: + break; + } + } + + const signed char opt = parseBoolean(args); + + switch (opt) + { + case 1: + partyHandler->setShareItems( + PartyShare::YES); + break; + case 0: + partyHandler->setShareItems( + PartyShare::NO); + break; + case -1: + tab->chatLog(strprintf(BOOLEAN_OPTIONS, "item"), + ChatMsgType::BY_SERVER); + break; + default: + break; + } + return true; +} + +impHandler(partyExpShare) +{ + if (localPlayer == nullptr) + return false; + + if (localPlayer->isInParty() == false) + return true; + + ChatTab *tab = event.tab; + if (tab == nullptr) + tab = localChatTab; + if (tab == nullptr) + return true; + + const std::string args = event.args; + if (args.empty()) + { + switch (partyHandler->getShareExperience()) + { + case PartyShare::YES: + // TRANSLATORS: chat message + tab->chatLog(_("Experience sharing enabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NO: + // TRANSLATORS: chat message + tab->chatLog(_("Experience sharing disabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NOT_POSSIBLE: + // TRANSLATORS: chat message + tab->chatLog(_("Experience sharing not possible."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::UNKNOWN: + // TRANSLATORS: chat message + tab->chatLog(_("Experience sharing unknown."), + ChatMsgType::BY_SERVER); + return true; + default: + break; + } + } + + const signed char opt = parseBoolean(args); + + switch (opt) + { + case 1: + partyHandler->setShareExperience( + PartyShare::YES); + break; + case 0: + partyHandler->setShareExperience( + PartyShare::NO); + break; + case -1: + tab->chatLog(strprintf(BOOLEAN_OPTIONS, "exp"), + ChatMsgType::BY_SERVER); + break; + default: + break; + } + return true; +} + +impHandler(partyAutoItemShare) +{ + if (localPlayer == nullptr) + return false; + + if (localPlayer->isInParty() == false) + return true; + + ChatTab *tab = event.tab; + if (tab == nullptr) + tab = localChatTab; + if (tab == nullptr) + return true; + + const std::string args = event.args; + if (args.empty()) + { + switch (partyHandler->getShareAutoItems()) + { + case PartyShare::YES: + // TRANSLATORS: chat message + tab->chatLog(_("Auto item sharing enabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NO: + // TRANSLATORS: chat message + tab->chatLog(_("Auto item sharing disabled."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::NOT_POSSIBLE: + // TRANSLATORS: chat message + tab->chatLog(_("Auto item sharing not possible."), + ChatMsgType::BY_SERVER); + return true; + case PartyShare::UNKNOWN: + // TRANSLATORS: chat message + tab->chatLog(_("Auto item sharing unknown."), + ChatMsgType::BY_SERVER); + return true; + default: + break; + } + } + + const signed char opt = parseBoolean(args); + + switch (opt) + { + case 1: + partyHandler->setShareAutoItems( + PartyShare::YES); + break; + case 0: + partyHandler->setShareAutoItems( + PartyShare::NO); + break; + case -1: + tab->chatLog(strprintf(BOOLEAN_OPTIONS, "item"), + ChatMsgType::BY_SERVER); + break; + default: + break; + } + return true; +} + +impHandler0(outfitToChat) +{ + if ((outfitWindow == nullptr) || (chatWindow == nullptr)) + return false; + + const std::string str = outfitWindow->getOutfitString(); + if (!str.empty()) + chatWindow->addInputText(str); + return true; +} + +impHandler0(outfitClear) +{ + if (outfitWindow == nullptr) + return false; + + outfitWindow->clearCurrentOutfit(); + return true; +} + +impHandler(moveAttackUp) +{ + if (actorManager == nullptr) + return false; + const std::string args = event.args; + const int idx = actorManager->getAttackMobIndex(args); + if (idx > 0) + { + std::list<std::string> mobs + = actorManager->getAttackMobs(); + std::list<std::string>::iterator it = mobs.begin(); + std::list<std::string>::iterator it2 = it; + while (it != mobs.end()) + { + if (*it == args) + { + -- it2; + mobs.splice(it2, mobs, it); + actorManager->setAttackMobs(mobs); + actorManager->rebuildAttackMobs(); + break; + } + ++ it; + ++ it2; + } + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; + } + return false; +} + +impHandler(moveAttackDown) +{ + if (actorManager == nullptr) + return false; + const std::string args = event.args; + const int idx = actorManager->getAttackMobIndex(args); + const int size = actorManager->getAttackMobsSize(); + if (idx + 1 < size) + { + std::list<std::string> mobs + = actorManager->getAttackMobs(); + std::list<std::string>::iterator it = mobs.begin(); + std::list<std::string>::iterator it2 = it; + while (it != mobs.end()) + { + if (*it == args) + { + ++ it2; + if (it2 == mobs.end()) + break; + + mobs.splice(it, mobs, it2); + actorManager->setAttackMobs(mobs); + actorManager->rebuildAttackMobs(); + break; + } + ++ it; + ++ it2; + } + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; + } + return false; +} + +impHandler(movePriorityAttackUp) +{ + if (actorManager == nullptr) + return false; + const std::string args = event.args; + const int idx = actorManager-> + getPriorityAttackMobIndex(args); + if (idx > 0) + { + std::list<std::string> mobs + = actorManager->getPriorityAttackMobs(); + std::list<std::string>::iterator it = mobs.begin(); + std::list<std::string>::iterator it2 = it; + while (it != mobs.end()) + { + if (*it == args) + { + -- it2; + mobs.splice(it2, mobs, it); + actorManager->setPriorityAttackMobs(mobs); + actorManager->rebuildPriorityAttackMobs(); + break; + } + ++ it; + ++ it2; + } + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; + } + return false; +} + +impHandler(movePriorityAttackDown) +{ + if (actorManager == nullptr) + return false; + const std::string args = event.args; + const int idx = actorManager + ->getPriorityAttackMobIndex(args); + const int size = actorManager->getPriorityAttackMobsSize(); + if (idx + 1 < size) + { + std::list<std::string> mobs + = actorManager->getPriorityAttackMobs(); + std::list<std::string>::iterator it = mobs.begin(); + std::list<std::string>::iterator it2 = it; + while (it != mobs.end()) + { + if (*it == args) + { + ++ it2; + if (it2 == mobs.end()) + break; + + mobs.splice(it, mobs, it2); + actorManager->setPriorityAttackMobs(mobs); + actorManager->rebuildPriorityAttackMobs(); + break; + } + ++ it; + ++ it2; + } + + if (socialWindow != nullptr) + socialWindow->updateAttackFilter(); + return true; + } + return false; +} + +impHandler(addSkillShortcut) +{ + const std::string args = event.args; + if (args.empty() || + itemShortcutWindow == nullptr) + { + return false; + } + const SkillInfo *restrict const skill = skillDialog->getSkill( + atoi(args.c_str())); + if (skill == nullptr) + return false; + + const int num = itemShortcutWindow->getTabIndex(); + if (num < 0 || + num >= CAST_S32(SHORTCUT_TABS) || + num == CAST_S32(SHORTCUT_AUTO_TAB)) + { + return false; + } + + ItemShortcut *const selShortcut = itemShortcut[num]; + const size_t index = selShortcut->getFreeIndex(); + if (index == SHORTCUT_ITEMS) + return true; + + selShortcut->setItem(index, + skill->id + SKILL_MIN_ID, + fromInt(skill->customSelectedLevel, ItemColor)); + selShortcut->setItemData(index, + skill->toDataStr()); + +// popupMenu->showSkillLevelPopup(skill); + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/move.cpp b/src/progs/manaplus/actions/move.cpp new file mode 100644 index 000000000..d2be4de7e --- /dev/null +++ b/src/progs/manaplus/actions/move.cpp @@ -0,0 +1,280 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/move.h" + +#include "game.h" + +#include "actions/actiondef.h" +#include "actions/pets.h" + +#include "being/crazymoves.h" +#include "being/localplayer.h" + +#include "enums/being/beingdirection.h" + +#include "gui/windows/socialwindow.h" +#include "gui/windows/npcdialog.h" +#include "gui/windows/outfitwindow.h" + +#include "gui/popups/popupmenu.h" + +#include "input/inputactionoperators.h" + +#include "net/playerhandler.h" + +#include "debug.h" + +namespace Actions +{ + +static bool closeMoveNpcDialog(bool focus) +{ + NpcDialog *const dialog = NpcDialog::getActive(); + if (dialog != nullptr) + { + if (dialog->isCloseState() != 0) + { + dialog->closeDialog(); + return true; + } + else if (focus) + { + dialog->refocus(); + } + } + return false; +} + +impHandler(moveUp) +{ + if (inputManager.isActionActive(InputAction::EMOTE)) + return directUp(event); + else if (inputManager.isActionActive(InputAction::PET_EMOTE)) + return petDirectUp(event); + else if (inputManager.isActionActive(InputAction::STOP_ATTACK)) + return petMoveUp(event); + else if (!localPlayer->canMove()) + return directUp(event); + if (popupMenu->isPopupVisible()) + { + popupMenu->moveUp(); + return true; + } + return closeMoveNpcDialog(false); +} + +impHandler(moveDown) +{ + if (inputManager.isActionActive(InputAction::EMOTE)) + return directDown(event); + else if (inputManager.isActionActive(InputAction::PET_EMOTE)) + return petDirectDown(event); + else if (inputManager.isActionActive(InputAction::STOP_ATTACK)) + return petMoveDown(event); + else if (!localPlayer->canMove()) + return directDown(event); + if (popupMenu->isPopupVisible()) + { + popupMenu->moveDown(); + return true; + } + return closeMoveNpcDialog(false); +} + +impHandler(moveLeft) +{ + if (outfitWindow != nullptr && + inputManager.isActionActive(InputAction::WEAR_OUTFIT)) + { + outfitWindow->wearPreviousOutfit(); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + if (inputManager.isActionActive(InputAction::EMOTE)) + return directLeft(event); + else if (inputManager.isActionActive(InputAction::PET_EMOTE)) + return petDirectLeft(event); + else if (inputManager.isActionActive(InputAction::STOP_ATTACK)) + return petMoveLeft(event); + else if (!localPlayer->canMove()) + return directLeft(event); + return closeMoveNpcDialog(false); +} + +impHandler(moveRight) +{ + if (outfitWindow != nullptr && + inputManager.isActionActive(InputAction::WEAR_OUTFIT)) + { + outfitWindow->wearNextOutfit(); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + if (inputManager.isActionActive(InputAction::EMOTE)) + return directRight(event); + else if (inputManager.isActionActive(InputAction::PET_EMOTE)) + return petDirectRight(event); + else if (inputManager.isActionActive(InputAction::STOP_ATTACK)) + return petMoveRight(event); + else if (!localPlayer->canMove()) + return directRight(event); + return closeMoveNpcDialog(false); +} + +impHandler(moveForward) +{ + if (inputManager.isActionActive(InputAction::EMOTE)) + return directRight(event); + return closeMoveNpcDialog(false); +} + +impHandler(moveToPoint) +{ + const int num = event.action - InputAction::MOVE_TO_POINT_1; + if ((socialWindow != nullptr) && num >= 0) + { + socialWindow->selectPortal(num); + return true; + } + + return false; +} + +impHandler0(crazyMoves) +{ + if (localPlayer != nullptr) + { + ::crazyMoves->crazyMove(); + return true; + } + return false; +} + +impHandler0(moveToTarget) +{ + if (localPlayer != nullptr && + !inputManager.isActionActive(InputAction::TARGET_ATTACK) && + !inputManager.isActionActive(InputAction::ATTACK)) + { + localPlayer->moveToTarget(); + return true; + } + return false; +} + +impHandler0(moveToHome) +{ + if (localPlayer != nullptr && + !inputManager.isActionActive(InputAction::TARGET_ATTACK) && + !inputManager.isActionActive(InputAction::ATTACK)) + { + localPlayer->moveToHome(); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + return false; +} + +impHandler0(directUp) +{ + if (localPlayer != nullptr) + { + if (localPlayer->getDirection() != BeingDirection::UP) + { +// if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) + { + localPlayer->setDirection(BeingDirection::UP); + if (playerHandler != nullptr) + playerHandler->setDirection(BeingDirection::UP); + } + } + return true; + } + return false; +} + +impHandler0(directDown) +{ + if (localPlayer != nullptr) + { + if (localPlayer->getDirection() != BeingDirection::DOWN) + { +// if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) + { + localPlayer->setDirection(BeingDirection::DOWN); + if (playerHandler != nullptr) + { + playerHandler->setDirection( + BeingDirection::DOWN); + } + } + } + return true; + } + return false; +} + +impHandler0(directLeft) +{ + if (localPlayer != nullptr) + { + if (localPlayer->getDirection() != BeingDirection::LEFT) + { +// if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) + { + localPlayer->setDirection(BeingDirection::LEFT); + if (playerHandler != nullptr) + { + playerHandler->setDirection( + BeingDirection::LEFT); + } + } + } + return true; + } + return false; +} + +impHandler0(directRight) +{ + if (localPlayer != nullptr) + { + if (localPlayer->getDirection() != BeingDirection::RIGHT) + { +// if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) + { + localPlayer->setDirection(BeingDirection::RIGHT); + if (playerHandler != nullptr) + { + playerHandler->setDirection( + BeingDirection::RIGHT); + } + } + } + return true; + } + return false; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/pets.cpp b/src/progs/manaplus/actions/pets.cpp new file mode 100644 index 000000000..25c5f1411 --- /dev/null +++ b/src/progs/manaplus/actions/pets.cpp @@ -0,0 +1,248 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/pets.h" + +#include "actormanager.h" +#include "game.h" + +#include "actions/actiondef.h" + +#include "being/localplayer.h" +#include "being/playerinfo.h" + +#include "enums/being/beingdirection.h" + +#include "const/gui/chat.h" + +#include "input/inputactionoperators.h" + +#include "listeners/inputactionreplaylistener.h" + +#include "gui/shortcut/emoteshortcut.h" + +#include "net/chathandler.h" +#include "net/pethandler.h" +#include "net/serverfeatures.h" + +#include "utils/chatutils.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#include "debug.h" + +namespace Actions +{ + +static const Being *getPet() +{ + const BeingId id = PlayerInfo::getPetBeingId(); + if (id == BeingId_zero) + return nullptr; + return actorManager->findBeing(id); +} + +impHandler(commandEmotePet) +{ + petHandler->emote(CAST_U8(atoi(event.args.c_str()))); + return true; +} + +impHandler(talkPet) +{ + if (!serverFeatures->haveTalkPet()) + return false; + + std::string args = event.args; + if (findCutFirst(args, "/me ")) + args = textToMe(args); + chatHandler->talkPet(args, GENERAL_CHANNEL); + return true; +} + +impHandler(setPetName) +{ + const std::string args = event.args; + if (args.empty()) + { + const Being *const pet = getPet(); + if (pet == nullptr) + return false; + // TRANSLATORS: dialog header + inputActionReplayListener.openDialog(_("Rename your pet"), + pet->getName(), + InputAction::PET_SET_NAME); + return false; + } + + petHandler->setName(args); + return true; +} + +impHandler(petEmote) +{ + if (!serverFeatures->haveTalkPet()) + return false; + + if (event.action >= InputAction::PET_EMOTE_1 + && event.action <= InputAction::PET_EMOTE_48) + { + const int emotion = event.action - InputAction::PET_EMOTE_1; + if (emoteShortcut != nullptr) + petHandler->emote(emoteShortcut->getEmote(emotion)); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + + return false; +} + +impHandler(catchPet) +{ + if ((localPlayer == nullptr) || (actorManager == nullptr)) + return false; + + Being *target = nullptr; + const std::string args = event.args; + if (!args.empty()) + { + if (args[0] == ':') + { + target = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + } + else + { + target = actorManager->findNearestByName(args); + } + } + + if (target == nullptr) + target = localPlayer->getTarget(); + else + localPlayer->setTarget(target); + if (target != nullptr) + petHandler->catchPet(target); + return true; +} + +impHandler0(petMoveUp) +{ + const Being *const pet = getPet(); + if (pet == nullptr) + return false; + petHandler->move(pet->getTileX(), pet->getTileY() - 1); + return true; +} + +impHandler0(petMoveDown) +{ + const Being *const pet = getPet(); + if (pet == nullptr) + return false; + petHandler->move(pet->getTileX(), pet->getTileY() + 1); + return true; +} + +impHandler0(petMoveLeft) +{ + const Being *const pet = getPet(); + if (pet == nullptr) + return false; + petHandler->move(pet->getTileX() - 1, pet->getTileY()); + return true; +} + +impHandler0(petMoveRight) +{ + const Being *const pet = getPet(); + if (pet == nullptr) + return false; + petHandler->move(pet->getTileX() + 1, pet->getTileY()); + return true; +} + +impHandler0(petDirectUp) +{ + petHandler->setDirection(BeingDirection::UP); + return true; +} + +impHandler0(petDirectDown) +{ + petHandler->setDirection(BeingDirection::DOWN); + return true; +} + +impHandler0(petDirectLeft) +{ + petHandler->setDirection(BeingDirection::LEFT); + return true; +} + +impHandler0(petDirectRight) +{ + petHandler->setDirection(BeingDirection::RIGHT); + return true; +} + +impHandler(petMove) +{ + int x = 0; + int y = 0; + + if (parse2Int(event.args, x, y)) + { + petHandler->move(x, y); + return true; + } + return false; +} + +impHandler0(petFeed) +{ + if (petHandler != nullptr) + petHandler->feed(); + return true; +} + +impHandler0(petDropLoot) +{ + if (petHandler != nullptr) + petHandler->dropLoot(); + return true; +} + +impHandler0(petReturnToEgg) +{ + if (petHandler != nullptr) + petHandler->returnToEgg(); + return true; +} + +impHandler0(petUnequip) +{ + if (petHandler != nullptr) + petHandler->unequip(); + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/statusbar.cpp b/src/progs/manaplus/actions/statusbar.cpp new file mode 100644 index 000000000..0d5ba68cd --- /dev/null +++ b/src/progs/manaplus/actions/statusbar.cpp @@ -0,0 +1,200 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/statusbar.h" + +#include "game.h" +#include "soundmanager.h" + +#include "actions/actiondef.h" + +#include "being/localplayer.h" +#include "being/playerrelation.h" +#include "being/playerrelations.h" + +#include "gui/viewport.h" + +#include "gui/widgets/tabs/chat/chattab.h" + +#include "listeners/updatestatuslistener.h" + +#include "resources/map/map.h" + +#include "utils/gettext.h" + +PRAGMA48(GCC diagnostic push) +PRAGMA48(GCC diagnostic ignored "-Wshadow") +#ifdef ANDROID +#ifndef USE_SDL2 +#include <SDL_screenkeyboard.h> +#endif // USE_SDL2 +#endif // ANDROID +PRAGMA48(GCC diagnostic pop) + +#include "debug.h" + +namespace Actions +{ + +impHandler0(switchQuickDrop) +{ + callYellowBarCond(changeQuickDropCounter) +} + +impHandler0(changeCrazyMove) +{ + callYellowBar(changeCrazyMoveType); +} + +impHandler0(changePickupType) +{ + callYellowBar(changePickUpType); +} + +impHandler0(changeMoveType) +{ + callYellowBar(changeMoveType); +} + +impHandler0(changeAttackWeaponType) +{ + callYellowBar(changeAttackWeaponType); +} + +impHandler0(changeAttackType) +{ + callYellowBar(changeAttackType); +} + +impHandler0(changeTargetingType) +{ + callYellowBar(changeTargetingType); +} + +impHandler0(changeFollowMode) +{ + callYellowBar(changeFollowMode); +} + +impHandler0(changeImitationMode) +{ + callYellowBar(changeImitationMode); +} + +impHandler0(changeMagicAttackType) +{ + callYellowBar(changeMagicAttackType); +} + +impHandler0(changePvpMode) +{ + callYellowBar(changePvpAttackType); +} + +impHandler0(changeMoveToTarget) +{ + callYellowBar(changeMoveToTargetType); +} + +impHandler0(changeGameModifier) +{ + if (localPlayer != nullptr) + { + GameModifiers::changeGameModifiers(false); + return true; + } + return false; +} + +impHandler0(changeAudio) +{ + soundManager.changeAudio(); + if (localPlayer != nullptr) + localPlayer->updateMusic(); + return true; +} + +impHandler0(away) +{ + GameModifiers::changeAwayMode(true); + if (localPlayer != nullptr) + { + localPlayer->updateStatus(); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + return false; +} + +impHandler0(camera) +{ + if (viewport != nullptr) + { + viewport->toggleCameraMode(); + if (Game::instance() != nullptr) + Game::instance()->setValidSpeed(); + return true; + } + return false; +} + +impHandler0(changeMapMode) +{ + if (viewport != nullptr) + viewport->toggleMapDrawType(); + UpdateStatusListener::distributeEvent(); + if (Game::instance() != nullptr) + { + if (Map *const map = Game::instance()->getCurrentMap()) + map->redrawMap(); + } + return true; +} + +impHandler0(changeTrade) +{ + unsigned int deflt = playerRelations.getDefault(); + if ((deflt & PlayerRelation::TRADE) != 0u) + { + if (localChatTab != nullptr) + { + // TRANSLATORS: disable trades message + localChatTab->chatLog(_("Ignoring incoming trade requests"), + ChatMsgType::BY_SERVER); + } + deflt &= ~PlayerRelation::TRADE; + } + else + { + if (localChatTab != nullptr) + { + // TRANSLATORS: enable trades message + localChatTab->chatLog(_("Accepting incoming trade requests"), + ChatMsgType::BY_SERVER); + } + deflt |= PlayerRelation::TRADE; + } + + playerRelations.setDefault(deflt); + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/tabs.cpp b/src/progs/manaplus/actions/tabs.cpp new file mode 100644 index 000000000..99d964b40 --- /dev/null +++ b/src/progs/manaplus/actions/tabs.cpp @@ -0,0 +1,106 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/tabs.h" + +#include "actions/actiondef.h" + +#include "gui/windows/inventorywindow.h" +#include "gui/windows/socialwindow.h" +#include "gui/windows/shortcutwindow.h" + +#include "debug.h" + +namespace Actions +{ + +impHandler0(prevSocialTab) +{ + if (socialWindow != nullptr) + { + socialWindow->prevTab(); + return true; + } + return false; +} + +impHandler0(nextSocialTab) +{ + if (socialWindow != nullptr) + { + socialWindow->nextTab(); + return true; + } + return false; +} + +impHandler0(nextShortcutsTab) +{ + if (itemShortcutWindow != nullptr) + { + itemShortcutWindow->nextTab(); + return true; + } + return false; +} + +impHandler0(prevShortcutsTab) +{ + if (itemShortcutWindow != nullptr) + { + itemShortcutWindow->prevTab(); + return true; + } + return false; +} + +impHandler0(nextCommandsTab) +{ + if (spellShortcutWindow != nullptr) + { + spellShortcutWindow->nextTab(); + return true; + } + return false; +} + +impHandler0(prevCommandsTab) +{ + if (spellShortcutWindow != nullptr) + { + spellShortcutWindow->prevTab(); + return true; + } + return false; +} + +impHandler0(nextInvTab) +{ + InventoryWindow::nextTab(); + return true; +} + +impHandler0(prevInvTab) +{ + InventoryWindow::prevTab(); + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/target.cpp b/src/progs/manaplus/actions/target.cpp new file mode 100644 index 000000000..a72cbe502 --- /dev/null +++ b/src/progs/manaplus/actions/target.cpp @@ -0,0 +1,90 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/target.h" + +#include "actions/actiondef.h" + +#include "being/localplayer.h" + +#include "gui/popups/popupmenu.h" + +#include "debug.h" + +namespace Actions +{ + +static bool setTarget(const ActorTypeT type, const AllowSort allowSort) +{ + if (localPlayer != nullptr) + return localPlayer->setNewTarget(type, allowSort) != nullptr; + return false; +} + +impHandler0(targetPlayer) +{ + return setTarget(ActorType::Player, AllowSort_true); +} + +impHandler0(targetMonster) +{ + return setTarget(ActorType::Monster, AllowSort_true); +} + +impHandler0(targetClosestMonster) +{ + return setTarget(ActorType::Monster, AllowSort_false); +} + +impHandler0(targetNPC) +{ + return setTarget(ActorType::Npc, AllowSort_true); +} + +impHandler0(targetMercenary) +{ + return setTarget(ActorType::Mercenary, AllowSort_true); +} + +impHandler0(targetSkillUnit) +{ + return setTarget(ActorType::SkillUnit, AllowSort_true); +} + +impHandler0(targetPet) +{ + return setTarget(ActorType::Pet, AllowSort_true); +} + +impHandler0(contextMenu) +{ + if (localPlayer == nullptr) + return false; + const Being *const target = localPlayer->getTarget(); + if (target == nullptr) + return true; + + popupMenu->showPopup(target->getPixelX(), + target->getPixelY(), + target); + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/actions/windows.cpp b/src/progs/manaplus/actions/windows.cpp new file mode 100644 index 000000000..7c9b57d0a --- /dev/null +++ b/src/progs/manaplus/actions/windows.cpp @@ -0,0 +1,383 @@ +/* + * The ManaPlus Client + * Copyright (C) 2012-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 "actions/windows.h" + +#include "actormanager.h" +#include "client.h" + +#include "actions/actiondef.h" + +#include "being/localplayer.h" + +#include "gui/dialogsmanager.h" + +#include "gui/windows/bankwindow.h" +#include "gui/windows/skilldialog.h" +#include "gui/windows/socialwindow.h" +#include "gui/windows/statuswindow.h" +#include "gui/windows/questswindow.h" +#include "gui/windows/whoisonline.h" +#include "gui/windows/chatwindow.h" +#include "gui/windows/debugwindow.h" +#include "gui/windows/didyouknowwindow.h" +#include "gui/windows/equipmentwindow.h" +#include "gui/windows/helpwindow.h" +#include "gui/windows/inventorywindow.h" +#include "gui/windows/killstats.h" +#include "gui/windows/mailwindow.h" +#include "gui/windows/minimap.h" +#include "gui/windows/outfitwindow.h" +#include "gui/windows/setupwindow.h" +#include "gui/windows/serverinfowindow.h" +#include "gui/windows/shopwindow.h" +#include "gui/windows/shortcutwindow.h" +#include "gui/windows/updaterwindow.h" + +#include "gui/widgets/createwidget.h" + +#include "gui/widgets/tabs/chat/chattab.h" + +#include "utils/gettext.h" + +#include "net/net.h" + +#include "debug.h" + +namespace Actions +{ + +impHandler0(setupWindowShow) +{ + if (setupWindow != nullptr) + { + if (setupWindow->isWindowVisible()) + { + setupWindow->doCancel(); + } + else + { + setupWindow->setVisible(Visible_true); + setupWindow->requestMoveToTop(); + } + return true; + } + return false; +} + +impHandler0(hideWindows) +{ + if (setupWindow != nullptr) + setupWindow->hideWindows(); + return true; +} + +static bool showHelpPage(const std::string &page, const bool showHide) +{ + if (helpWindow != nullptr) + { + if (showHide && helpWindow->isWindowVisible()) + { + helpWindow->setVisible(Visible_false); + } + else + { + helpWindow->loadHelp(page); + helpWindow->requestMoveToTop(); + } + return true; + } + return false; +} + +impHandler(helpWindowShow) +{ + if ((chatWindow == nullptr) || !chatWindow->isInputFocused()) + return showHelpPage("index", true); + if (event.tab == nullptr) + return showHelpPage("chatcommands", true); + switch (event.tab->getType()) + { + case ChatTabType::PARTY: + return showHelpPage("chatparty", true); + case ChatTabType::GUILD: + return showHelpPage("chatguild", true); + case ChatTabType::WHISPER: + return showHelpPage("chatwhisper", true); + case ChatTabType::DEBUG: + return showHelpPage("chatdebug", true); + case ChatTabType::TRADE: + return showHelpPage("chattrade", true); + case ChatTabType::BATTLE: + return showHelpPage("chatbattle", true); + case ChatTabType::LANG: + return showHelpPage("chatlang", true); + case ChatTabType::GM: + return showHelpPage("chatgm", true); + case ChatTabType::CHANNEL: + return showHelpPage("chatchannel", true); + default: + case ChatTabType::UNKNOWN: + case ChatTabType::INPUT: + return showHelpPage("chatcommands", true); + } +} + +impHandler0(aboutWindowShow) +{ + return showHelpPage("about", false); +} + +static void showHideWindow(Window *const window) +{ + if (window != nullptr) + { + window->setVisible(fromBool( + !window->isWindowVisible(), Visible)); + if (window->isWindowVisible()) + window->requestMoveToTop(); + } +} + +impHandler0(statusWindowShow) +{ + showHideWindow(statusWindow); + return true; +} + +impHandler0(inventoryWindowShow) +{ + showHideWindow(inventoryWindow); + return true; +} + +impHandler0(equipmentWindowShow) +{ + showHideWindow(equipmentWindow); + return true; +} + +impHandler0(skillDialogShow) +{ + showHideWindow(skillDialog); + return true; +} + +impHandler0(minimapWindowShow) +{ + if (minimap != nullptr) + { + minimap->toggle(); + return true; + } + return false; +} + +impHandler0(chatWindowShow) +{ + showHideWindow(chatWindow); + return true; +} + +impHandler0(shortcutWindowShow) +{ + showHideWindow(itemShortcutWindow); + return true; +} + +impHandler0(debugWindowShow) +{ + showHideWindow(debugWindow); + return true; +} + +impHandler0(socialWindowShow) +{ + showHideWindow(socialWindow); + return true; +} + +impHandler0(emoteShortcutWindowShow) +{ + showHideWindow(emoteShortcutWindow); + return true; +} + +impHandler0(outfitWindowShow) +{ + showHideWindow(outfitWindow); + return true; +} + +impHandler0(shopWindowShow) +{ + showHideWindow(shopWindow); + return true; +} + +impHandler0(dropShortcutWindowShow) +{ + showHideWindow(dropShortcutWindow); + return true; +} + +impHandler0(killStatsWindowShow) +{ + showHideWindow(killStats); + return true; +} + +impHandler0(spellShortcutWindowShow) +{ + showHideWindow(spellShortcutWindow); + return true; +} + +impHandler0(whoIsOnlineWindowShow) +{ + showHideWindow(whoIsOnline); + return true; +} + +impHandler0(didYouKnowWindowShow) +{ + showHideWindow(didYouKnowWindow); + return true; +} + +impHandler0(questsWindowShow) +{ + showHideWindow(questsWindow); + return true; +} + +impHandler0(bankWindowShow) +{ +#ifdef TMWA_SUPPORT + if (Net::getNetworkType() == ServerType::TMWATHENA) + return false; +#endif // TMWA_SUPPORT + + showHideWindow(bankWindow); + return true; +} + +impHandler0(cartWindowShow) +{ + if (Net::getNetworkType() == ServerType::TMWATHENA || + (localPlayer == nullptr) || + !localPlayer->getHaveCart()) + { + return false; + } + + showHideWindow(cartWindow); + if (inventoryWindow != nullptr) + inventoryWindow->updateDropButton(); + return true; +} + +impHandler0(updaterWindowShow) +{ + if (updaterWindow != nullptr) + updaterWindow->deleteSelf(); + else + DialogsManager::createUpdaterWindow(); + return true; +} + +impHandler0(quickWindowShow) +{ + if (setupWindow != nullptr) + { + if (setupWindow->isWindowVisible()) + setupWindow->doCancel(); + setupWindow->setVisible(Visible_true); + // TRANSLATORS: settings tab name + setupWindow->activateTab(_("Quick")); + setupWindow->requestMoveToTop(); + return true; + } + return false; +} + +impHandler0(mailWindowShow) +{ + showHideWindow(mailWindow); + return true; +} + +impHandler0(serverInfoWindowShow) +{ + if (serverInfoWindow != nullptr && + serverInfoWindow->isWindowVisible()) + { + serverInfoWindow->close(); + serverInfoWindow = nullptr; + } + else + { + serverInfoWindow = CREATEWIDGETR(ServerInfoWindow, + client->getCurrentServer()); + serverInfoWindow->requestMoveToTop(); + } + return true; +} + +impHandler(showItems) +{ + const std::string args = event.args; + if (args.empty()) + return false; + + Being *being = nullptr; + if (args[0] == ':') + { + being = actorManager->findBeing(fromInt(atoi( + args.substr(1).c_str()), BeingId)); + if ((being != nullptr) && being->getType() == ActorType::Monster) + being = nullptr; + } + else + { + being = actorManager->findBeingByName(args, ActorType::Player); + } + if (being == nullptr) + return true; + if (being == localPlayer) + { + if (equipmentWindow != nullptr && + !equipmentWindow->isWindowVisible()) + { + equipmentWindow->setVisible(Visible_true); + } + } + else + { + if (beingEquipmentWindow != nullptr) + { + beingEquipmentWindow->setBeing(being); + beingEquipmentWindow->setVisible(Visible_true); + } + } + return true; +} + +} // namespace Actions diff --git a/src/progs/manaplus/client.cpp b/src/progs/manaplus/client.cpp new file mode 100644 index 000000000..6e245029d --- /dev/null +++ b/src/progs/manaplus/client.cpp @@ -0,0 +1,1981 @@ +/* + * 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/client.h" + +#include "chatlogger.h" +#include "configmanager.h" +#include "dirs.h" +#include "eventsmanager.h" +#include "game.h" +#include "graphicsmanager.h" +#include "main.h" +#include "party.h" +#include "settings.h" +#include "soundmanager.h" +#include "spellmanager.h" + +#include "being/localplayer.h" +#include "being/playerinfo.h" +#include "being/playerrelations.h" + +#include "const/net/net.h" + +#include "enums/being/attributesstrings.h" + +#include "fs/virtfs/fs.h" +#include "fs/virtfs/tools.h" + +#include "gui/dialogsmanager.h" +#include "gui/gui.h" +#include "gui/skin.h" +#include "gui/popupmanager.h" +#include "gui/windowmanager.h" + +#include "gui/shortcut/dropshortcut.h" +#include "gui/shortcut/emoteshortcut.h" +#include "gui/shortcut/itemshortcut.h" +#include "gui/shortcut/spellshortcut.h" + +#include "gui/windows/changeemaildialog.h" +#include "gui/windows/changepassworddialog.h" +#include "gui/windows/charselectdialog.h" +#include "gui/windows/connectiondialog.h" +#include "gui/windows/equipmentwindow.h" +#include "gui/windows/logindialog.h" +#include "gui/windows/npcdialog.h" +#include "gui/windows/okdialog.h" +#include "gui/windows/registerdialog.h" +#include "gui/windows/serverdialog.h" +#include "gui/windows/setupwindow.h" +#include "gui/windows/updaterwindow.h" +#include "gui/windows/quitdialog.h" +#include "gui/windows/worldselectdialog.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/createwidget.h" +#include "gui/widgets/desktop.h" +#include "gui/widgets/windowcontainer.h" + +#include "input/inputmanager.h" +#include "input/joystick.h" +#include "input/keyboardconfig.h" + +#include "input/touch/touchmanager.h" + +#include "net/charserverhandler.h" +#include "net/chathandler.h" +#include "net/download.h" +#include "net/gamehandler.h" +#include "net/generalhandler.h" +#include "net/guildhandler.h" +#include "net/inventoryhandler.h" +#include "net/ipc.h" +#include "net/loginhandler.h" +#include "net/net.h" +#include "net/updatetypeoperators.h" +#include "net/packetlimiter.h" +#include "net/partyhandler.h" + +#ifdef TMWA_SUPPORT +#include "net/tmwa/guildmanager.h" +#endif // TMWA_SUPPORT + +#include "particle/particleengine.h" + +#include "resources/dbmanager.h" +#include "resources/imagehelper.h" + +#include "resources/dye/dyepalette.h" + +#include "resources/resourcemanager/resourcemanager.h" + +#include "resources/sprite/spritereference.h" + +#include "utils/checkutils.h" +#include "utils/cpu.h" +#include "utils/delete2.h" +#include "utils/dumplibs.h" +#include "utils/dumpsizes.h" +#include "utils/env.h" +#include "utils/fuzzer.h" +#include "utils/gettext.h" +#include "utils/gettexthelper.h" +#include "utils/mrand.h" +#ifdef ANDROID +#include "fs/paths.h" +#endif // ANDROID +#include "utils/sdlcheckutils.h" +#include "utils/timer.h" + +#include "utils/translation/translationmanager.h" + +#include "listeners/assertlistener.h" +#include "listeners/errorlistener.h" + +#ifdef USE_OPENGL +#include "test/testlauncher.h" +#include "test/testmain.h" +#else // USE_OPENGL +#include "configuration.h" +#endif // USE_OPENGL + +#ifdef WIN32 +PRAGMA48(GCC diagnostic push) +PRAGMA48(GCC diagnostic ignored "-Wshadow") +#include <SDL_syswm.h> +PRAGMA48(GCC diagnostic pop) +#include "fs/specialfolder.h" +#undef ERROR +#endif // WIN32 + +#ifdef ANDROID +#ifndef USE_SDL2 +PRAGMA48(GCC diagnostic push) +PRAGMA48(GCC diagnostic ignored "-Wshadow") +#include <SDL_screenkeyboard.h> +PRAGMA48(GCC diagnostic pop) +#include <fstream> +#endif // USE_SDL2 +#endif // ANDROID + +#include <sys/stat.h> + +#ifdef USE_MUMBLE +#include "mumblemanager.h" +#endif // USE_MUMBLE + +PRAGMA48(GCC diagnostic push) +PRAGMA48(GCC diagnostic ignored "-Wshadow") +#ifdef USE_SDL2 +#include <SDL2_framerate.h> +#else // USE_SDL2 +#include <SDL_framerate.h> +#endif // USE_SDL2 +PRAGMA48(GCC diagnostic pop) + +#include "debug.h" + +std::string errorMessage; +LoginData loginData; + +Client *client = nullptr; + +extern FPSmanager fpsManager; +extern int evolPacketOffset; + +volatile bool runCounters; +bool isSafeMode = false; +int serverVersion = 0; +int packetVersion = 0; +unsigned int tmwServerVersion = 0; +time_t start_time; +unsigned int mLastHost = 0; +unsigned long mSearchHash = 0; +int textures_count = 0; +volatile bool isTerminate = false; + +namespace +{ + class AccountListener final : public ActionListener + { + public: + AccountListener() + { } + + A_DELETE_COPY(AccountListener) + + void action(const ActionEvent &event A_UNUSED) override final + { + client->setState(State::CHAR_SELECT); + } + } accountListener; + + class LoginListener final : public ActionListener + { + public: + LoginListener() + { } + + A_DELETE_COPY(LoginListener) + + void action(const ActionEvent &event A_UNUSED) override final + { + client->setState(State::PRE_LOGIN); + } + } loginListener; +} // namespace + +Client::Client() : + ActionListener(), + mCurrentServer(), + mGame(nullptr), + mCurrentDialog(nullptr), + mQuitDialog(nullptr), + mSetupButton(nullptr), + mVideoButton(nullptr), + mHelpButton(nullptr), + mAboutButton(nullptr), + mThemesButton(nullptr), + mPerfomanceButton(nullptr), +#ifdef ANDROID + mCloseButton(nullptr), +#endif // ANDROID + mState(State::CHOOSE_SERVER), + mOldState(State::START), + mSkin(nullptr), + mButtonPadding(1), + mButtonSpacing(3), + mPing(0), + mConfigAutoSaved(false) +{ + WindowManager::init(); +} + +void Client::testsInit() +{ + if (!settings.options.test.empty() && + settings.options.test != "99") + { + gameInit(); + } + else + { + initRand(); + logger = new Logger; + Dirs::initLocalDataDir(); + Dirs::initTempDir(); + Dirs::initConfigDir(); + GettextHelper::initLang(); + } +} + +void Client::gameInit() +{ + logger = new Logger; + + initRand(); + + assertListener = new AssertListener; + // Load branding information + if (!settings.options.brandingPath.empty()) + branding.init(settings.options.brandingPath); + setBrandingDefaults(branding); + + Dirs::initRootDir(); + Dirs::initHomeDir(); + + // Configure logger + if (!settings.options.logFileName.empty()) + { + settings.logFileName = settings.options.logFileName; + } + else + { + settings.logFileName = pathJoin(settings.localDataDir, + "manaplus.log"); + } + logger->log("Log file: " + settings.logFileName); + logger->setLogFile(settings.logFileName); + +#ifdef USE_FUZZER + Fuzzer::init(); +#endif // USE_FUZZER + + if (settings.options.ipc == true) + IPC::start(); + if (settings.options.test.empty()) + ConfigManager::backupConfig("config.xml"); + ConfigManager::initConfiguration(); + settings.init(); + Net::loadIgnorePackets(); + setPathsDefaults(paths); + initFeatures(); + initPaths(); + logger->log("init 4"); + logger->setDebugLog(config.getBoolValue("debugLog")); + logger->setReportUnimplemented(config.getBoolValue("unimplimentedLog")); + + config.incValue("runcount"); + +#ifndef ANDROID + if (settings.options.test.empty()) + ConfigManager::storeSafeParameters(); +#endif // ANDROID + + if (!VirtFs::setWriteDir(settings.localDataDir)) + { + logger->error(strprintf("%s couldn't be set as home directory! " + "Exiting.", settings.localDataDir.c_str())); + } + + GettextHelper::initLang(); + + chatLogger = new ChatLogger; + if (settings.options.chatLogDir.empty()) + { + chatLogger->setBaseLogDir(settings.localDataDir + + std::string("/logs/")); + } + else + { + chatLogger->setBaseLogDir(settings.options.chatLogDir); + } + + logger->setLogToStandardOut(config.getBoolValue("logToStandardOut")); + + // Log the client version + logger->log1(FULL_VERSION); + logger->log("Start configPath: " + config.getConfigPath()); + + Dirs::initScreenshotDir(); + + updateEnv(); + dumpLibs(); + dumpSizes(); + + // Initialize SDL + logger->log1("Initializing SDL..."); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) + { + logger->safeError(strprintf("Could not initialize SDL: %s", + SDL_GetError())); + } + atexit(SDL_Quit); + + PacketLimiter::initPacketLimiter(); +#ifndef USE_SDL2 + SDL_EnableUNICODE(1); +#endif // USE_SDL2 + + WindowManager::applyKeyRepeat(); + + // disable unused SDL events +#ifndef USE_SDL2 + SDL_EventState(SDL_VIDEOEXPOSE, SDL_IGNORE); +#endif // USE_SDL2 + + SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE); + SDL_EventState(SDL_USEREVENT, SDL_IGNORE); + +#ifdef WIN32 + Dirs::extractDataDir(); + Dirs::mountDataDir(); +#endif // WIN32 + + WindowManager::setIcon(); + ConfigManager::checkConfigVersion(); + logVars(); + Cpu::detect(); + DyePalette::initFunctions(); +#if defined(USE_OPENGL) +#if !defined(ANDROID) && !defined(__APPLE__) && \ + !defined(__native_client__) && !defined(UNITTESTS) + if (!settings.options.safeMode && + settings.options.renderer < 0 && + settings.options.test.empty() && + !settings.options.validate && + !config.getBoolValue("videodetected")) + { + graphicsManager.detectVideoSettings(); + } +#endif // !defined(ANDROID) && !defined(__APPLE__) && + // !defined(__native_client__) && !defined(UNITTESTS) +#endif // defined(USE_OPENGL) + + initGraphics(); + + touchManager.init(); + +#ifndef WIN32 + Dirs::extractDataDir(); + Dirs::mountDataDir(); +#endif // WIN32 + + Dirs::updateDataPath(); + + // Add the main data directories to our PhysicsFS search path + if (!settings.options.dataPath.empty()) + { + VirtFs::mountDir(settings.options.dataPath, + Append_false); + } + + // Add the local data directory to PhysicsFS search path + VirtFs::mountDir(settings.localDataDir, + Append_false); + TranslationManager::loadCurrentLang(); + TranslationManager::loadDictionaryLang(); +#ifdef ENABLE_CUSTOMNLS + TranslationManager::loadGettextLang(); +#endif // ENABLE_CUSTOMNLS + + WindowManager::initTitle(); + + mainGraphics->postInit(); + + theme = new Theme; + Theme::selectSkin(); + ActorSprite::load(); + touchManager.init(); + + // Initialize the item and emote shortcuts. + for (size_t f = 0; f < SHORTCUT_TABS; f ++) + itemShortcut[f] = new ItemShortcut(f); + emoteShortcut = new EmoteShortcut; + dropShortcut = new DropShortcut; + + gui = new Gui; + gui->postInit(mainGraphics); + dialogsManager = new DialogsManager; + popupManager = new PopupManager; + + initSoundManager(); + eventsManager.init(); + + // Initialize keyboard + keyboard.init(); + inputManager.init(); + + // Initialise player relations + playerRelations.init(); + Joystick::init(); + WindowManager::createWindows(); + + keyboard.update(); + if (joystick != nullptr) + joystick->update(); + + // Initialize default server + mCurrentServer.hostname = settings.options.serverName; + mCurrentServer.port = settings.options.serverPort; + if (!settings.options.serverType.empty()) + { + mCurrentServer.type = ServerInfo::parseType( + settings.options.serverType); + } + + loginData.username = settings.options.username; + loginData.password = settings.options.password; + LoginDialog::savedPassword = settings.options.password; + loginData.remember = (serverConfig.getValue("remember", 1) != 0); + loginData.registerLogin = false; + + if (mCurrentServer.hostname.empty()) + { + mCurrentServer.hostname = branding.getValue("defaultServer", ""); + settings.options.serverName = mCurrentServer.hostname; + } + + if (mCurrentServer.port == 0) + { + mCurrentServer.port = CAST_U16(branding.getValue( + "defaultPort", CAST_S32(DEFAULT_PORT))); + mCurrentServer.type = ServerInfo::parseType( + branding.getValue("defaultServerType", "tmwathena")); + } + + chatLogger->setServerName(mCurrentServer.hostname); + + if (loginData.username.empty() && loginData.remember) + loginData.username = serverConfig.getValue("username", ""); + + if (mState != State::ERROR) + mState = State::CHOOSE_SERVER; + + startTimers(); + + const int fpsLimit = config.getIntValue("fpslimit"); + settings.limitFps = fpsLimit > 0; + + SDL_initFramerate(&fpsManager); + WindowManager::setFramerate(fpsLimit); + initConfigListeners(); + + settings.guiAlpha = config.getFloatValue("guialpha"); + optionChanged("fpslimit"); + + start_time = time(nullptr); + + PlayerInfo::init(); + +#ifdef ANDROID +#ifndef USE_SDL2 + WindowManager::updateScreenKeyboard(SDL_GetScreenKeyboardHeight(nullptr)); +#endif // USE_SDL2 +#endif // ANDROID + +#ifdef USE_MUMBLE + if (!mumbleManager) + mumbleManager = new MumbleManager; +#endif // USE_MUMBLE + + mSkin = theme->load("windowmenu.xml", ""); + if (mSkin != nullptr) + { + mButtonPadding = mSkin->getPadding(); + mButtonSpacing = mSkin->getOption("spacing", 3); + } + if (settings.options.error) + inputManager.executeAction(InputAction::ERROR); + + if (settings.options.validate == true) + runValidate(); +} + +Client::~Client() +{ + if (!settings.options.testMode) + gameClear(); + else + testsClear(); + CHECKLISTENERS +} + +void Client::initConfigListeners() +{ + config.addListener("fpslimit", this); + config.addListener("guialpha", this); + config.addListener("gamma", this); + config.addListener("enableGamma", this); + config.addListener("particleEmitterSkip", this); + config.addListener("vsync", this); + config.addListener("repeateDelay", this); + config.addListener("repeateInterval", this); + config.addListener("logInput", this); +} + +void Client::initSoundManager() +{ + // Initialize sound engine + try + { + if (config.getBoolValue("sound")) + soundManager.init(); + + soundManager.setSfxVolume(config.getIntValue("sfxVolume")); + soundManager.setMusicVolume(config.getIntValue("musicVolume")); + } + catch (const char *const err) + { + mState = State::ERROR; + errorMessage = err; + logger->log("Warning: %s", err); + } + soundManager.playMusic(branding.getValue( + "loginMusic", + "keprohm.ogg"), + SkipError_true); +} + +void Client::initGraphics() +{ + WindowManager::applyVSync(); + runCounters = config.getBoolValue("packetcounters"); + + graphicsManager.initGraphics(); + + imageHelper->postInit(); + setConfigDefaults2(config); + WindowManager::applyGrabMode(); + WindowManager::applyGamma(); + + mainGraphics->beginDraw(); +} + +void Client::testsClear() +{ + if (!settings.options.test.empty()) + gameClear(); + else + BeingInfo::clear(); +} + +void Client::gameClear() +{ + if (logger != nullptr) + logger->log1("Quitting1"); + isTerminate = true; + config.removeListeners(this); + + delete2(assertListener); + + IPC::stop(); + eventsManager.shutdown(); + WindowManager::deleteWindows(); + if (windowContainer != nullptr) + windowContainer->slowLogic(); + + stopTimers(); + DbManager::unloadDb(); + + if (loginHandler != nullptr) + loginHandler->clearWorlds(); + + if (chatHandler != nullptr) + chatHandler->clear(); + + if (charServerHandler != nullptr) + charServerHandler->clear(); + + delete2(ipc); + +#ifdef USE_MUMBLE + delete2(mumbleManager); +#endif // USE_MUMBLE + + PlayerInfo::deinit(); + + // Before config.write() since it writes the shortcuts to the config + for (unsigned f = 0; f < SHORTCUT_TABS; f ++) + delete2(itemShortcut[f]) + delete2(emoteShortcut); + delete2(dropShortcut); + + playerRelations.store(); + + if (logger != nullptr) + logger->log1("Quitting2"); + + delete2(mCurrentDialog); + delete2(popupManager); + delete2(dialogsManager); + delete2(gui); + + if (inventoryHandler != nullptr) + inventoryHandler->clear(); + + if (logger != nullptr) + logger->log1("Quitting3"); + + touchManager.clear(); + + GraphicsManager::deleteRenderers(); + + if (logger != nullptr) + logger->log1("Quitting4"); + + XML::cleanupXML(); + + if (logger != nullptr) + logger->log1("Quitting5"); + + BeingInfo::clear(); + + // Shutdown sound + soundManager.close(); + + if (logger != nullptr) + logger->log1("Quitting6"); + + ActorSprite::unload(); + + ResourceManager::deleteInstance(); + + if (logger != nullptr) + logger->log1("Quitting8"); + + WindowManager::deleteIcon(); + + if (logger != nullptr) + logger->log1("Quitting9"); + + delete2(joystick); + + keyboard.deinit(); + + if (logger != nullptr) + logger->log1("Quitting10"); + + soundManager.shutdown(); + touchManager.shutdown(); + +#ifdef DEBUG_CONFIG + config.enableKeyLogging(); +#endif // DEBUG_CONFIG + + config.removeOldKeys(); + config.write(); + serverConfig.write(); + + config.clear(); + serverConfig.clear(); + + if (logger != nullptr) + logger->log1("Quitting11"); + +#ifdef USE_PROFILER + Perfomance::clear(); +#endif // USE_PROFILER + +#ifdef DEBUG_OPENGL_LEAKS + if (logger) + logger->log("textures left: %d", textures_count); +#endif // DEBUG_OPENGL_LEAKS + + Graphics::cleanUp(); + + if (logger != nullptr) + logger->log1("Quitting12"); + + delete2(chatLogger); + TranslationManager::close(); +} + +int Client::testsExec() +{ +#ifdef USE_OPENGL + if (settings.options.test.empty()) + { + TestMain test; + return test.exec(); + } + else + { + TestLauncher launcher(settings.options.test); + return launcher.exec(); + } +#else // USE_OPENGL + + return 0; +#endif // USE_OPENGL +} + +#define ADDBUTTON(var, object) var = object; \ + x -= var->getWidth() + mButtonSpacing; \ + var->setPosition(x, mButtonPadding); \ + top->add(var); + +void Client::stateConnectGame1() +{ + if ((gameHandler != nullptr) && + (loginHandler != nullptr) && + gameHandler->isConnected()) + { + loginHandler->disconnect(); + } +} + +void Client::stateConnectServer1() +{ + if (mOldState == State::CHOOSE_SERVER) + { + settings.serverName = mCurrentServer.hostname; + ConfigManager::initServerConfig(mCurrentServer.hostname); + PacketLimiter::initPacketLimiter(); + initTradeFilter(); + Dirs::initUsersDir(); + playerRelations.init(); + + // Initialize the item and emote shortcuts. + for (unsigned f = 0; f < SHORTCUT_TABS; f ++) + { + delete itemShortcut[f]; + itemShortcut[f] = new ItemShortcut(f); + } + delete emoteShortcut; + emoteShortcut = new EmoteShortcut; + + // Initialize the drop shortcuts. + delete dropShortcut; + dropShortcut = new DropShortcut; + + initFeatures(); + PlayerInfo::loadData(); + loginData.registerUrl = mCurrentServer.registerUrl; + loginData.packetVersion = mCurrentServer.packetVersion; + if (!mCurrentServer.onlineListUrl.empty()) + settings.onlineListUrl = mCurrentServer.onlineListUrl; + else + settings.onlineListUrl = settings.serverName; + settings.persistentIp = mCurrentServer.persistentIp; + settings.supportUrl = mCurrentServer.supportUrl; + settings.updateMirrors = mCurrentServer.updateMirrors; + settings.enableRemoteCommands = (serverConfig.getValue( + "enableRemoteCommands", 1) != 0); + + if (settings.options.username.empty()) + { + if (loginData.remember) + loginData.username = serverConfig.getValue("username", ""); + else + loginData.username.clear(); + } + else + { + loginData.username = settings.options.username; + } + settings.login = loginData.username; + WindowManager::updateTitle(); + + loginData.remember = (serverConfig.getValue("remember", 1) != 0); + Net::connectToServer(mCurrentServer); + +#ifdef USE_MUMBLE + if (mumbleManager) + mumbleManager->setServer(mCurrentServer.hostname); +#endif // USE_MUMBLE + +#ifdef TMWA_SUPPORT + GuildManager::init(); +#endif // TMWA_SUPPORT + + if (!mConfigAutoSaved) + { + mConfigAutoSaved = true; + config.write(); + } + } + else if (mOldState != State::CHOOSE_SERVER && + (loginHandler != nullptr) && + loginHandler->isConnected()) + { + mState = State::PRE_LOGIN; + } +} + +void Client::stateWorldSelect1() +{ + if (mOldState == State::UPDATE && + (loginHandler != nullptr)) + { + if (loginHandler->getWorlds().size() < 2) + mState = State::PRE_LOGIN; + } +} + +void Client::stateGame1() +{ + if (gui == nullptr) + return; + + BasicContainer2 *const top = static_cast<BasicContainer2*>( + gui->getTop()); + + if (top == nullptr) + return; + + CREATEWIDGETV(desktop, Desktop, nullptr); + top->add(desktop); + int x = top->getWidth() - mButtonPadding; + ADDBUTTON(mSetupButton, new Button(desktop, + // TRANSLATORS: setup tab quick button + _("Setup"), "Setup", this)) + ADDBUTTON(mPerfomanceButton, new Button(desktop, + // TRANSLATORS: perfoamance tab quick button + _("Performance"), "Perfomance", this)) + ADDBUTTON(mVideoButton, new Button(desktop, + // TRANSLATORS: video tab quick button + _("Video"), "Video", this)) + ADDBUTTON(mThemesButton, new Button(desktop, + // TRANSLATORS: theme tab quick button + _("Theme"), "Themes", this)) + ADDBUTTON(mAboutButton, new Button(desktop, + // TRANSLATORS: theme tab quick button + _("About"), "about", this)) + ADDBUTTON(mHelpButton, new Button(desktop, + // TRANSLATORS: theme tab quick button + _("Help"), "help", this)) +#ifdef ANDROID + ADDBUTTON(mCloseButton, new Button(desktop, + // TRANSLATORS: close quick button + _("Close"), "close", this)) +#endif // ANDROID + + desktop->setSize(mainGraphics->getWidth(), + mainGraphics->getHeight()); +} + +void Client::stateSwitchLogin1() +{ + if (mOldState == State::GAME && + (gameHandler != nullptr)) + { + gameHandler->disconnect(); + } +} + +int Client::gameExec() +{ + int lastTickTime = tick_time; + + while (mState != State::EXIT) + { + PROFILER_START(); + if (eventsManager.handleEvents()) + continue; + + BLOCK_START("Client::gameExec 3") + if (generalHandler != nullptr) + generalHandler->flushNetwork(); + BLOCK_END("Client::gameExec 3") + + BLOCK_START("Client::gameExec 4") + if (gui != nullptr) + gui->logic(); + cur_time = time(nullptr); + int k = 0; + while (lastTickTime != tick_time && + k < 40) + { + if (mGame != nullptr) + mGame->logic(); + else if (gui != nullptr) + gui->handleInput(); + + ++lastTickTime; + k ++; + } + soundManager.logic(); + + logic_count += k; + if (gui != nullptr) + gui->slowLogic(); + if (mGame != nullptr) + mGame->slowLogic(); + slowLogic(); + BLOCK_END("Client::gameExec 4") + + // This is done because at some point tick_time will wrap. + lastTickTime = tick_time; + + // Update the screen when application is visible, delay otherwise. + if (!WindowManager::getIsMinimized()) + { + frame_count++; + if (gui != nullptr) + gui->draw(); + mainGraphics->updateScreen(); + } + else + { + SDL_Delay(100); + } + + BLOCK_START("~Client::SDL_framerateDelay") + if (settings.limitFps) + SDL_framerateDelay(&fpsManager); + BLOCK_END("~Client::SDL_framerateDelay") + + BLOCK_START("Client::gameExec 6") + if (mState == State::CONNECT_GAME) + { + stateConnectGame1(); + } + else if (mState == State::CONNECT_SERVER) + { + stateConnectServer1(); + } + else if (mState == State::WORLD_SELECT) + { + stateWorldSelect1(); + } + else if (mOldState == State::START || + (mOldState == State::GAME && mState != State::GAME)) + { + stateGame1(); + } + else if (mState == State::SWITCH_LOGIN) + { + stateSwitchLogin1(); + } + BLOCK_END("Client::gameExec 6") + + if (mState != mOldState) + { + BLOCK_START("Client::gameExec 7") + PlayerInfo::stateChange(mState); + + if (mOldState == State::GAME) + { + delete2(mGame); + assertListener = new AssertListener; + Game::clearInstance(); + ResourceManager::cleanOrphans(); + Party::clearParties(); + Guild::clearGuilds(); + NpcDialog::clearDialogs(); + if (guildHandler != nullptr) + guildHandler->clear(); + if (partyHandler != nullptr) + partyHandler->clear(); + if (chatLogger != nullptr) + chatLogger->clear(); + if (!settings.options.dataPath.empty()) + UpdaterWindow::unloadMods(settings.options.dataPath); + else + UpdaterWindow::unloadMods(settings.oldUpdates); + if (!settings.options.skipUpdate) + UpdaterWindow::unloadMods(settings.oldUpdates + "/fix/"); + } + else if (mOldState == State::CHAR_SELECT) + { + if (mState != State::CHANGEPASSWORD && + charServerHandler != nullptr) + { + charServerHandler->clear(); + } + } + + mOldState = mState; + + // Get rid of the dialog of the previous state + delete2(mCurrentDialog); + + // State has changed, while the quitDialog was active, it might + // not be correct anymore + if (mQuitDialog != nullptr) + { + mQuitDialog->scheduleDelete(); + mQuitDialog = nullptr; + } + BLOCK_END("Client::gameExec 7") + + BLOCK_START("Client::gameExec 8") + switch (mState) + { + case State::CHOOSE_SERVER: + { + BLOCK_START("Client::gameExec STATE_CHOOSE_SERVER") + logger->log1("State: CHOOSE SERVER"); + unloadData(); + + // Allow changing this using a server choice dialog + // We show the dialog box only if the command-line + // options weren't set. + if (settings.options.serverName.empty() && + settings.options.serverPort == 0 && + !branding.getValue("onlineServerList", "a").empty()) + { + // Don't allow an alpha opacity + // lower than the default value + theme->setMinimumOpacity(0.8F); + + CREATEWIDGETV(mCurrentDialog, ServerDialog, + &mCurrentServer, + settings.configDir); + } + else + { + mState = State::CONNECT_SERVER; + + // Reset options so that cancelling or connect + // timeout will show the server dialog. + settings.options.serverName.clear(); + settings.options.serverPort = 0; + } + BLOCK_END("Client::gameExec STATE_CHOOSE_SERVER") + break; + } + + case State::CONNECT_SERVER: + BLOCK_START("Client::gameExec State::CONNECT_SERVER") + logger->log1("State: CONNECT SERVER"); + loginData.updateHosts.clear(); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Connecting to server"), + State::SWITCH_SERVER); + TranslationManager::loadCurrentLang(); + TranslationManager::loadDictionaryLang(); + BLOCK_END("Client::gameExec State::CONNECT_SERVER") + break; + + case State::PRE_LOGIN: + logger->log1("State: PRE_LOGIN"); + break; + + case State::LOGIN: + BLOCK_START("Client::gameExec State::LOGIN") + logger->log1("State: LOGIN"); + // Don't allow an alpha opacity + // lower than the default value + theme->setMinimumOpacity(0.8F); + + if (packetVersion == 0) + { + packetVersion = loginData.packetVersion; + if (packetVersion != 0) + { + loginHandler->updatePacketVersion(); + logger->log("Preconfigured packet version: %d", + packetVersion); + } + } + + loginData.updateType = static_cast<UpdateTypeT>( + serverConfig.getValue("updateType", 0)); + + mSearchHash = Net::Download::adlerBuffer( + const_cast<char*>(mCurrentServer.hostname.c_str()), + CAST_S32(mCurrentServer.hostname.size())); + if (settings.options.username.empty() || + settings.options.password.empty()) + { + CREATEWIDGETV(mCurrentDialog, LoginDialog, + loginData, + &mCurrentServer, + &settings.options.updateHost); + } + else + { + mState = State::LOGIN_ATTEMPT; + // Clear the password so that when login fails, the + // dialog will show up next time. + settings.options.password.clear(); + } + BLOCK_END("Client::gameExec State::LOGIN") + break; + + case State::LOGIN_ATTEMPT: + BLOCK_START("Client::gameExec State::LOGIN_ATTEMPT") + logger->log1("State: LOGIN ATTEMPT"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Logging in"), + State::SWITCH_SERVER); + if (loginHandler != nullptr) + loginHandler->loginOrRegister(&loginData); + BLOCK_END("Client::gameExec State::LOGIN_ATTEMPT") + break; + + case State::WORLD_SELECT: + BLOCK_START("Client::gameExec State::WORLD_SELECT") + logger->log1("State: WORLD SELECT"); + { + TranslationManager::loadCurrentLang(); + TranslationManager::loadDictionaryLang(); + if (loginHandler == nullptr) + { + BLOCK_END("Client::gameExec State::WORLD_SELECT") + break; + } + Worlds worlds = loginHandler->getWorlds(); + + if (worlds.empty()) + { + // Trust that the netcode knows what it's doing + mState = State::UPDATE; + } + else if (worlds.size() == 1) + { + loginHandler->chooseServer( + 0, mCurrentServer.persistentIp); + mState = State::UPDATE; + } + else + { + CREATEWIDGETV(mCurrentDialog, WorldSelectDialog, + worlds); + if (settings.options.chooseDefault) + { + static_cast<WorldSelectDialog*>(mCurrentDialog) + ->action(ActionEvent(nullptr, "ok")); + } + } + } + BLOCK_END("Client::gameExec State::WORLD_SELECT") + break; + + case State::WORLD_SELECT_ATTEMPT: + BLOCK_START("Client::gameExec State::WORLD_SELECT_ATTEMPT") + logger->log1("State: WORLD SELECT ATTEMPT"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Entering game world"), + State::WORLD_SELECT); + BLOCK_END("Client::gameExec State::WORLD_SELECT_ATTEMPT") + break; + + case State::UPDATE: + BLOCK_START("Client::gameExec State::UPDATE") + logger->log1("State: UPDATE"); + + // Determine which source to use for the update host + if (!settings.options.updateHost.empty()) + settings.updateHost = settings.options.updateHost; + else + settings.updateHost = loginData.updateHost; + Dirs::initUpdatesDir(); + + if (!settings.oldUpdates.empty()) + UpdaterWindow::unloadUpdates(settings.oldUpdates); + + if (settings.options.skipUpdate) + { + mState = State::LOAD_DATA; + settings.oldUpdates.clear(); + UpdaterWindow::loadDirMods(settings.options.dataPath); + } + else if ((loginData.updateType & UpdateType::Skip) != 0) + { + settings.oldUpdates = pathJoin(settings.localDataDir, + settings.updatesDir); + UpdaterWindow::loadLocalUpdates(settings.oldUpdates); + mState = State::LOAD_DATA; + } + else + { + settings.oldUpdates = pathJoin(settings.localDataDir, + settings.updatesDir); + CREATEWIDGETV(mCurrentDialog, UpdaterWindow, + settings.updateHost, + settings.oldUpdates, + settings.options.dataPath.empty(), + loginData.updateType); + } + BLOCK_END("Client::gameExec State::UPDATE") + break; + + case State::LOAD_DATA: + { + BLOCK_START("Client::gameExec State::LOAD_DATA") + logger->log1("State: LOAD DATA"); + + loadData(); + + mState = State::GET_CHARACTERS; + BLOCK_END("Client::gameExec State::LOAD_DATA") + break; + } + case State::GET_CHARACTERS: + BLOCK_START("Client::gameExec State::GET_CHARACTERS") + logger->log1("State: GET CHARACTERS"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Requesting characters"), + State::SWITCH_SERVER); + if (charServerHandler != nullptr) + charServerHandler->requestCharacters(); + BLOCK_END("Client::gameExec State::GET_CHARACTERS") + break; + + case State::CHAR_SELECT: + BLOCK_START("Client::gameExec State::CHAR_SELECT") + logger->log1("State: CHAR SELECT"); + // Don't allow an alpha opacity + // lower than the default value + theme->setMinimumOpacity(0.8F); + + settings.login = loginData.username; + WindowManager::updateTitle(); + + CREATEWIDGETV(mCurrentDialog, CharSelectDialog, + loginData); + + if (!(static_cast<CharSelectDialog*>(mCurrentDialog)) + ->selectByName(settings.options.character, + CharSelectDialog::Choose)) + { + (static_cast<CharSelectDialog*>(mCurrentDialog)) + ->selectByName( + serverConfig.getValue("lastCharacter", ""), + settings.options.chooseDefault ? + CharSelectDialog::Choose : + CharSelectDialog::Focus); + } + + // Choosing character on the command line should work only + // once, clear it so that 'switch character' works. + settings.options.character.clear(); + BLOCK_END("Client::gameExec State::CHAR_SELECT") + break; + + case State::CONNECT_GAME: + BLOCK_START("Client::gameExec State::CONNECT_GAME") + logger->log1("State: CONNECT GAME"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Connecting to the game server"), + State::CHOOSE_SERVER); + if (gameHandler != nullptr) + gameHandler->connect(); + BLOCK_END("Client::gameExec State::CONNECT_GAME") + break; + + case State::CHANGE_MAP: + BLOCK_START("Client::gameExec State::CHANGE_MAP") + logger->log1("State: CHANGE_MAP"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Changing game servers"), + State::SWITCH_CHARACTER); + if (gameHandler != nullptr) + gameHandler->connect(); + BLOCK_END("Client::gameExec State::CHANGE_MAP") + break; + + case State::GAME: + BLOCK_START("Client::gameExec State::GAME") + if (localPlayer != nullptr) + { + logger->log("Memorizing selected character %s", + localPlayer->getName().c_str()); + serverConfig.setValue("lastCharacter", + localPlayer->getName()); +#ifdef USE_MUMBLE + if (mumbleManager) + mumbleManager->setPlayer(localPlayer->getName()); +#endif // USE_MUMBLE + } + + // Fade out logon-music here too to give the desired effect + // of "flowing" into the game. + soundManager.fadeOutMusic(1000); + + // Allow any alpha opacity + theme->setMinimumOpacity(-1.0F); + + if (chatLogger != nullptr) + chatLogger->setServerName(settings.serverName); + +#ifdef ANDROID + delete2(mCloseButton); +#endif // ANDROID + + delete2(mSetupButton); + delete2(mVideoButton); + delete2(mThemesButton); + delete2(mAboutButton); + delete2(mHelpButton); + delete2(mPerfomanceButton); + delete2(desktop); + + mCurrentDialog = nullptr; + + logger->log1("State: GAME"); + if (generalHandler != nullptr) + generalHandler->reloadPartially(); + mGame = new Game; + BLOCK_END("Client::gameExec State::GAME") + break; + + case State::LOGIN_ERROR: + BLOCK_START("Client::gameExec State::LOGIN_ERROR") + logger->log1("State: LOGIN ERROR"); + CREATEWIDGETV(mCurrentDialog, OkDialog, + // TRANSLATORS: error dialog header + _("Error"), + errorMessage, + // TRANSLATORS: ok dialog button + _("Close"), + DialogType::ERROR, + Modal_true, + ShowCenter_true, + nullptr, + 260); + mCurrentDialog->addActionListener(&loginListener); + mCurrentDialog = nullptr; // OkDialog deletes itself + BLOCK_END("Client::gameExec State::LOGIN_ERROR") + break; + + case State::ACCOUNTCHANGE_ERROR: + BLOCK_START("Client::gameExec State::ACCOUNTCHANGE_ERROR") + logger->log1("State: ACCOUNT CHANGE ERROR"); + CREATEWIDGETV(mCurrentDialog, OkDialog, + // TRANSLATORS: error dialog header + _("Error"), + errorMessage, + // TRANSLATORS: ok dialog button + _("Close"), + DialogType::ERROR, + Modal_true, + ShowCenter_true, + nullptr, + 260); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = nullptr; // OkDialog deletes itself + BLOCK_END("Client::gameExec State::ACCOUNTCHANGE_ERROR") + break; + + case State::REGISTER_PREP: + BLOCK_START("Client::gameExec State::REGISTER_PREP") + logger->log1("State: REGISTER_PREP"); + CREATEWIDGETV(mCurrentDialog, ConnectionDialog, + // TRANSLATORS: connection dialog header + _("Requesting registration details"), + State::LOGIN); + loginHandler->getRegistrationDetails(); + BLOCK_END("Client::gameExec State::REGISTER_PREP") + break; + + case State::REGISTER: + logger->log1("State: REGISTER"); + CREATEWIDGETV(mCurrentDialog, RegisterDialog, + loginData); + break; + + case State::REGISTER_ATTEMPT: + BLOCK_START("Client::gameExec State::REGISTER_ATTEMPT") + logger->log("Username is %s", loginData.username.c_str()); + if (loginHandler != nullptr) + loginHandler->registerAccount(&loginData); + BLOCK_END("Client::gameExec State::REGISTER_ATTEMPT") + break; + + case State::CHANGEPASSWORD: + BLOCK_START("Client::gameExec State::CHANGEPASSWORD") + logger->log1("State: CHANGE PASSWORD"); + CREATEWIDGETV(mCurrentDialog, ChangePasswordDialog, + loginData); + mCurrentDialog->setVisible(Visible_true); + BLOCK_END("Client::gameExec State::CHANGEPASSWORD") + break; + + case State::CHANGEPASSWORD_ATTEMPT: + BLOCK_START("Client::gameExec " + "State::CHANGEPASSWORD_ATTEMPT") + logger->log1("State: CHANGE PASSWORD ATTEMPT"); + if (loginHandler != nullptr) + { + loginHandler->changePassword(loginData.password, + loginData.newPassword); + } + BLOCK_END("Client::gameExec State::CHANGEPASSWORD_ATTEMPT") + break; + + case State::CHANGEPASSWORD_SUCCESS: + BLOCK_START("Client::gameExec " + "State::CHANGEPASSWORD_SUCCESS") + logger->log1("State: CHANGE PASSWORD SUCCESS"); + CREATEWIDGETV(mCurrentDialog, OkDialog, + // TRANSLATORS: password change message header + _("Password Change"), + // TRANSLATORS: password change message text + _("Password changed successfully!"), + // TRANSLATORS: ok dialog button + _("OK"), + DialogType::ERROR, + Modal_true, + ShowCenter_true, + nullptr, + 260); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = nullptr; // OkDialog deletes itself + loginData.password = loginData.newPassword; + loginData.newPassword.clear(); + BLOCK_END("Client::gameExec State::CHANGEPASSWORD_SUCCESS") + break; + + case State::CHANGEEMAIL: + logger->log1("State: CHANGE EMAIL"); + CREATEWIDGETV(mCurrentDialog, + ChangeEmailDialog, + loginData); + mCurrentDialog->setVisible(Visible_true); + break; + + case State::CHANGEEMAIL_ATTEMPT: + logger->log1("State: CHANGE EMAIL ATTEMPT"); + if (loginHandler != nullptr) + loginHandler->changeEmail(loginData.email); + break; + + case State::CHANGEEMAIL_SUCCESS: + logger->log1("State: CHANGE EMAIL SUCCESS"); + CREATEWIDGETV(mCurrentDialog, OkDialog, + // TRANSLATORS: email change message header + _("Email Change"), + // TRANSLATORS: email change message text + _("Email changed successfully!"), + // TRANSLATORS: ok dialog button + _("OK"), + DialogType::ERROR, + Modal_true, + ShowCenter_true, + nullptr, + 260); + mCurrentDialog->addActionListener(&accountListener); + mCurrentDialog = nullptr; // OkDialog deletes itself + break; + + case State::SWITCH_SERVER: + BLOCK_START("Client::gameExec State::SWITCH_SERVER") + logger->log1("State: SWITCH SERVER"); + + if (loginHandler != nullptr) + loginHandler->disconnect(); + if (gameHandler != nullptr) + { + gameHandler->disconnect(); + gameHandler->clear(); + } + settings.serverName.clear(); + settings.login.clear(); + WindowManager::updateTitle(); + serverConfig.write(); + serverConfig.unload(); + if (setupWindow != nullptr) + setupWindow->externalUnload(); + + mState = State::CHOOSE_SERVER; + BLOCK_END("Client::gameExec State::SWITCH_SERVER") + break; + + case State::SWITCH_LOGIN: + BLOCK_START("Client::gameExec State::SWITCH_LOGIN") + logger->log1("State: SWITCH LOGIN"); + + if (loginHandler != nullptr) + { + loginHandler->logout(); + loginHandler->disconnect(); + } + if (gameHandler != nullptr) + gameHandler->disconnect(); + if (loginHandler != nullptr) + loginHandler->connect(); + + settings.login.clear(); + WindowManager::updateTitle(); + mState = State::LOGIN; + BLOCK_END("Client::gameExec State::SWITCH_LOGIN") + break; + + case State::SWITCH_CHARACTER: + BLOCK_START("Client::gameExec State::SWITCH_CHARACTER") + logger->log1("State: SWITCH CHARACTER"); + + // Done with game + if (gameHandler != nullptr) + gameHandler->disconnect(); + + settings.login.clear(); + WindowManager::updateTitle(); + mState = State::GET_CHARACTERS; + BLOCK_END("Client::gameExec State::SWITCH_CHARACTER") + break; + + case State::LOGOUT_ATTEMPT: + logger->log1("State: LOGOUT ATTEMPT"); + break; + + case State::WAIT: + logger->log1("State: WAIT"); + break; + + case State::EXIT: + BLOCK_START("Client::gameExec State::EXIT") + logger->log1("State: EXIT"); + Net::unload(); + BLOCK_END("Client::gameExec State::EXIT") + break; + + case State::FORCE_QUIT: + BLOCK_START("Client::gameExec State::FORCE_QUIT") + logger->log1("State: FORCE QUIT"); + if (generalHandler != nullptr) + generalHandler->unload(); + mState = State::EXIT; + BLOCK_END("Client::gameExec State::FORCE_QUIT") + break; + + case State::ERROR: + BLOCK_START("Client::gameExec State::ERROR") + config.write(); + if (mOldState == State::GAME) + serverConfig.write(); + logger->log1("State: ERROR"); + logger->log("Error: %s\n", errorMessage.c_str()); + mCurrentDialog = DialogsManager::openErrorDialog( + // TRANSLATORS: error message header + _("Error"), + errorMessage, + Modal_true); + mCurrentDialog->addActionListener(&errorListener); + mCurrentDialog = nullptr; // OkDialog deletes itself + gameHandler->disconnect(); + BLOCK_END("Client::gameExec State::ERROR") + break; + + case State::AUTORECONNECT_SERVER: + // ++++++ + break; + + case State::START: + default: + mState = State::FORCE_QUIT; + break; + } + BLOCK_END("Client::gameExec 8") + } + PROFILER_END(); + } + + return 0; +} + +void Client::optionChanged(const std::string &name) +{ + if (name == "fpslimit") + { + const int fpsLimit = config.getIntValue("fpslimit"); + settings.limitFps = fpsLimit > 0; + WindowManager::setFramerate(fpsLimit); + } + else if (name == "guialpha" || + name == "enableGuiOpacity") + { + const float alpha = config.getFloatValue("guialpha"); + settings.guiAlpha = alpha; + ImageHelper::setEnableAlpha(alpha != 1.0F && + config.getBoolValue("enableGuiOpacity")); + } + else if (name == "gamma" || + name == "enableGamma") + { + WindowManager::applyGamma(); + } + else if (name == "particleEmitterSkip") + { + ParticleEngine::emitterSkip = + config.getIntValue("particleEmitterSkip") + 1; + } + else if (name == "vsync") + { + WindowManager::applyVSync(); + } + else if (name == "repeateInterval" || + name == "repeateDelay") + { + WindowManager::applyKeyRepeat(); + } +} + +void Client::action(const ActionEvent &event) +{ + std::string tab; + const std::string &eventId = event.getId(); + + if (eventId == "close") + { + setState(State::FORCE_QUIT); + return; + } + if (eventId == "Setup") + { + tab.clear(); + } + else if (eventId == "help") + { + inputManager.executeAction(InputAction::WINDOW_HELP); + return; + } + else if (eventId == "about") + { + inputManager.executeAction(InputAction::WINDOW_ABOUT); + return; + } + else if (eventId == "Video") + { + tab = "Video"; + } + else if (eventId == "Themes") + { + tab = "Theme"; + } + else if (eventId == "Perfomance") + { + tab = "Perfomance"; + } + else + { + return; + } + + if (setupWindow != nullptr) + { + setupWindow->setVisible(fromBool( + !setupWindow->isWindowVisible(), Visible)); + if (setupWindow->isWindowVisible()) + { + if (!tab.empty()) + setupWindow->activateTab(tab); + setupWindow->requestMoveToTop(); + } + } +} + +void Client::initFeatures() +{ + features.init(paths.getStringValue("featuresFile"), + UseVirtFs_true, + SkipError_true); + setFeaturesDefaults(features); + settings.fixDeadAnimation = features.getBoolValue("fixDeadAnimation"); +} + +void Client::initPaths() +{ + settings.gmCommandSymbol = paths.getStringValue("gmCommandSymbol"); + settings.gmCharCommandSymbol = paths.getStringValue("gmCharCommandSymbol"); + settings.linkCommandSymbol = paths.getStringValue("linkCommandSymbol"); + if (settings.linkCommandSymbol.empty()) + settings.linkCommandSymbol = "="; + settings.overweightPercent = paths.getIntValue("overweightPercent"); + settings.playerNameOffset = paths.getIntValue( + "playerNameOffset"); + settings.playerBadgeAtRightOffset = paths.getIntValue( + "playerBadgeAtRightOffset"); + settings.unknownSkillsAutoTab = paths.getBoolValue("unknownSkillsAutoTab"); + settings.enableNewMailSystem = paths.getBoolValue("enableNewMailSystem"); +} + +void Client::initTradeFilter() +{ + const std::string tradeListName = + settings.serverConfigDir + "/tradefilter.txt"; + + std::ofstream tradeFile; + struct stat statbuf; + + if ((stat(tradeListName.c_str(), &statbuf) != 0) || + !S_ISREG(statbuf.st_mode)) + { + tradeFile.open(tradeListName.c_str(), + std::ios::out); + if (tradeFile.is_open()) + { + tradeFile << ": sell" << std::endl; + tradeFile << ": buy" << std::endl; + tradeFile << ": trade" << std::endl; + tradeFile << "i sell" << std::endl; + tradeFile << "i buy" << std::endl; + tradeFile << "i trade" << std::endl; + tradeFile << "i trading" << std::endl; + tradeFile << "i am buy" << std::endl; + tradeFile << "i am sell" << std::endl; + tradeFile << "i am trade" << std::endl; + tradeFile << "i am trading" << std::endl; + tradeFile << "i'm buy" << std::endl; + tradeFile << "i'm sell" << std::endl; + tradeFile << "i'm trade" << std::endl; + tradeFile << "i'm trading" << std::endl; + } + else + { + reportAlways("Error opening file for writing: %s", + tradeListName.c_str()); + } + tradeFile.close(); + } +} + +bool Client::isTmw() +{ + const std::string &name = settings.serverName; + if (name == "server.themanaworld.org" || + name == "themanaworld.org" || + name == "167.114.129.72") + { + return true; + } + return false; +} + +void Client::moveButtons(const int width) +{ + if (mSetupButton != nullptr) + { + int x = width - mSetupButton->getWidth() - mButtonPadding; + mSetupButton->setPosition(x, mButtonPadding); +#ifndef WIN32 + x -= mPerfomanceButton->getWidth() + mButtonSpacing; + mPerfomanceButton->setPosition(x, mButtonPadding); + + x -= mVideoButton->getWidth() + mButtonSpacing; + mVideoButton->setPosition(x, mButtonPadding); + + x -= mThemesButton->getWidth() + mButtonSpacing; + mThemesButton->setPosition(x, mButtonPadding); + + x -= mAboutButton->getWidth() + mButtonSpacing; + mAboutButton->setPosition(x, mButtonPadding); + + x -= mHelpButton->getWidth() + mButtonSpacing; + mHelpButton->setPosition(x, mButtonPadding); +#ifdef ANDROID + x -= mCloseButton->getWidth() + mButtonSpacing; + mCloseButton->setPosition(x, mButtonPadding); +#endif // ANDROID +#endif // WIN32 + } +} + +void Client::windowRemoved(const Window *const window) +{ + if (mCurrentDialog == window) + mCurrentDialog = nullptr; +} + +void Client::logVars() +{ +#ifdef ANDROID + logger->log("APPDIR: %s", getenv("APPDIR")); + logger->log("DATADIR2: %s", getSdStoragePath().c_str()); +#endif // ANDROID +} + +void Client::slowLogic() +{ + if ((gameHandler == nullptr) || + !gameHandler->mustPing()) + { + return; + } + + if (get_elapsed_time1(mPing) > 1500) + { + mPing = tick_time; + if (mState == State::UPDATE || + mState == State::LOGIN || + mState == State::LOGIN_ATTEMPT || + mState == State::REGISTER || + mState == State::REGISTER_ATTEMPT) + { + if (loginHandler != nullptr) + loginHandler->ping(); + if (generalHandler != nullptr) + generalHandler->flushSend(); + } + else if (mState == State::CHAR_SELECT) + { + if (charServerHandler != nullptr) + charServerHandler->ping(); + if (generalHandler != nullptr) + generalHandler->flushSend(); + } + } +} + +void Client::loadData() +{ + // If another data path has been set, + // we don't load any other files... + if (settings.options.dataPath.empty()) + { + // Add customdata directory + VirtFs::searchAndAddArchives( + "customdata/", + "zip", + Append_false); + } + + if (!settings.options.skipUpdate) + { + VirtFs::searchAndAddArchives( + settings.updatesDir + "/local/", + "zip", + Append_false); + + VirtFs::mountDir(pathJoin( + settings.localDataDir, + settings.updatesDir, + "local/"), + Append_false); + } + + logger->log("Init paths"); + paths.init("paths.xml", UseVirtFs_true); + setPathsDefaults(paths); + initPaths(); + if (SpriteReference::Empty == nullptr) + { + SpriteReference::Empty = new SpriteReference( + paths.getStringValue("spriteErrorFile"), + 0); + } + + if (BeingInfo::unknown == nullptr) + BeingInfo::unknown = new BeingInfo; + + initFeatures(); + TranslationManager::loadCurrentLang(); + TranslationManager::loadDictionaryLang(); + PlayerInfo::stateChange(mState); + + delete spellManager; + spellManager = new SpellManager; + delete spellShortcut; + spellShortcut = new SpellShortcut; + + AttributesEnum::init(); + DbManager::loadDb(); + EquipmentWindow::prepareSlotNames(); + + ActorSprite::load(); + + if (desktop != nullptr) + desktop->reloadWallpaper(); +} + +void Client::unloadData() +{ + DbManager::unloadDb(); + mCurrentServer.supportUrl.clear(); + settings.supportUrl.clear(); + if (settings.options.dataPath.empty()) + { + // Add customdata directory + VirtFs::searchAndRemoveArchives( + "customdata/", + "zip"); + } + + if (!settings.oldUpdates.empty()) + { + UpdaterWindow::unloadUpdates(settings.oldUpdates); + settings.oldUpdates.clear(); + } + + if (!settings.options.skipUpdate) + { + VirtFs::searchAndRemoveArchives( + pathJoin(settings.updatesDir, "local/"), + "zip"); + + VirtFs::unmountDirSilent(pathJoin( + settings.localDataDir, + settings.updatesDir, + "local/")); + } + + ResourceManager::clearCache(); + + loginData.clearUpdateHost(); + serverVersion = 0; + packetVersion = 0; + tmwServerVersion = 0; + evolPacketOffset = 0; +} + +void Client::runValidate() +{ + loadData(); + WindowManager::createValidateWindows(); + + WindowManager::deleteValidateWindows(); + unloadData(); + delete2(client); + VirtFs::deinit(); + exit(0); +} diff --git a/src/progs/manaplus/client.h b/src/progs/manaplus/client.h new file mode 100644 index 000000000..af537bb68 --- /dev/null +++ b/src/progs/manaplus/client.h @@ -0,0 +1,167 @@ +/* + * 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_CLIENT_H +#define PROGS_MANAPLUS_CLIENT_H + +#include "enums/state.h" + +#include "listeners/actionlistener.h" +#include "listeners/configlistener.h" + +#include "net/serverinfo.h" + +#include "localconsts.h" + +class Button; +class Game; +class LoginData; +class Skin; +class Window; +class QuitDialog; + +extern bool isSafeMode; +extern int serverVersion; +extern unsigned int tmwServerVersion; +extern time_t start_time; +extern int textures_count; + +extern std::string errorMessage; +extern LoginData loginData; + +/** + * The core part of the client. This class initializes all subsystems, runs + * the event loop, and shuts everything down again. + */ +class Client final : public ConfigListener, + public ActionListener +{ + public: + Client(); + + A_DELETE_COPY(Client) + + ~Client(); + + void gameInit(); + + void testsInit(); + + int gameExec(); + + static int testsExec(); + + void setState(const StateT state) + { mState = state; } + + StateT getState() const noexcept2 A_WARN_UNUSED + { return mState; } + + static bool isTmw() A_WARN_UNUSED; + + void optionChanged(const std::string &name) override final; + + void action(const ActionEvent &event) override final; + + static void initTradeFilter(); + + void moveButtons(const int width); + + void windowRemoved(const Window *const window); + + void slowLogic(); + + ServerInfo &getCurrentServer() + { return mCurrentServer; } + + private: + void initSoundManager(); + + void initConfigListeners(); + + static void initGraphics(); + + static void initFeatures(); + + static void initPaths(); + + void gameClear(); + + void testsClear(); + +#ifdef ANDROID + static void logVars(); +#else // ANDROID + + static void logVars() A_CONST; +#endif // ANDROID + + void stateConnectGame1(); + + void stateConnectServer1(); + + void stateWorldSelect1(); + + void stateGame1(); + + void stateSwitchLogin1(); + + void loadData(); + + void unloadData(); + + void runValidate() +#ifndef BAD_CILKPLUS + __attribute__ ((noreturn)) +#endif // BAD_CILKPLUS +; + + ServerInfo mCurrentServer; + + Game *mGame; + Window *mCurrentDialog; + QuitDialog *mQuitDialog; + Button *mSetupButton; + Button *mVideoButton; + Button *mHelpButton; + Button *mAboutButton; + Button *mThemesButton; + Button *mPerfomanceButton; +#ifdef ANDROID + Button *mCloseButton; +#endif // ANDROID + + StateT mState; + StateT mOldState; + + Skin *mSkin; + int mButtonPadding; + int mButtonSpacing; + int mPing; + bool mConfigAutoSaved; +}; + +extern Client *client; +extern unsigned int mLastHost; +extern unsigned long mSearchHash; + +#endif // PROGS_MANAPLUS_CLIENT_H 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 |