summaryrefslogtreecommitdiff
path: root/src/progs/manaverse
diff options
context:
space:
mode:
Diffstat (limited to 'src/progs/manaverse')
-rw-r--r--src/progs/manaverse/actions/actions.cpp2048
-rw-r--r--src/progs/manaverse/actions/chat.cpp840
-rw-r--r--src/progs/manaverse/actions/commands.cpp2225
-rw-r--r--src/progs/manaverse/actions/move.cpp281
-rw-r--r--src/progs/manaverse/actions/pets.cpp250
-rw-r--r--src/progs/manaverse/actions/statusbar.cpp205
-rw-r--r--src/progs/manaverse/actions/tabs.cpp107
-rw-r--r--src/progs/manaverse/actions/target.cpp91
-rw-r--r--src/progs/manaverse/actions/windows.cpp393
-rw-r--r--src/progs/manaverse/client.cpp2090
-rw-r--r--src/progs/manaverse/client.h172
-rw-r--r--src/progs/manaverse/gui/viewport.cpp1153
-rw-r--r--src/progs/manaverse/gui/viewport.h250
13 files changed, 10105 insertions, 0 deletions
diff --git a/src/progs/manaverse/actions/actions.cpp b/src/progs/manaverse/actions/actions.cpp
new file mode 100644
index 000000000..8e997cb2a
--- /dev/null
+++ b/src/progs/manaverse/actions/actions.cpp
@@ -0,0 +1,2048 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/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/foreach.h"
+#include "utils/gettext.h"
+#include "utils/parameters.h"
+#include "utils/timer.h"
+
+#ifdef TMWA_SUPPORT
+#include "net/playerhandler.h"
+
+#include "utils/mathutils.h"
+#endif // TMWA_SUPPORT
+
+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)
+ info->upload = nullptr;
+ 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://ix.io",
+ &uploadUpdate,
+ false, true, false);
+ info->upload = upload;
+ info->text = str;
+ info->addStr = addStr;
+ info->tab = tab;
+ upload->setFile(fileName, -1);
+ 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,
+ true,
+ false);
+ 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;
+ int 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(1);
+ 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,
+ 0,
+ 0);
+ }
+ 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,
+ 0,
+ 0);
+ }
+ 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(0);
+ }
+ 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)
+{
+ const std::string args = event.args;
+ Being *being = nullptr;
+
+ if (!args.empty() && args[0] == ':')
+ {
+ being = actorManager->findBeing(fromInt(atoi(
+ args.substr(1).c_str()), BeingId));
+ }
+ else
+ {
+ being = findBeing(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(false);
+ // 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,
+ ActorType::Unknown);
+ }
+ 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, false);
+ 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,
+ ActorType::Unknown);
+ }
+ 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, false);
+ 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, false);
+ }
+ return true;
+}
+
+impHandler0(safeVideoMode)
+{
+ WindowManager::setFullScreen(false);
+
+ return true;
+}
+
+impHandler0(stopSit)
+{
+ if (localPlayer != nullptr)
+ {
+ localPlayer->stopAttack(false);
+ // 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)
+{
+#if defined(ANDROID) || defined(__SWITCH__)
+#ifdef USE_SDL2
+ if (SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE)
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else
+ {
+ debugChatTab->chatLog("Unable to stop IPC service.",
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ else
+ {
+ IPC::start();
+ if (ipc != nullptr)
+ {
+ debugChatTab->chatLog(
+ strprintf("IPC service available on port %d", ipc->getPort()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else
+ {
+ debugChatTab->chatLog("Unable to start IPC service",
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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 cleared"),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ return true;
+}
+
+impHandler0(cleanFonts)
+{
+ if (gui != nullptr)
+ gui->clearFonts();
+ if (debugChatTab != nullptr)
+ {
+ // TRANSLATORS: clear fonts cache message
+ debugChatTab->chatLog(_("Cache cleared"),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ // TRANSLATORS: chat fonts message
+ debugChatTab->chatLog(strprintf("%s %d", _("Cache size:"), all),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+#ifdef DEBUG_FONT_COUNTERS
+ debugChatTab->chatLog("",
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ debugChatTab->chatLog(strprintf("%s %d",
+ // TRANSLATORS: chat fonts message
+ _("Created:"), font->getCreateCounter()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ debugChatTab->chatLog(strprintf("%s %d",
+ // TRANSLATORS: chat fonts message
+ _("Deleted:"), font->getDeleteCounter()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+#endif
+*/
+ return true;
+}
+
+impHandler0(disconnect)
+{
+ if (!settings.options.uniqueSession)
+ {
+ if (gameHandler != nullptr)
+ gameHandler->disconnect2();
+ return true;
+ }
+ else
+ return false;
+}
+
+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,
+ ActorType::Unknown);
+ }
+ }
+
+ if (sz == 2)
+ {
+ if (target != nullptr)
+ {
+ const int itemId = atoi(pars[1].c_str());
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ debugChatTab->chatLog("logs directory: "
+ + settings.localDataDir,
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ debugChatTab->chatLog("screenshots directory: "
+ + settings.screenshotDir,
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ debugChatTab->chatLog("temp directory: "
+ + settings.tempDir,
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else
+ {
+ // TRANSLATORS: uptime command
+ debugChatTab->chatLog(strprintf(_("Client uptime: %s"),
+ timeDiffToString(CAST_S32(cur_time - start_time)).c_str()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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(_("Orphaned resource images:"), res);
+ }
+ else
+ {
+ ResourceManager::Resources *res = ResourceManager::getResources();
+ // TRANSLATORS: dump command
+ debugChatTab->chatLog(_("Resource images:") + toString(res->size()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ res = ResourceManager::getOrphanedResources();
+ // TRANSLATORS: dump command
+ debugChatTab->chatLog(_("Orphaned resource images:")
+ + toString(res->size()),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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")
+PRAGMA11(GCC diagnostic push)
+PRAGMA11(GCC diagnostic ignored "-Warray-bounds")
+impHandler0(error)
+{
+ int *const ptr = nullptr;
+ *(ptr + 1) = 20;
+// logger->log("test %d", *ptr);
+ exit(1);
+}
+PRAGMA11(GCC diagnostic pop)
+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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ return true;
+}
+
+impHandler(dumpTests)
+{
+ const std::string str = config.getStringValue("testInfo");
+ outStringNormal(event.tab, str, str);
+ return true;
+}
+
+impHandler0(dumpOGL)
+{
+#ifdef USE_OPENGL
+#if !defined(ANDROID) && !defined(__native_client__) && !defined(__SWITCH__)
+ NormalOpenGLGraphics::dumpSettings();
+#endif // !defined(ANDROID) && !defined(__native_client__) &&
+ // !defined(__SWITCH__)
+#endif // USE_OPENGL
+
+ 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, TTF_STYLE_NORMAL);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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(_("Config uploaded to:"),
+ config.getFileName(),
+ "?xml",
+ event.tab);
+ return true;
+}
+
+impHandler(uploadServerConfig)
+{
+ // TRANSLATORS: upload config chat message
+ uploadFile(_("Server config Uploaded to:"),
+ serverConfig.getFileName(),
+ "?xml",
+ event.tab);
+ return true;
+}
+
+impHandler(uploadLog)
+{
+ // TRANSLATORS: upload log chat message
+ uploadFile(_("Log uploaded to:"),
+ settings.logFileName,
+ "",
+ 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)
+{
+ StringVect pars;
+ if (!splitParameters(pars, event.args, " ,", '\"'))
+ return false;
+ const int sz = CAST_S32(pars.size());
+ if (sz < 1)
+ return false;
+
+ const int itemId = atoi(pars[0].c_str());
+
+ if (itemId < SPELL_MIN_ID)
+ {
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (inv != nullptr)
+ {
+ ItemColor color = ItemColor_one;
+ int16_t useType = 0;
+ StringVect pars2;
+ if (!splitParameters(pars2, pars[0], " ,", '\"'))
+ return false;
+ const int sz2 = CAST_S32(pars2.size());
+ if (sz2 < 1)
+ return false;
+ if (sz2 >= 2)
+ color = fromInt(atoi(pars2[1].c_str()), ItemColor);
+ if (sz >= 2)
+ useType = CAST_S16(atoi(pars[1].c_str()));
+ const Item *const item = inv->findItem(itemId,
+ color);
+ PlayerInfo::useEquipItem(item, useType, 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,
+ 0,
+ 0);
+ }
+ 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,
+ 0,
+ 0);
+ }
+ 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,
+ 0,
+ 0);
+ }
+ 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,
+ ActorType::Unknown);
+ }
+ 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(true);
+ 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,
+ true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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 have never seen this nick."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else
+ {
+ // TRANSLATORS: last seen error
+ tab->chatLog(_("You have not seen this nick before."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+
+ 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/manaverse/actions/chat.cpp b/src/progs/manaverse/actions/chat.cpp
new file mode 100644
index 000000000..27963a75f
--- /dev/null
+++ b/src/progs/manaverse/actions/chat.cpp
@@ -0,0 +1,840 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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 "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/clanhandler.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);
+ return;
+ }
+
+ switch (tab->getType())
+ {
+ case ChatTabType::CLAN:
+ {
+ if (clanHandler != nullptr)
+ clanHandler->chat(str);
+ break;
+ }
+ 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);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ 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("\" "),
+ true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else if (localChatTab != nullptr)
+ {
+ // TRANSLATORS: guild invite message
+ localChatTab->chatLog(_("Please specify a name."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ if (chatWindow != nullptr)
+ chatWindow->setReturnTogglesChat(false);
+ return true;
+ case -1:
+ if (event.tab != nullptr)
+ {
+ event.tab->chatLog(strprintf(BOOLEAN_OPTIONS, "toggle"),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+ return true;
+}
+
+impHandler(addText)
+{
+ if (chatWindow != nullptr)
+ chatWindow->addInputText(event.args, true);
+ 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 = _("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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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/manaverse/actions/commands.cpp b/src/progs/manaverse/actions/commands.cpp
new file mode 100644
index 000000000..3f23fe5e3
--- /dev/null
+++ b/src/progs/manaverse/actions/commands.cpp
@@ -0,0 +1,2225 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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"
+
+#ifdef HAVE_MALLOC_TRIM
+#include <malloc.h>
+#endif
+
+#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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ else
+ {
+ // TRANSLATORS: unignore command
+ event.tab->chatLog(str2,
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ }
+}
+
+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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ 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,
+ ActorType::Unknown);
+ 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,
+ ActorType::Unknown);
+ 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, "ftp") &&
+ !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") && !strStartWith(url, "ftp"))
+ 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, true);
+ 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)
+ {
+ if (emoteShortcut != nullptr)
+ {
+ const int emotion = event.action - InputAction::HOMUN_EMOTE_1;
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NO:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Item sharing disabled."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NOT_POSSIBLE:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Item sharing not possible."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::UNKNOWN:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Item sharing unknown."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NO:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Experience sharing disabled."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NOT_POSSIBLE:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Experience sharing not possible."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::UNKNOWN:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Experience sharing unknown."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NO:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Auto item sharing disabled."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::NOT_POSSIBLE:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Auto item sharing not possible."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ return true;
+ case PartyShare::UNKNOWN:
+ // TRANSLATORS: chat message
+ tab->chatLog(_("Auto item sharing unknown."),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ 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, true);
+ 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;
+}
+
+impHandler0(trimMemory)
+{
+#ifdef HAVE_MALLOC_TRIM
+ malloc_trim(0);
+#else
+ // TRANSLATORS: chat error about trim command
+ localChatTab->chatLog(_("Trim memory not supported"),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+#endif
+ return true;
+}
+
+} // namespace Actions
diff --git a/src/progs/manaverse/actions/move.cpp b/src/progs/manaverse/actions/move.cpp
new file mode 100644
index 000000000..38eff9403
--- /dev/null
+++ b/src/progs/manaverse/actions/move.cpp
@@ -0,0 +1,281 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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(false);
+ 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(false);
+ 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(-1);
+ 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/manaverse/actions/pets.cpp b/src/progs/manaverse/actions/pets.cpp
new file mode 100644
index 000000000..f4c3823a3
--- /dev/null
+++ b/src/progs/manaverse/actions/pets.cpp
@@ -0,0 +1,250 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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 "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);
+ 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)
+ {
+ if (emoteShortcut != nullptr)
+ {
+ const int emotion = event.action - InputAction::PET_EMOTE_1;
+ 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,
+ ActorType::Unknown);
+ }
+ }
+
+ 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/manaverse/actions/statusbar.cpp b/src/progs/manaverse/actions/statusbar.cpp
new file mode 100644
index 000000000..062d42a53
--- /dev/null
+++ b/src/progs/manaverse/actions/statusbar.cpp
@@ -0,0 +1,205 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ deflt &= ~PlayerRelation::TRADE;
+ }
+ else
+ {
+ if (localChatTab != nullptr)
+ {
+ // TRANSLATORS: enable trades message
+ localChatTab->chatLog(_("Accepting incoming trade requests"),
+ ChatMsgType::BY_SERVER,
+ IgnoreRecord_false,
+ TryRemoveColors_true);
+ }
+ deflt |= PlayerRelation::TRADE;
+ }
+
+ playerRelations.setDefault(deflt);
+ return true;
+}
+
+} // namespace Actions
diff --git a/src/progs/manaverse/actions/tabs.cpp b/src/progs/manaverse/actions/tabs.cpp
new file mode 100644
index 000000000..b5140a2e9
--- /dev/null
+++ b/src/progs/manaverse/actions/tabs.cpp
@@ -0,0 +1,107 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/manaverse/actions/target.cpp b/src/progs/manaverse/actions/target.cpp
new file mode 100644
index 000000000..9b97cf576
--- /dev/null
+++ b/src/progs/manaverse/actions/target.cpp
@@ -0,0 +1,91 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/manaverse/actions/windows.cpp b/src/progs/manaverse/actions/windows.cpp
new file mode 100644
index 000000000..9f8a68faa
--- /dev/null
+++ b/src/progs/manaverse/actions/windows.cpp
@@ -0,0 +1,393 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2012-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/clanwindow.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);
+ case ChatTabType::CLAN:
+ return showHelpPage("chatclan", 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;
+}
+
+impHandler0(clanWindowShow)
+{
+ showHideWindow(clanWindow);
+ 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/manaverse/client.cpp b/src/progs/manaverse/client.cpp
new file mode 100644
index 000000000..b13fa9f20
--- /dev/null
+++ b/src/progs/manaverse/client.cpp
@@ -0,0 +1,2090 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/manaverse/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 "pincodemanager.h"
+#include "settings.h"
+#include "soundmanager.h"
+#include "spellmanager.h"
+
+#include "being/localclan.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/useragent.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/perfstat.h"
+#include "utils/sdlcheckutils.h"
+#include "utils/sdlhelper.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;
+int packetVersionMain = 0;
+int packetVersionRe = 0;
+int packetVersionZero = 0;
+int packetsType = 0;
+int itemIdLen = 2;
+bool packets_main = true;
+bool packets_re = false;
+bool packets_zero = false;
+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;
+ SDL::initLogger();
+ Dirs::initLocalDataDir();
+ Dirs::initTempDir();
+ Dirs::initConfigDir();
+ GettextHelper::initLang();
+ }
+}
+
+void Client::gameInit()
+{
+ logger = new Logger;
+ SDL::initLogger();
+
+ initRand();
+
+ assertListener = new AssertListener;
+ // Load branding information
+ if (!settings.options.brandingPath.empty())
+ {
+ branding.init(settings.options.brandingPath,
+ UseVirtFs_false,
+ SkipError_false);
+ }
+ setBrandingDefaults(branding);
+
+ Dirs::initRootDir();
+ Dirs::initHomeDir();
+
+ // Configure logger
+ if (!settings.options.logFileName.empty())
+ {
+ settings.logFileName = settings.options.logFileName;
+ }
+ else
+ {
+ settings.logFileName = pathJoin(settings.localDataDir,
+ "manaverse.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();
+ SDL::setLogLevel(config.getIntValue("sdlLogLevel"));
+ 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);
+ }
+
+ // Log the client version
+ logger->log1(FULL_VERSION);
+ logger->log("Start configPath: " + config.getConfigPath());
+
+ Dirs::initScreenshotDir();
+
+ updateEnv();
+ SDL::allowScreenSaver(config.getBoolValue("allowscreensaver"));
+ 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()));
+ }
+#ifndef __SWITCH__
+ atexit(SDL_Quit);
+#endif
+ PacketLimiter::initPacketLimiter();
+#ifndef USE_SDL2
+ SDL_EnableUNICODE(1);
+#endif // USE_SDL2
+
+ WindowManager::applyKeyRepeat();
+ eventsManager.init();
+ eventsManager.enableEvents();
+
+#ifdef WIN32
+ Dirs::mountDataDir();
+#endif // WIN32
+#ifndef USE_SDL2
+ WindowManager::setIcon();
+#endif // USE_SDL2
+
+ ConfigManager::checkConfigVersion();
+ logVars();
+ Cpu::detect();
+ DyePalette::initFunctions();
+#if defined(USE_OPENGL)
+#if !defined(ANDROID) && !defined(__APPLE__)
+#if !defined(__native_client__) && !defined(__SWITCH__) && !defined(UNITTESTS)
+ if (!settings.options.safeMode &&
+ settings.options.renderer < 0 &&
+ settings.options.test.empty() &&
+ !settings.options.validate &&
+ !config.getBoolValue("videodetected"))
+ {
+ graphicsManager.detectVideoSettings();
+ }
+#endif // !defined(__native_client__) && !defined(__SWITCH__) &&
+ // !defined(UNITTESTS)
+#endif // !defined(ANDROID) && !defined(__APPLE__) &&
+#endif // defined(USE_OPENGL)
+
+ initGraphics();
+ UserAgent::update();
+
+ touchManager.init();
+
+#ifndef WIN32
+ Dirs::extractDataDir();
+ Dirs::mountDataDir();
+#endif // WIN32
+
+ Dirs::updateDataPath();
+
+ // Add the main data directories to our VirtFs search path
+ if (!settings.options.dataPath.empty())
+ {
+ VirtFs::mountDir(settings.options.dataPath,
+ Append_false);
+ }
+
+ // Add the local data directory to VirtFs search path
+ VirtFs::mountDir(settings.localDataDir,
+ Append_false);
+ TranslationManager::loadCurrentLang();
+ TranslationManager::loadDictionaryLang();
+#ifdef ENABLE_CUSTOMNLS
+ TranslationManager::loadGettextLang();
+#endif // ENABLE_CUSTOMNLS
+
+#ifdef USE_SDL2
+ WindowManager::setIcon();
+#endif // USE_SDL2
+ 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();
+
+ // 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",
+ "",
+ true,
+ Theme::getThemePath());
+ 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()
+{
+#ifndef USE_SDL2
+ WindowManager::applyVSync();
+#endif // USE_SDL2
+
+ runCounters = config.getBoolValue("packetcounters");
+
+ graphicsManager.initGraphics();
+#ifdef USE_SDL2
+ WindowManager::applyVSync();
+#endif // USE_SDL2
+
+ 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();
+
+ soundManager.shutdown();
+
+ if (logger != nullptr)
+ logger->log1("Quitting8");
+
+ WindowManager::deleteIcon();
+
+ if (logger != nullptr)
+ logger->log1("Quitting9");
+
+ delete2(joystick)
+
+ keyboard.deinit();
+
+ if (logger != nullptr)
+ logger->log1("Quitting10");
+
+ 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;
+ }
+#ifdef SAVE_PASSWORD
+ LoginDialog::savedPassword = loginData.remember ?
+ serverConfig.getValue("password", "") : "";
+ loginData.password = LoginDialog::savedPassword;
+ settings.options.password = LoginDialog::savedPassword;
+#endif
+ 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 (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", BUTTON_SKIN, this))
+ ADDBUTTON(mPerfomanceButton, new Button(desktop,
+ // TRANSLATORS: perfoamance tab quick button
+ _("Performance"), "Perfomance", BUTTON_SKIN, this))
+ ADDBUTTON(mVideoButton, new Button(desktop,
+ // TRANSLATORS: video tab quick button
+ _("Video"), "Video", BUTTON_SKIN, this))
+ ADDBUTTON(mThemesButton, new Button(desktop,
+ // TRANSLATORS: theme tab quick button
+ _("Theme"), "Themes", BUTTON_SKIN, this))
+ ADDBUTTON(mAboutButton, new Button(desktop,
+ // TRANSLATORS: theme tab quick button
+ _("About"), "about", BUTTON_SKIN, this))
+ ADDBUTTON(mHelpButton, new Button(desktop,
+ // TRANSLATORS: theme tab quick button
+ _("Help"), "help", BUTTON_SKIN, this))
+#ifdef ANDROID
+ ADDBUTTON(mCloseButton, new Button(desktop,
+ // TRANSLATORS: close quick button
+ _("Close"), "close", BUTTON_SKIN, 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;
+
+ Perf::init();
+
+ while (mState != State::EXIT)
+ {
+ PROFILER_START();
+ PERF_STAT(0);
+ if (eventsManager.handleEvents())
+ continue;
+
+ PERF_STAT(1);
+
+ BLOCK_START("Client::gameExec 3")
+ if (generalHandler != nullptr)
+ generalHandler->flushNetwork();
+ BLOCK_END("Client::gameExec 3")
+
+ PERF_STAT(2);
+
+ BLOCK_START("Client::gameExec 4")
+ if (gui != nullptr)
+ gui->logic();
+
+ PERF_STAT(3);
+
+ 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 ++;
+ }
+
+ PERF_STAT(4);
+
+ soundManager.logic();
+
+ PERF_STAT(5);
+
+ logic_count = logic_count + k;
+ if (gui != nullptr)
+ gui->slowLogic();
+
+ PERF_STAT(6);
+
+ if (mGame != nullptr)
+ mGame->slowLogic();
+
+ PERF_STAT(7);
+
+ slowLogic();
+
+ PERF_STAT(8);
+
+ BLOCK_END("Client::gameExec 4")
+
+ // This is done because at some point tick_time will wrap.
+ lastTickTime = tick_time;
+
+ 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")
+
+ PERF_STAT(9);
+
+ if (mState != mOldState)
+ {
+ BLOCK_START("Client::gameExec 7")
+ PlayerInfo::stateChange(mState);
+
+ if (mOldState == State::GAME)
+ {
+ delete2(mGame)
+ assertListener = new AssertListener;
+ Game::clearInstance();
+ ResourceManager::cleanOrphans(false);
+ 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();
+ pincodeManager.closeDialogs();
+
+ // 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();
+ pincodeManager.init();
+ 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);
+ pincodeManager.updateState();
+
+ 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
+ Perf::init();
+ }
+
+ // 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();
+#ifdef SAVE_PASSWORD
+ if (errorMessage == "Wrong password.")
+ {
+ serverConfig.setValue("password", "");
+ serverConfig.write();
+ }
+#endif
+ if (mOldState == State::GAME)
+ serverConfig.write();
+ logger->log1("State: ERROR");
+ logger->log("Error: %s\n", errorMessage.c_str());
+ pincodeManager.closeDialogs();
+ 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")
+ }
+
+ PERF_STAT(10);
+
+ // Update the screen when application is visible, delay otherwise.
+ if (!WindowManager::getIsMinimized())
+ {
+ frame_count = frame_count + 1;
+ if (gui != nullptr)
+ gui->draw();
+ mainGraphics->updateScreen();
+ }
+ else
+ {
+ SDL_Delay(100);
+ }
+
+ PERF_STAT(11);
+
+ BLOCK_START("~Client::SDL_framerateDelay")
+ if (settings.limitFps)
+ SDL_framerateDelay(&fpsManager);
+ BLOCK_END("~Client::SDL_framerateDelay")
+
+ PERF_STAT(12);
+ PERF_NEXTFRAME();
+ PROFILER_END();
+ }
+
+ return settings.exitcode;
+}
+
+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.fixedInventorySize = paths.getIntValue("fixedInventorySize");
+ 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::focusWindow()
+{
+ if (mCurrentDialog != nullptr)
+ {
+ mCurrentDialog->requestFocus();
+ }
+}
+
+void Client::updatePinState()
+{
+ if (mCurrentDialog == nullptr ||
+ mState != State::CHAR_SELECT)
+ {
+ return;
+ }
+ CharSelectDialog *const dialog =
+ dynamic_cast<CharSelectDialog*>(mCurrentDialog);
+ if (dialog != nullptr)
+ pincodeManager.updateState();
+}
+
+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,
+ SkipError_false);
+ 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);
+
+ AttributesEnum::init();
+ DbManager::loadDb();
+
+ delete spellManager;
+ spellManager = new SpellManager;
+ delete spellShortcut;
+ spellShortcut = new SpellShortcut;
+
+ 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();
+ localClan.clear();
+ serverVersion = 0;
+ packetVersion = 0;
+ packetVersionMain = 0;
+ packetVersionRe = 0;
+ packetVersionZero = 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/manaverse/client.h b/src/progs/manaverse/client.h
new file mode 100644
index 000000000..e74b179d2
--- /dev/null
+++ b/src/progs/manaverse/client.h
@@ -0,0 +1,172 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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_MANAVERSE_CLIENT_H
+#define PROGS_MANAVERSE_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() override final;
+
+ 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 focusWindow();
+
+ void updatePinState();
+
+ 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();
+#endif // ANDROID
+
+ static 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_MANAVERSE_CLIENT_H
diff --git a/src/progs/manaverse/gui/viewport.cpp b/src/progs/manaverse/gui/viewport.cpp
new file mode 100644
index 000000000..0d0a1abc4
--- /dev/null
+++ b/src/progs/manaverse/gui/viewport.cpp
@@ -0,0 +1,1153 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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/manaverse/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, 255));
+ 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, 0.0F);
+ const int mousePosX = mMouseX + mPixelViewX;
+ const int mousePosY = mMouseY + mPixelViewY;
+ Vector mouseDestination(mousePosX, mousePosY, 0.0F);
+
+ 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,
+ false, 0) ||
+ inputManager.isActionActive(InputAction::ATTACK))
+ {
+ validateSpeed();
+ if (!mStatsReUpdated && localPlayer != mHoverBeing)
+ {
+ localPlayer->attack(mHoverBeing,
+ !inputManager.isActionActive(
+ InputAction::STOP_ATTACK),
+ false);
+ 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(false);
+ 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();
+
+ // 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)
+ {
+ const int pixelX = mMousePressX + mPixelViewX;
+ const int pixelY = mMousePressY + mPixelViewY;
+ 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();
+ if (mMap != nullptr)
+ mMap->screenResized();
+}
diff --git a/src/progs/manaverse/gui/viewport.h b/src/progs/manaverse/gui/viewport.h
new file mode 100644
index 000000000..781f4255f
--- /dev/null
+++ b/src/progs/manaverse/gui/viewport.h
@@ -0,0 +1,250 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2020 The ManaPlus Developers
+ * Copyright (C) 2020-2023 The ManaVerse 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_MANAVERSE_GUI_VIEWPORT_H
+#define PROGS_MANAVERSE_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() override final;
+
+ /**
+ * 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,
+ const int y);
+
+ 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)
+ 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_MANAVERSE_GUI_VIEWPORT_H