| @@ -0,0 +1,40 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SCRIPTS_H | |
| 21 | + | #define FS_SCRIPTS_H | |
| 22 | + | ||
| 23 | + | #include "luascript.h" | |
| 24 | + | #include "enums.h" | |
| 25 | + | ||
| 26 | + | class Scripts | |
| 27 | + | { | |
| 28 | + | public: | |
| 29 | + | Scripts(); | |
| 30 | + | ~Scripts(); | |
| 31 | + | ||
| 32 | + | bool loadScripts(std::string folderName, bool isLib, bool reload); | |
| 33 | + | LuaScriptInterface& getScriptInterface() { | |
| 34 | + | return scriptInterface; | |
| 35 | + | } | |
| 36 | + | private: | |
| 37 | + | LuaScriptInterface scriptInterface; | |
| 38 | + | }; | |
| 39 | + | ||
| 40 | + | #endif |
| @@ -0,0 +1,127 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #include "otpch.h" | |
| 21 | + | ||
| 22 | + | #include "scriptmanager.h" | |
| 23 | + | ||
| 24 | + | #include "actions.h" | |
| 25 | + | #include "chat.h" | |
| 26 | + | #include "talkaction.h" | |
| 27 | + | #include "spells.h" | |
| 28 | + | #include "movement.h" | |
| 29 | + | #include "weapons.h" | |
| 30 | + | #include "globalevent.h" | |
| 31 | + | #include "events.h" | |
| 32 | + | #include "script.h" | |
| 33 | + | ||
| 34 | + | Actions* g_actions = nullptr; | |
| 35 | + | CreatureEvents* g_creatureEvents = nullptr; | |
| 36 | + | Chat* g_chat = nullptr; | |
| 37 | + | Events* g_events = nullptr; | |
| 38 | + | GlobalEvents* g_globalEvents = nullptr; | |
| 39 | + | Spells* g_spells = nullptr; | |
| 40 | + | TalkActions* g_talkActions = nullptr; | |
| 41 | + | MoveEvents* g_moveEvents = nullptr; | |
| 42 | + | Weapons* g_weapons = nullptr; | |
| 43 | + | Scripts* g_scripts = nullptr; | |
| 44 | + | ||
| 45 | + | extern LuaEnvironment g_luaEnvironment; | |
| 46 | + | ||
| 47 | + | ScriptingManager::~ScriptingManager() | |
| 48 | + | { | |
| 49 | + | delete g_events; | |
| 50 | + | delete g_weapons; | |
| 51 | + | delete g_spells; | |
| 52 | + | delete g_actions; | |
| 53 | + | delete g_talkActions; | |
| 54 | + | delete g_moveEvents; | |
| 55 | + | delete g_chat; | |
| 56 | + | delete g_creatureEvents; | |
| 57 | + | delete g_globalEvents; | |
| 58 | + | delete g_scripts; | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | bool ScriptingManager::loadScriptSystems() | |
| 62 | + | { | |
| 63 | + | if (g_luaEnvironment.loadFile("data/global.lua") == -1) { | |
| 64 | + | std::cout << "[Warning - ScriptingManager::loadScriptSystems] Can not load data/global.lua" << std::endl; | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | g_scripts = new Scripts(); | |
| 68 | + | std::cout << ">> Loading lua libs" << std::endl; | |
| 69 | + | if (!g_scripts->loadScripts("scripts/lib", true, false)) { | |
| 70 | + | std::cout << "> ERROR: Unable to load lua libs!" << std::endl; | |
| 71 | + | return false; | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | g_chat = new Chat(); | |
| 75 | + | ||
| 76 | + | g_weapons = new Weapons(); | |
| 77 | + | if (!g_weapons->loadFromXml()) { | |
| 78 | + | std::cout << "> ERROR: Unable to load weapons!" << std::endl; | |
| 79 | + | return false; | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | g_weapons->loadDefaults(); | |
| 83 | + | ||
| 84 | + | g_spells = new Spells(); | |
| 85 | + | if (!g_spells->loadFromXml()) { | |
| 86 | + | std::cout << "> ERROR: Unable to load spells!" << std::endl; | |
| 87 | + | return false; | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | g_actions = new Actions(); | |
| 91 | + | if (!g_actions->loadFromXml()) { | |
| 92 | + | std::cout << "> ERROR: Unable to load actions!" << std::endl; | |
| 93 | + | return false; | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | g_talkActions = new TalkActions(); | |
| 97 | + | if (!g_talkActions->loadFromXml()) { | |
| 98 | + | std::cout << "> ERROR: Unable to load talk actions!" << std::endl; | |
| 99 | + | return false; | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | g_moveEvents = new MoveEvents(); | |
| 103 | + | if (!g_moveEvents->loadFromXml()) { | |
| 104 | + | std::cout << "> ERROR: Unable to load move events!" << std::endl; | |
| 105 | + | return false; | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | g_creatureEvents = new CreatureEvents(); | |
| 109 | + | if (!g_creatureEvents->loadFromXml()) { | |
| 110 | + | std::cout << "> ERROR: Unable to load creature events!" << std::endl; | |
| 111 | + | return false; | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | g_globalEvents = new GlobalEvents(); | |
| 115 | + | if (!g_globalEvents->loadFromXml()) { | |
| 116 | + | std::cout << "> ERROR: Unable to load global events!" << std::endl; | |
| 117 | + | return false; | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | g_events = new Events(); | |
| 121 | + | if (!g_events->load()) { | |
| 122 | + | std::cout << "> ERROR: Unable to load events!" << std::endl; | |
| 123 | + | return false; | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | return true; | |
| 127 | + | } |
| @@ -0,0 +1,41 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B | |
| 21 | + | #define FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B | |
| 22 | + | ||
| 23 | + | class ScriptingManager | |
| 24 | + | { | |
| 25 | + | public: | |
| 26 | + | ScriptingManager() = default; | |
| 27 | + | ~ScriptingManager(); | |
| 28 | + | ||
| 29 | + | // non-copyable | |
| 30 | + | ScriptingManager(const ScriptingManager&) = delete; | |
| 31 | + | ScriptingManager& operator=(const ScriptingManager&) = delete; | |
| 32 | + | ||
| 33 | + | static ScriptingManager& getInstance() { | |
| 34 | + | static ScriptingManager instance; | |
| 35 | + | return instance; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | bool loadScriptSystems(); | |
| 39 | + | }; | |
| 40 | + | ||
| 41 | + | #endif |
| @@ -0,0 +1,206 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #include "otpch.h" | |
| 21 | + | ||
| 22 | + | #include "outputmessage.h" | |
| 23 | + | #include "server.h" | |
| 24 | + | #include "scheduler.h" | |
| 25 | + | #include "configmanager.h" | |
| 26 | + | #include "ban.h" | |
| 27 | + | ||
| 28 | + | extern ConfigManager g_config; | |
| 29 | + | Ban g_bans; | |
| 30 | + | ||
| 31 | + | ServiceManager::~ServiceManager() | |
| 32 | + | { | |
| 33 | + | stop(); | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | void ServiceManager::die() | |
| 37 | + | { | |
| 38 | + | io_service.stop(); | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | void ServiceManager::run() | |
| 42 | + | { | |
| 43 | + | assert(!running); | |
| 44 | + | running = true; | |
| 45 | + | io_service.run(); | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | void ServiceManager::stop() | |
| 49 | + | { | |
| 50 | + | if (!running) { | |
| 51 | + | return; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | running = false; | |
| 55 | + | ||
| 56 | + | for (auto& servicePortIt : acceptors) { | |
| 57 | + | try { | |
| 58 | + | io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); | |
| 59 | + | } catch (boost::system::system_error& e) { | |
| 60 | + | std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; | |
| 61 | + | } | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | acceptors.clear(); | |
| 65 | + | ||
| 66 | + | death_timer.expires_from_now(boost::posix_time::seconds(3)); | |
| 67 | + | death_timer.async_wait(std::bind(&ServiceManager::die, this)); | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | ServicePort::~ServicePort() | |
| 71 | + | { | |
| 72 | + | close(); | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | bool ServicePort::is_single_socket() const | |
| 76 | + | { | |
| 77 | + | return !services.empty() && services.front()->is_single_socket(); | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | std::string ServicePort::get_protocol_names() const | |
| 81 | + | { | |
| 82 | + | if (services.empty()) { | |
| 83 | + | return std::string(); | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | std::string str = services.front()->get_protocol_name(); | |
| 87 | + | for (size_t i = 1; i < services.size(); ++i) { | |
| 88 | + | str.push_back(','); | |
| 89 | + | str.push_back(' '); | |
| 90 | + | str.append(services[i]->get_protocol_name()); | |
| 91 | + | } | |
| 92 | + | return str; | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | void ServicePort::accept() | |
| 96 | + | { | |
| 97 | + | if (!acceptor) { | |
| 98 | + | return; | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | auto connection = ConnectionManager::getInstance().createConnection(io_service, shared_from_this()); | |
| 102 | + | acceptor->async_accept(connection->getSocket(), std::bind(&ServicePort::onAccept, shared_from_this(), connection, std::placeholders::_1)); | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | void ServicePort::onAccept(Connection_ptr connection, const boost::system::error_code& error) | |
| 106 | + | { | |
| 107 | + | if (!error) { | |
| 108 | + | if (services.empty()) { | |
| 109 | + | return; | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | auto remote_ip = connection->getIP(); | |
| 113 | + | if (remote_ip != 0 && g_bans.acceptConnection(remote_ip)) { | |
| 114 | + | Service_ptr service = services.front(); | |
| 115 | + | if (service->is_single_socket()) { | |
| 116 | + | connection->accept(service->make_protocol(connection)); | |
| 117 | + | } else { | |
| 118 | + | connection->accept(); | |
| 119 | + | } | |
| 120 | + | } else { | |
| 121 | + | connection->close(Connection::FORCE_CLOSE); | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | accept(); | |
| 125 | + | } else if (error != boost::asio::error::operation_aborted) { | |
| 126 | + | if (!pendingStart) { | |
| 127 | + | close(); | |
| 128 | + | pendingStart = true; | |
| 129 | + | g_scheduler.addEvent(createSchedulerTask(15000, | |
| 130 | + | std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), serverPort))); | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const | |
| 136 | + | { | |
| 137 | + | uint8_t protocolID = msg.getByte(); | |
| 138 | + | for (auto& service : services) { | |
| 139 | + | if (protocolID != service->get_protocol_identifier()) { | |
| 140 | + | continue; | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { | |
| 144 | + | return service->make_protocol(connection); | |
| 145 | + | } | |
| 146 | + | } | |
| 147 | + | return nullptr; | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | void ServicePort::onStopServer() | |
| 151 | + | { | |
| 152 | + | close(); | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | void ServicePort::openAcceptor(std::weak_ptr<ServicePort> weak_service, uint16_t port) | |
| 156 | + | { | |
| 157 | + | if (auto service = weak_service.lock()) { | |
| 158 | + | service->open(port); | |
| 159 | + | } | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | void ServicePort::open(uint16_t port) | |
| 163 | + | { | |
| 164 | + | close(); | |
| 165 | + | ||
| 166 | + | serverPort = port; | |
| 167 | + | pendingStart = false; | |
| 168 | + | ||
| 169 | + | try { | |
| 170 | + | if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { | |
| 171 | + | acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( | |
| 172 | + | boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); | |
| 173 | + | } else { | |
| 174 | + | acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( | |
| 175 | + | boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); | |
| 179 | + | ||
| 180 | + | accept(); | |
| 181 | + | } catch (boost::system::system_error& e) { | |
| 182 | + | std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; | |
| 183 | + | ||
| 184 | + | pendingStart = true; | |
| 185 | + | g_scheduler.addEvent(createSchedulerTask(15000, | |
| 186 | + | std::bind(&ServicePort::openAcceptor, std::weak_ptr<ServicePort>(shared_from_this()), port))); | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | void ServicePort::close() | |
| 191 | + | { | |
| 192 | + | if (acceptor && acceptor->is_open()) { | |
| 193 | + | boost::system::error_code error; | |
| 194 | + | acceptor->close(error); | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | bool ServicePort::add_service(const Service_ptr& new_svc) | |
| 199 | + | { | |
| 200 | + | if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) { | |
| 201 | + | return false; | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | services.push_back(new_svc); | |
| 205 | + | return true; | |
| 206 | + | } |
| @@ -0,0 +1,156 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SERVER_H_984DA68ABF744127850F90CC710F281B | |
| 21 | + | #define FS_SERVER_H_984DA68ABF744127850F90CC710F281B | |
| 22 | + | ||
| 23 | + | #include "connection.h" | |
| 24 | + | #include "signals.h" | |
| 25 | + | #include <memory> | |
| 26 | + | ||
| 27 | + | class Protocol; | |
| 28 | + | ||
| 29 | + | class ServiceBase | |
| 30 | + | { | |
| 31 | + | public: | |
| 32 | + | virtual bool is_single_socket() const = 0; | |
| 33 | + | virtual bool is_checksummed() const = 0; | |
| 34 | + | virtual uint8_t get_protocol_identifier() const = 0; | |
| 35 | + | virtual const char* get_protocol_name() const = 0; | |
| 36 | + | ||
| 37 | + | virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; | |
| 38 | + | }; | |
| 39 | + | ||
| 40 | + | template <typename ProtocolType> | |
| 41 | + | class Service final : public ServiceBase | |
| 42 | + | { | |
| 43 | + | public: | |
| 44 | + | bool is_single_socket() const override { | |
| 45 | + | return ProtocolType::server_sends_first; | |
| 46 | + | } | |
| 47 | + | bool is_checksummed() const override { | |
| 48 | + | return ProtocolType::use_checksum; | |
| 49 | + | } | |
| 50 | + | uint8_t get_protocol_identifier() const override { | |
| 51 | + | return ProtocolType::protocol_identifier; | |
| 52 | + | } | |
| 53 | + | const char* get_protocol_name() const override { | |
| 54 | + | return ProtocolType::protocol_name(); | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | Protocol_ptr make_protocol(const Connection_ptr& c) const override { | |
| 58 | + | return std::make_shared<ProtocolType>(c); | |
| 59 | + | } | |
| 60 | + | }; | |
| 61 | + | ||
| 62 | + | class ServicePort : public std::enable_shared_from_this<ServicePort> | |
| 63 | + | { | |
| 64 | + | public: | |
| 65 | + | explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} | |
| 66 | + | ~ServicePort(); | |
| 67 | + | ||
| 68 | + | // non-copyable | |
| 69 | + | ServicePort(const ServicePort&) = delete; | |
| 70 | + | ServicePort& operator=(const ServicePort&) = delete; | |
| 71 | + | ||
| 72 | + | static void openAcceptor(std::weak_ptr<ServicePort> weak_service, uint16_t port); | |
| 73 | + | void open(uint16_t port); | |
| 74 | + | void close(); | |
| 75 | + | bool is_single_socket() const; | |
| 76 | + | std::string get_protocol_names() const; | |
| 77 | + | ||
| 78 | + | bool add_service(const Service_ptr& new_svc); | |
| 79 | + | Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const; | |
| 80 | + | ||
| 81 | + | void onStopServer(); | |
| 82 | + | void onAccept(Connection_ptr connection, const boost::system::error_code& error); | |
| 83 | + | ||
| 84 | + | private: | |
| 85 | + | void accept(); | |
| 86 | + | ||
| 87 | + | boost::asio::io_service& io_service; | |
| 88 | + | std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor; | |
| 89 | + | std::vector<Service_ptr> services; | |
| 90 | + | ||
| 91 | + | uint16_t serverPort = 0; | |
| 92 | + | bool pendingStart = false; | |
| 93 | + | }; | |
| 94 | + | ||
| 95 | + | class ServiceManager | |
| 96 | + | { | |
| 97 | + | public: | |
| 98 | + | ServiceManager() = default; | |
| 99 | + | ~ServiceManager(); | |
| 100 | + | ||
| 101 | + | // non-copyable | |
| 102 | + | ServiceManager(const ServiceManager&) = delete; | |
| 103 | + | ServiceManager& operator=(const ServiceManager&) = delete; | |
| 104 | + | ||
| 105 | + | void run(); | |
| 106 | + | void stop(); | |
| 107 | + | ||
| 108 | + | template <typename ProtocolType> | |
| 109 | + | bool add(uint16_t port); | |
| 110 | + | ||
| 111 | + | bool is_running() const { | |
| 112 | + | return acceptors.empty() == false; | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | private: | |
| 116 | + | void die(); | |
| 117 | + | ||
| 118 | + | std::unordered_map<uint16_t, ServicePort_ptr> acceptors; | |
| 119 | + | ||
| 120 | + | boost::asio::io_service io_service; | |
| 121 | + | Signals signals{io_service}; | |
| 122 | + | boost::asio::deadline_timer death_timer { io_service }; | |
| 123 | + | bool running = false; | |
| 124 | + | }; | |
| 125 | + | ||
| 126 | + | template <typename ProtocolType> | |
| 127 | + | bool ServiceManager::add(uint16_t port) | |
| 128 | + | { | |
| 129 | + | if (port == 0) { | |
| 130 | + | std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." << std::endl; | |
| 131 | + | return false; | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | ServicePort_ptr service_port; | |
| 135 | + | ||
| 136 | + | auto foundServicePort = acceptors.find(port); | |
| 137 | + | ||
| 138 | + | if (foundServicePort == acceptors.end()) { | |
| 139 | + | service_port = std::make_shared<ServicePort>(io_service); | |
| 140 | + | service_port->open(port); | |
| 141 | + | acceptors[port] = service_port; | |
| 142 | + | } else { | |
| 143 | + | service_port = foundServicePort->second; | |
| 144 | + | ||
| 145 | + | if (service_port->is_single_socket() || ProtocolType::server_sends_first) { | |
| 146 | + | std::cout << "ERROR: " << ProtocolType::protocol_name() << | |
| 147 | + | " and " << service_port->get_protocol_names() << | |
| 148 | + | " cannot use the same port " << port << '.' << std::endl; | |
| 149 | + | return false; | |
| 150 | + | } | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | return service_port->add_service(std::make_shared<Service<ProtocolType>>()); | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | #endif |
| @@ -0,0 +1,57 @@ | |||
| 1 | + | #include "otpch.h" | |
| 2 | + | ||
| 3 | + | #include "shaders.h" | |
| 4 | + | ||
| 5 | + | #include "pugicast.h" | |
| 6 | + | #include "tools.h" | |
| 7 | + | ||
| 8 | + | bool Shaders::reload() | |
| 9 | + | { | |
| 10 | + | shaders.clear(); | |
| 11 | + | return loadFromXml(); | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | bool Shaders::loadFromXml() | |
| 15 | + | { | |
| 16 | + | pugi::xml_document doc; | |
| 17 | + | pugi::xml_parse_result result = doc.load_file("data/XML/shaders.xml"); | |
| 18 | + | if (!result) { | |
| 19 | + | printXMLError("Error - Shaders::loadFromXml", "data/XML/shaders.xml", result); | |
| 20 | + | return false; | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | for (auto shaderNode : doc.child("shaders").children()) { | |
| 24 | + | shaders.emplace_back( | |
| 25 | + | static_cast<uint8_t>(pugi::cast<uint16_t>(shaderNode.attribute("id").value())), | |
| 26 | + | shaderNode.attribute("name").as_string(), | |
| 27 | + | shaderNode.attribute("premium").as_bool() | |
| 28 | + | ); | |
| 29 | + | } | |
| 30 | + | shaders.shrink_to_fit(); | |
| 31 | + | return true; | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | Shader* Shaders::getShaderByID(uint8_t id) | |
| 35 | + | { | |
| 36 | + | auto it = std::find_if(shaders.begin(), shaders.end(), [id](const Shader& shader) { | |
| 37 | + | return shader.id == id; | |
| 38 | + | }); | |
| 39 | + | ||
| 40 | + | return it != shaders.end() ? &*it : nullptr; | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | Shader* Shaders::getShaderByName(const std::string& name) { | |
| 44 | + | auto shaderName = name.c_str(); | |
| 45 | + | for (auto& it : shaders) { | |
| 46 | + | if (strcasecmp(shaderName, it.name.c_str()) == 0) { | |
| 47 | + | return ⁢ | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | return nullptr; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | std::string Shaders::getShaderNameByID(uint8_t id) { | |
| 55 | + | auto shader = getShaderByID(id); | |
| 56 | + | return shader ? shader->name : ""; | |
| 57 | + | } |
| @@ -0,0 +1,31 @@ | |||
| 1 | + | #ifndef FS_SHADERS_H | |
| 2 | + | #define FS_SHADERS_H | |
| 3 | + | ||
| 4 | + | struct Shader | |
| 5 | + | { | |
| 6 | + | Shader(uint8_t id, std::string name, bool premium) : | |
| 7 | + | name(std::move(name)), id(id), premium(premium) {} | |
| 8 | + | ||
| 9 | + | uint8_t id; | |
| 10 | + | std::string name; | |
| 11 | + | bool premium; | |
| 12 | + | }; | |
| 13 | + | ||
| 14 | + | class Shaders | |
| 15 | + | { | |
| 16 | + | public: | |
| 17 | + | bool reload(); | |
| 18 | + | bool loadFromXml(); | |
| 19 | + | Shader* getShaderByID(uint8_t id); | |
| 20 | + | Shader* getShaderByName(const std::string& name); | |
| 21 | + | std::string getShaderNameByID(uint8_t id); | |
| 22 | + | ||
| 23 | + | const std::vector<Shader>& getShaders() const { | |
| 24 | + | return shaders; | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | private: | |
| 28 | + | std::vector<Shader> shaders; | |
| 29 | + | }; | |
| 30 | + | ||
| 31 | + | #endif |
| @@ -0,0 +1,220 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #include "otpch.h" | |
| 21 | + | #include <csignal> | |
| 22 | + | ||
| 23 | + | #include "signals.h" | |
| 24 | + | #include "tasks.h" | |
| 25 | + | #include "game.h" | |
| 26 | + | #include "actions.h" | |
| 27 | + | #include "configmanager.h" | |
| 28 | + | #include "spells.h" | |
| 29 | + | #include "talkaction.h" | |
| 30 | + | #include "movement.h" | |
| 31 | + | #include "weapons.h" | |
| 32 | + | #include "raids.h" | |
| 33 | + | #include "quests.h" | |
| 34 | + | #include "mounts.h" | |
| 35 | + | #include "auras.h" | |
| 36 | + | #include "wings.h" | |
| 37 | + | #include "globalevent.h" | |
| 38 | + | #include "monster.h" | |
| 39 | + | #include "events.h" | |
| 40 | + | #include "scheduler.h" | |
| 41 | + | #include "databasetasks.h" | |
| 42 | + | ||
| 43 | + | ||
| 44 | + | extern Scheduler g_scheduler; | |
| 45 | + | extern DatabaseTasks g_databaseTasks; | |
| 46 | + | extern Dispatcher g_dispatcher; | |
| 47 | + | ||
| 48 | + | extern ConfigManager g_config; | |
| 49 | + | extern Actions* g_actions; | |
| 50 | + | extern Monsters g_monsters; | |
| 51 | + | extern TalkActions* g_talkActions; | |
| 52 | + | extern MoveEvents* g_moveEvents; | |
| 53 | + | extern Spells* g_spells; | |
| 54 | + | extern Weapons* g_weapons; | |
| 55 | + | extern Game g_game; | |
| 56 | + | extern CreatureEvents* g_creatureEvents; | |
| 57 | + | extern GlobalEvents* g_globalEvents; | |
| 58 | + | extern Events* g_events; | |
| 59 | + | extern Chat* g_chat; | |
| 60 | + | extern LuaEnvironment g_luaEnvironment; | |
| 61 | + | ||
| 62 | + | using ErrorCode = boost::system::error_code; | |
| 63 | + | ||
| 64 | + | Signals::Signals(boost::asio::io_service& service) : | |
| 65 | + | set(service) | |
| 66 | + | { | |
| 67 | + | set.add(SIGINT); | |
| 68 | + | set.add(SIGTERM); | |
| 69 | + | #ifndef _WIN32 | |
| 70 | + | set.add(SIGUSR1); | |
| 71 | + | set.add(SIGHUP); | |
| 72 | + | #else | |
| 73 | + | // This must be a blocking call as Windows calls it in a new thread and terminates | |
| 74 | + | // the process when the handler returns (or after 5 seconds, whichever is earlier). | |
| 75 | + | // On Windows it is called in a new thread. | |
| 76 | + | signal(SIGBREAK, dispatchSignalHandler); | |
| 77 | + | #endif | |
| 78 | + | ||
| 79 | + | asyncWait(); | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | void Signals::asyncWait() | |
| 83 | + | { | |
| 84 | + | set.async_wait([this] (ErrorCode err, int signal) { | |
| 85 | + | if (err) { | |
| 86 | + | std::cerr << "Signal handling error: " << err.message() << std::endl; | |
| 87 | + | return; | |
| 88 | + | } | |
| 89 | + | dispatchSignalHandler(signal); | |
| 90 | + | asyncWait(); | |
| 91 | + | }); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | // On Windows this function does not need to be signal-safe, | |
| 95 | + | // as it is called in a new thread. | |
| 96 | + | // https://github.com/otland/forgottenserver/pull/2473 | |
| 97 | + | void Signals::dispatchSignalHandler(int signal) | |
| 98 | + | { | |
| 99 | + | switch(signal) { | |
| 100 | + | case SIGINT: //Shuts the server down | |
| 101 | + | g_dispatcher.addTask(createTask(sigintHandler)); | |
| 102 | + | break; | |
| 103 | + | case SIGTERM: //Shuts the server down | |
| 104 | + | g_dispatcher.addTask(createTask(sigtermHandler)); | |
| 105 | + | break; | |
| 106 | + | #ifndef _WIN32 | |
| 107 | + | case SIGHUP: //Reload config/data | |
| 108 | + | g_dispatcher.addTask(createTask(sighupHandler)); | |
| 109 | + | break; | |
| 110 | + | case SIGUSR1: //Saves game state | |
| 111 | + | g_dispatcher.addTask(createTask(sigusr1Handler)); | |
| 112 | + | break; | |
| 113 | + | #else | |
| 114 | + | case SIGBREAK: //Shuts the server down | |
| 115 | + | g_dispatcher.addTask(createTask(sigbreakHandler)); | |
| 116 | + | // hold the thread until other threads end | |
| 117 | + | g_scheduler.join(); | |
| 118 | + | g_databaseTasks.join(); | |
| 119 | + | g_dispatcher.join(); | |
| 120 | + | break; | |
| 121 | + | #endif | |
| 122 | + | default: | |
| 123 | + | break; | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | void Signals::sigbreakHandler() | |
| 128 | + | { | |
| 129 | + | //Dispatcher thread | |
| 130 | + | std::cout << "SIGBREAK received, shutting game server down..." << std::endl; | |
| 131 | + | g_game.setGameState(GAME_STATE_SHUTDOWN); | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | void Signals::sigtermHandler() | |
| 135 | + | { | |
| 136 | + | //Dispatcher thread | |
| 137 | + | std::cout << "SIGTERM received, shutting game server down..." << std::endl; | |
| 138 | + | g_game.setGameState(GAME_STATE_SHUTDOWN); | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | void Signals::sigusr1Handler() | |
| 142 | + | { | |
| 143 | + | //Dispatcher thread | |
| 144 | + | std::cout << "SIGUSR1 received, saving the game state..." << std::endl; | |
| 145 | + | g_game.saveGameState(); | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | void Signals::sighupHandler() | |
| 149 | + | { | |
| 150 | + | //Dispatcher thread | |
| 151 | + | std::cout << "SIGHUP received, reloading config files..." << std::endl; | |
| 152 | + | ||
| 153 | + | g_actions->reload(); | |
| 154 | + | std::cout << "Reloaded actions." << std::endl; | |
| 155 | + | ||
| 156 | + | g_config.reload(); | |
| 157 | + | std::cout << "Reloaded config." << std::endl; | |
| 158 | + | ||
| 159 | + | g_creatureEvents->reload(); | |
| 160 | + | std::cout << "Reloaded creature scripts." << std::endl; | |
| 161 | + | ||
| 162 | + | g_moveEvents->reload(); | |
| 163 | + | std::cout << "Reloaded movements." << std::endl; | |
| 164 | + | ||
| 165 | + | Npcs::reload(); | |
| 166 | + | std::cout << "Reloaded npcs." << std::endl; | |
| 167 | + | ||
| 168 | + | g_game.raids.reload(); | |
| 169 | + | g_game.raids.startup(); | |
| 170 | + | std::cout << "Reloaded raids." << std::endl; | |
| 171 | + | ||
| 172 | + | g_spells->reload(); | |
| 173 | + | std::cout << "Reloaded monsters." << std::endl; | |
| 174 | + | ||
| 175 | + | g_monsters.reload(); | |
| 176 | + | std::cout << "Reloaded spells." << std::endl; | |
| 177 | + | ||
| 178 | + | g_talkActions->reload(); | |
| 179 | + | std::cout << "Reloaded talk actions." << std::endl; | |
| 180 | + | ||
| 181 | + | Item::items.reload(); | |
| 182 | + | std::cout << "Reloaded items." << std::endl; | |
| 183 | + | ||
| 184 | + | g_weapons->reload(); | |
| 185 | + | g_weapons->loadDefaults(); | |
| 186 | + | std::cout << "Reloaded weapons." << std::endl; | |
| 187 | + | ||
| 188 | + | g_game.quests.reload(); | |
| 189 | + | std::cout << "Reloaded quests." << std::endl; | |
| 190 | + | ||
| 191 | + | g_game.mounts.reload(); | |
| 192 | + | std::cout << "Reloaded mounts." << std::endl; | |
| 193 | + | ||
| 194 | + | g_game.auras.reload(); | |
| 195 | + | std::cout << "Reloaded auras." << std::endl; | |
| 196 | + | ||
| 197 | + | g_game.wings.reload(); | |
| 198 | + | std::cout << "Reloaded wings." << std::endl; | |
| 199 | + | ||
| 200 | + | g_globalEvents->reload(); | |
| 201 | + | std::cout << "Reloaded globalevents." << std::endl; | |
| 202 | + | ||
| 203 | + | g_events->load(); | |
| 204 | + | std::cout << "Reloaded events." << std::endl; | |
| 205 | + | ||
| 206 | + | g_chat->load(); | |
| 207 | + | std::cout << "Reloaded chatchannels." << std::endl; | |
| 208 | + | ||
| 209 | + | g_luaEnvironment.loadFile("data/global.lua"); | |
| 210 | + | std::cout << "Reloaded global.lua." << std::endl; | |
| 211 | + | ||
| 212 | + | lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | void Signals::sigintHandler() | |
| 216 | + | { | |
| 217 | + | //Dispatcher thread | |
| 218 | + | std::cout << "SIGINT received, shutting game server down..." << std::endl; | |
| 219 | + | g_game.setGameState(GAME_STATE_SHUTDOWN); | |
| 220 | + | } |
| @@ -0,0 +1,42 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F | |
| 21 | + | #define FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F | |
| 22 | + | ||
| 23 | + | #include <boost/asio.hpp> | |
| 24 | + | ||
| 25 | + | class Signals | |
| 26 | + | { | |
| 27 | + | boost::asio::signal_set set; | |
| 28 | + | public: | |
| 29 | + | explicit Signals(boost::asio::io_service& service); | |
| 30 | + | ||
| 31 | + | private: | |
| 32 | + | void asyncWait(); | |
| 33 | + | static void dispatchSignalHandler(int signal); | |
| 34 | + | ||
| 35 | + | static void sigbreakHandler(); | |
| 36 | + | static void sigintHandler(); | |
| 37 | + | static void sighupHandler(); | |
| 38 | + | static void sigtermHandler(); | |
| 39 | + | static void sigusr1Handler(); | |
| 40 | + | }; | |
| 41 | + | ||
| 42 | + | #endif |
| @@ -0,0 +1,332 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #include "otpch.h" | |
| 21 | + | ||
| 22 | + | #include "spawn.h" | |
| 23 | + | #include "game.h" | |
| 24 | + | #include "monster.h" | |
| 25 | + | #include "configmanager.h" | |
| 26 | + | #include "scheduler.h" | |
| 27 | + | ||
| 28 | + | #include "pugicast.h" | |
| 29 | + | #include "events.h" | |
| 30 | + | ||
| 31 | + | extern ConfigManager g_config; | |
| 32 | + | extern Monsters g_monsters; | |
| 33 | + | extern Game g_game; | |
| 34 | + | extern Events* g_events; | |
| 35 | + | ||
| 36 | + | static constexpr int32_t MINSPAWN_INTERVAL = 1000; | |
| 37 | + | ||
| 38 | + | bool Spawns::loadFromXml(const std::string& filename) | |
| 39 | + | { | |
| 40 | + | if (loaded) { | |
| 41 | + | return true; | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | pugi::xml_document doc; | |
| 45 | + | pugi::xml_parse_result result = doc.load_file(filename.c_str()); | |
| 46 | + | if (!result) { | |
| 47 | + | printXMLError("Error - Spawns::loadFromXml", filename, result); | |
| 48 | + | return false; | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | this->filename = filename; | |
| 52 | + | loaded = true; | |
| 53 | + | ||
| 54 | + | for (auto spawnNode : doc.child("spawns").children()) { | |
| 55 | + | Position centerPos( | |
| 56 | + | pugi::cast<uint16_t>(spawnNode.attribute("centerx").value()), | |
| 57 | + | pugi::cast<uint16_t>(spawnNode.attribute("centery").value()), | |
| 58 | + | pugi::cast<uint16_t>(spawnNode.attribute("centerz").value()) | |
| 59 | + | ); | |
| 60 | + | ||
| 61 | + | int32_t radius; | |
| 62 | + | pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius"); | |
| 63 | + | if (radiusAttribute) { | |
| 64 | + | radius = pugi::cast<int32_t>(radiusAttribute.value()); | |
| 65 | + | } else { | |
| 66 | + | radius = -1; | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | spawnList.emplace_front(centerPos, radius); | |
| 70 | + | Spawn& spawn = spawnList.front(); | |
| 71 | + | ||
| 72 | + | for (auto childNode : spawnNode.children()) { | |
| 73 | + | if (strcasecmp(childNode.name(), "monster") == 0) { | |
| 74 | + | pugi::xml_attribute nameAttribute = childNode.attribute("name"); | |
| 75 | + | if (!nameAttribute) { | |
| 76 | + | continue; | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | Direction dir; | |
| 80 | + | ||
| 81 | + | pugi::xml_attribute directionAttribute = childNode.attribute("direction"); | |
| 82 | + | if (directionAttribute) { | |
| 83 | + | dir = static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value())); | |
| 84 | + | } else { | |
| 85 | + | dir = DIRECTION_NORTH; | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | Position pos( | |
| 89 | + | centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()), | |
| 90 | + | centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()), | |
| 91 | + | centerPos.z | |
| 92 | + | ); | |
| 93 | + | uint32_t interval = pugi::cast<uint32_t>(childNode.attribute("spawntime").value()) * 1000; | |
| 94 | + | if (interval > MINSPAWN_INTERVAL) { | |
| 95 | + | spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); | |
| 96 | + | } else { | |
| 97 | + | std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; | |
| 98 | + | } | |
| 99 | + | } else if (strcasecmp(childNode.name(), "npc") == 0) { | |
| 100 | + | pugi::xml_attribute nameAttribute = childNode.attribute("name"); | |
| 101 | + | if (!nameAttribute) { | |
| 102 | + | continue; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | Npc* npc = Npc::createNpc(nameAttribute.as_string()); | |
| 106 | + | if (!npc) { | |
| 107 | + | continue; | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | pugi::xml_attribute directionAttribute = childNode.attribute("direction"); | |
| 111 | + | if (directionAttribute) { | |
| 112 | + | npc->setDirection(static_cast<Direction>(pugi::cast<uint16_t>(directionAttribute.value()))); | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | npc->setMasterPos(Position( | |
| 116 | + | centerPos.x + pugi::cast<uint16_t>(childNode.attribute("x").value()), | |
| 117 | + | centerPos.y + pugi::cast<uint16_t>(childNode.attribute("y").value()), | |
| 118 | + | centerPos.z | |
| 119 | + | ), radius); | |
| 120 | + | npcList.push_front(npc); | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | return true; | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | void Spawns::startup() | |
| 128 | + | { | |
| 129 | + | if (!loaded || isStarted()) { | |
| 130 | + | return; | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | for (Npc* npc : npcList) { | |
| 134 | + | g_game.placeCreature(npc, npc->getMasterPos(), false, true); | |
| 135 | + | } | |
| 136 | + | npcList.clear(); | |
| 137 | + | ||
| 138 | + | for (Spawn& spawn : spawnList) { | |
| 139 | + | spawn.startup(); | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | started = true; | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | void Spawns::clear() | |
| 146 | + | { | |
| 147 | + | for (Spawn& spawn : spawnList) { | |
| 148 | + | spawn.stopEvent(); | |
| 149 | + | } | |
| 150 | + | spawnList.clear(); | |
| 151 | + | ||
| 152 | + | loaded = false; | |
| 153 | + | started = false; | |
| 154 | + | filename.clear(); | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos) | |
| 158 | + | { | |
| 159 | + | if (radius == -1) { | |
| 160 | + | return true; | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) && | |
| 164 | + | (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius)); | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | void Spawn::startSpawnCheck() | |
| 168 | + | { | |
| 169 | + | if (checkSpawnEvent == 0) { | |
| 170 | + | checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | Spawn::~Spawn() | |
| 175 | + | { | |
| 176 | + | for (const auto& it : spawnedMap) { | |
| 177 | + | Monster* monster = it.second; | |
| 178 | + | monster->setSpawn(nullptr); | |
| 179 | + | monster->decrementReferenceCounter(); | |
| 180 | + | } | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | bool Spawn::findPlayer(const Position& pos) | |
| 184 | + | { | |
| 185 | + | SpectatorVec spectators; | |
| 186 | + | g_game.map.getSpectators(spectators, pos, false, true); | |
| 187 | + | for (Creature* spectator : spectators) { | |
| 188 | + | if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { | |
| 189 | + | return true; | |
| 190 | + | } | |
| 191 | + | } | |
| 192 | + | return false; | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | bool Spawn::isInSpawnZone(const Position& pos) | |
| 196 | + | { | |
| 197 | + | return Spawns::isInZone(centerPos, radius, pos); | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/) | |
| 201 | + | { | |
| 202 | + | std::unique_ptr<Monster> monster_ptr(new Monster(mType)); | |
| 203 | + | if (!g_events->eventMonsterOnSpawn(monster_ptr.get(), pos, startup, false)) { | |
| 204 | + | return false; | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | if (startup) { | |
| 208 | + | //No need to send out events to the surrounding since there is no one out there to listen! | |
| 209 | + | if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) { | |
| 210 | + | return false; | |
| 211 | + | } | |
| 212 | + | } else { | |
| 213 | + | if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) { | |
| 214 | + | return false; | |
| 215 | + | } | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | Monster* monster = monster_ptr.release(); | |
| 219 | + | monster->setDirection(dir); | |
| 220 | + | monster->setSpawn(this); | |
| 221 | + | monster->setMasterPos(pos); | |
| 222 | + | monster->incrementReferenceCounter(); | |
| 223 | + | ||
| 224 | + | spawnedMap.insert(spawned_pair(spawnId, monster)); | |
| 225 | + | spawnMap[spawnId].lastSpawn = OTSYS_TIME(); | |
| 226 | + | return true; | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | void Spawn::startup() | |
| 230 | + | { | |
| 231 | + | for (const auto& it : spawnMap) { | |
| 232 | + | uint32_t spawnId = it.first; | |
| 233 | + | const spawnBlock_t& sb = it.second; | |
| 234 | + | spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true); | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | void Spawn::checkSpawn() | |
| 239 | + | { | |
| 240 | + | checkSpawnEvent = 0; | |
| 241 | + | ||
| 242 | + | cleanup(); | |
| 243 | + | ||
| 244 | + | uint32_t spawnCount = 0; | |
| 245 | + | ||
| 246 | + | for (auto& it : spawnMap) { | |
| 247 | + | uint32_t spawnId = it.first; | |
| 248 | + | if (spawnedMap.find(spawnId) != spawnedMap.end()) { | |
| 249 | + | continue; | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | spawnBlock_t& sb = it.second; | |
| 253 | + | if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { | |
| 254 | + | if (findPlayer(sb.pos)) { | |
| 255 | + | sb.lastSpawn = OTSYS_TIME(); | |
| 256 | + | continue; | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); | |
| 260 | + | if (++spawnCount >= static_cast<uint32_t>(g_config.getNumber(ConfigManager::RATE_SPAWN))) { | |
| 261 | + | break; | |
| 262 | + | } | |
| 263 | + | } | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | if (spawnedMap.size() < spawnMap.size()) { | |
| 267 | + | checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); | |
| 268 | + | } | |
| 269 | + | } | |
| 270 | + | ||
| 271 | + | void Spawn::cleanup() | |
| 272 | + | { | |
| 273 | + | auto it = spawnedMap.begin(); | |
| 274 | + | while (it != spawnedMap.end()) { | |
| 275 | + | uint32_t spawnId = it->first; | |
| 276 | + | Monster* monster = it->second; | |
| 277 | + | if (monster->isRemoved()) { | |
| 278 | + | if (spawnId != 0) { | |
| 279 | + | spawnMap[spawnId].lastSpawn = OTSYS_TIME(); | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | monster->decrementReferenceCounter(); | |
| 283 | + | it = spawnedMap.erase(it); | |
| 284 | + | } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { | |
| 285 | + | spawnedMap.insert(spawned_pair(0, monster)); | |
| 286 | + | it = spawnedMap.erase(it); | |
| 287 | + | } else { | |
| 288 | + | ++it; | |
| 289 | + | } | |
| 290 | + | } | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval) | |
| 294 | + | { | |
| 295 | + | MonsterType* mType = g_monsters.getMonsterType(name); | |
| 296 | + | if (!mType) { | |
| 297 | + | std::cout << "[Spawn::addMonster] Can not find " << name << std::endl; | |
| 298 | + | return false; | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | this->interval = std::min(this->interval, interval); | |
| 302 | + | ||
| 303 | + | spawnBlock_t sb; | |
| 304 | + | sb.mType = mType; | |
| 305 | + | sb.pos = pos; | |
| 306 | + | sb.direction = dir; | |
| 307 | + | sb.interval = interval; | |
| 308 | + | sb.lastSpawn = 0; | |
| 309 | + | ||
| 310 | + | uint32_t spawnId = spawnMap.size() + 1; | |
| 311 | + | spawnMap[spawnId] = sb; | |
| 312 | + | return true; | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | void Spawn::removeMonster(Monster* monster) | |
| 316 | + | { | |
| 317 | + | for (auto it = spawnedMap.begin(), end = spawnedMap.end(); it != end; ++it) { | |
| 318 | + | if (it->second == monster) { | |
| 319 | + | monster->decrementReferenceCounter(); | |
| 320 | + | spawnedMap.erase(it); | |
| 321 | + | break; | |
| 322 | + | } | |
| 323 | + | } | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | void Spawn::stopEvent() | |
| 327 | + | { | |
| 328 | + | if (checkSpawnEvent != 0) { | |
| 329 | + | g_scheduler.stopEvent(checkSpawnEvent); | |
| 330 | + | checkSpawnEvent = 0; | |
| 331 | + | } | |
| 332 | + | } |
| @@ -0,0 +1,103 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B | |
| 21 | + | #define FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B | |
| 22 | + | ||
| 23 | + | #include "tile.h" | |
| 24 | + | #include "position.h" | |
| 25 | + | ||
| 26 | + | class Monster; | |
| 27 | + | class MonsterType; | |
| 28 | + | class Npc; | |
| 29 | + | ||
| 30 | + | struct spawnBlock_t { | |
| 31 | + | Position pos; | |
| 32 | + | MonsterType* mType; | |
| 33 | + | int64_t lastSpawn; | |
| 34 | + | uint32_t interval; | |
| 35 | + | Direction direction; | |
| 36 | + | }; | |
| 37 | + | ||
| 38 | + | class Spawn | |
| 39 | + | { | |
| 40 | + | public: | |
| 41 | + | Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {} | |
| 42 | + | ~Spawn(); | |
| 43 | + | ||
| 44 | + | // non-copyable | |
| 45 | + | Spawn(const Spawn&) = delete; | |
| 46 | + | Spawn& operator=(const Spawn&) = delete; | |
| 47 | + | ||
| 48 | + | bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); | |
| 49 | + | void removeMonster(Monster* monster); | |
| 50 | + | ||
| 51 | + | uint32_t getInterval() const { | |
| 52 | + | return interval; | |
| 53 | + | } | |
| 54 | + | void startup(); | |
| 55 | + | ||
| 56 | + | void startSpawnCheck(); | |
| 57 | + | void stopEvent(); | |
| 58 | + | ||
| 59 | + | bool isInSpawnZone(const Position& pos); | |
| 60 | + | void cleanup(); | |
| 61 | + | ||
| 62 | + | private: | |
| 63 | + | //map of the spawned creatures | |
| 64 | + | using SpawnedMap = std::multimap<uint32_t, Monster*>; | |
| 65 | + | using spawned_pair = SpawnedMap::value_type; | |
| 66 | + | SpawnedMap spawnedMap; | |
| 67 | + | ||
| 68 | + | //map of creatures in the spawn | |
| 69 | + | std::map<uint32_t, spawnBlock_t> spawnMap; | |
| 70 | + | ||
| 71 | + | Position centerPos; | |
| 72 | + | int32_t radius; | |
| 73 | + | ||
| 74 | + | uint32_t interval = 60000; | |
| 75 | + | uint32_t checkSpawnEvent = 0; | |
| 76 | + | ||
| 77 | + | static bool findPlayer(const Position& pos); | |
| 78 | + | bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); | |
| 79 | + | void checkSpawn(); | |
| 80 | + | }; | |
| 81 | + | ||
| 82 | + | class Spawns | |
| 83 | + | { | |
| 84 | + | public: | |
| 85 | + | static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); | |
| 86 | + | ||
| 87 | + | bool loadFromXml(const std::string& filename); | |
| 88 | + | void startup(); | |
| 89 | + | void clear(); | |
| 90 | + | ||
| 91 | + | bool isStarted() const { | |
| 92 | + | return started; | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | private: | |
| 96 | + | std::forward_list<Npc*> npcList; | |
| 97 | + | std::forward_list<Spawn> spawnList; | |
| 98 | + | std::string filename; | |
| 99 | + | bool loaded = false; | |
| 100 | + | bool started = false; | |
| 101 | + | }; | |
| 102 | + | ||
| 103 | + | #endif |
| @@ -0,0 +1,83 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #ifndef FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 | |
| 21 | + | #define FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 | |
| 22 | + | ||
| 23 | + | #include <vector> | |
| 24 | + | ||
| 25 | + | class Creature; | |
| 26 | + | ||
| 27 | + | class SpectatorVec | |
| 28 | + | { | |
| 29 | + | using Vec = std::vector<Creature*>; | |
| 30 | + | using Iterator = Vec::iterator; | |
| 31 | + | using ConstIterator = Vec::const_iterator; | |
| 32 | + | public: | |
| 33 | + | SpectatorVec() { | |
| 34 | + | vec.reserve(32); | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | void addSpectators(const SpectatorVec& spectators) { | |
| 38 | + | const size_t size = vec.size(); | |
| 39 | + | for (Creature* spectator : spectators.vec) { | |
| 40 | + | bool duplicate = false; | |
| 41 | + | for (size_t i = 0; i < size; ++i) { | |
| 42 | + | if (vec[i] == spectator) { | |
| 43 | + | duplicate = true; | |
| 44 | + | break; | |
| 45 | + | } | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | if (!duplicate) { | |
| 49 | + | vec.emplace_back(spectator); | |
| 50 | + | } | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | void erase(Creature* spectator) { | |
| 55 | + | for (size_t i = 0, len = vec.size(); i < len; i++) { | |
| 56 | + | if (vec[i] == spectator) { | |
| 57 | + | Creature* tmp = vec[len - 1]; | |
| 58 | + | vec[len - 1] = vec[i]; | |
| 59 | + | vec[i] = tmp; | |
| 60 | + | vec.pop_back(); | |
| 61 | + | break; | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | inline size_t size() const { return vec.size(); } | |
| 67 | + | inline bool empty() const { return vec.empty(); } | |
| 68 | + | inline Iterator begin() { return vec.begin(); } | |
| 69 | + | inline ConstIterator begin() const { return vec.begin(); } | |
| 70 | + | inline ConstIterator cbegin() const { return vec.cbegin(); } | |
| 71 | + | inline Iterator end() { return vec.end(); } | |
| 72 | + | inline ConstIterator end() const { return vec.end(); } | |
| 73 | + | inline ConstIterator cend() const { return vec.cend(); } | |
| 74 | + | inline void emplace_back(Creature* c) { return vec.emplace_back(c); } | |
| 75 | + | ||
| 76 | + | template<class InputIterator> | |
| 77 | + | inline void insert(Iterator pos, InputIterator first, InputIterator last) { vec.insert(pos, first, last); } | |
| 78 | + | ||
| 79 | + | private: | |
| 80 | + | Vec vec; | |
| 81 | + | }; | |
| 82 | + | ||
| 83 | + | #endif |
| @@ -0,0 +1,1301 @@ | |||
| 1 | + | /** | |
| 2 | + | * The Forgotten Server - a free and open-source MMORPG server emulator | |
| 3 | + | * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com> | |
| 4 | + | * | |
| 5 | + | * This program is free software; you can redistribute it and/or modify | |
| 6 | + | * it under the terms of the GNU General Public License as published by | |
| 7 | + | * the Free Software Foundation; either version 2 of the License, or | |
| 8 | + | * (at your option) any later version. | |
| 9 | + | * | |
| 10 | + | * This program is distributed in the hope that it will be useful, | |
| 11 | + | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | + | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | + | * GNU General Public License for more details. | |
| 14 | + | * | |
| 15 | + | * You should have received a copy of the GNU General Public License along | |
| 16 | + | * with this program; if not, write to the Free Software Foundation, Inc., | |
| 17 | + | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | #include "otpch.h" | |
| 21 | + | ||
| 22 | + | #include "combat.h" | |
| 23 | + | #include "configmanager.h" | |
| 24 | + | #include "game.h" | |
| 25 | + | #include "monster.h" | |
| 26 | + | #include "pugicast.h" | |
| 27 | + | #include "spells.h" | |
| 28 | + | ||
| 29 | + | extern Game g_game; | |
| 30 | + | extern Spells* g_spells; | |
| 31 | + | extern Monsters g_monsters; | |
| 32 | + | extern Vocations g_vocations; | |
| 33 | + | extern ConfigManager g_config; | |
| 34 | + | extern LuaEnvironment g_luaEnvironment; | |
| 35 | + | ||
| 36 | + | Spells::Spells() | |
| 37 | + | { | |
| 38 | + | scriptInterface.initState(); | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | Spells::~Spells() | |
| 42 | + | { | |
| 43 | + | clear(false); | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) | |
| 47 | + | { | |
| 48 | + | std::string str_words = words; | |
| 49 | + | ||
| 50 | + | //strip trailing spaces | |
| 51 | + | trimString(str_words); | |
| 52 | + | ||
| 53 | + | InstantSpell* instantSpell = getInstantSpell(str_words); | |
| 54 | + | if (!instantSpell) { | |
| 55 | + | return TALKACTION_CONTINUE; | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | std::string param; | |
| 59 | + | ||
| 60 | + | if (instantSpell->getHasParam()) { | |
| 61 | + | size_t spellLen = instantSpell->getWords().length(); | |
| 62 | + | size_t paramLen = str_words.length() - spellLen; | |
| 63 | + | std::string paramText = str_words.substr(spellLen, paramLen); | |
| 64 | + | if (!paramText.empty() && paramText.front() == ' ') { | |
| 65 | + | size_t loc1 = paramText.find('"', 1); | |
| 66 | + | if (loc1 != std::string::npos) { | |
| 67 | + | size_t loc2 = paramText.find('"', loc1 + 1); | |
| 68 | + | if (loc2 == std::string::npos) { | |
| 69 | + | loc2 = paramText.length(); | |
| 70 | + | } else if (paramText.find_last_not_of(' ') != loc2) { | |
| 71 | + | return TALKACTION_CONTINUE; | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); | |
| 75 | + | } else { | |
| 76 | + | trimString(paramText); | |
| 77 | + | loc1 = paramText.find(' ', 0); | |
| 78 | + | if (loc1 == std::string::npos) { | |
| 79 | + | param = paramText; | |
| 80 | + | } else { | |
| 81 | + | return TALKACTION_CONTINUE; | |
| 82 | + | } | |
| 83 | + | } | |
| 84 | + | } | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | if (instantSpell->playerCastInstant(player, param)) { | |
| 88 | + | words = instantSpell->getWords(); | |
| 89 | + | ||
| 90 | + | if (instantSpell->getHasParam() && !param.empty()) { | |
| 91 | + | words += " \"" + param + "\""; | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | return TALKACTION_BREAK; | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | return TALKACTION_FAILED; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | void Spells::clearMaps(bool fromLua) | |
| 101 | + | { | |
| 102 | + | for (auto instant = instants.begin(); instant != instants.end(); ) { | |
| 103 | + | if (fromLua == instant->second.fromLua) { | |
| 104 | + | instant = instants.erase(instant); | |
| 105 | + | } else { | |
| 106 | + | ++instant; | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | for (auto rune = runes.begin(); rune != runes.end(); ) { | |
| 111 | + | if (fromLua == rune->second.fromLua) { | |
| 112 | + | rune = runes.erase(rune); | |
| 113 | + | } else { | |
| 114 | + | ++rune; | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | void Spells::clear(bool fromLua) | |
| 120 | + | { | |
| 121 | + | clearMaps(fromLua); | |
| 122 | + | ||
| 123 | + | reInitState(fromLua); | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | LuaScriptInterface& Spells::getScriptInterface() | |
| 127 | + | { | |
| 128 | + | return scriptInterface; | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | std::string Spells::getScriptBaseName() const | |
| 132 | + | { | |
| 133 | + | return "spells"; | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | Event_ptr Spells::getEvent(const std::string& nodeName) | |
| 137 | + | { | |
| 138 | + | if (strcasecmp(nodeName.c_str(), "rune") == 0) { | |
| 139 | + | return Event_ptr(new RuneSpell(&scriptInterface)); | |
| 140 | + | } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { | |
| 141 | + | return Event_ptr(new InstantSpell(&scriptInterface)); | |
| 142 | + | } | |
| 143 | + | return nullptr; | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) | |
| 147 | + | { | |
| 148 | + | InstantSpell* instant = dynamic_cast<InstantSpell*>(event.get()); | |
| 149 | + | if (instant) { | |
| 150 | + | auto result = instants.emplace(instant->getWords(), std::move(*instant)); | |
| 151 | + | if (!result.second) { | |
| 152 | + | std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; | |
| 153 | + | } | |
| 154 | + | return result.second; | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | RuneSpell* rune = dynamic_cast<RuneSpell*>(event.get()); | |
| 158 | + | if (rune) { | |
| 159 | + | auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); | |
| 160 | + | if (!result.second) { | |
| 161 | + | std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; | |
| 162 | + | } | |
| 163 | + | return result.second; | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | return false; | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | bool Spells::registerInstantLuaEvent(InstantSpell* event) | |
| 170 | + | { | |
| 171 | + | InstantSpell_ptr instant { event }; | |
| 172 | + | if (instant) { | |
| 173 | + | std::string words = instant->getWords(); | |
| 174 | + | auto result = instants.emplace(instant->getWords(), std::move(*instant)); | |
| 175 | + | if (!result.second) { | |
| 176 | + | std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " << words << std::endl; | |
| 177 | + | } | |
| 178 | + | return result.second; | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | return false; | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | bool Spells::registerRuneLuaEvent(RuneSpell* event) | |
| 185 | + | { | |
| 186 | + | RuneSpell_ptr rune { event }; | |
| 187 | + | if (rune) { | |
| 188 | + | uint16_t id = rune->getRuneItemId(); | |
| 189 | + | auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); | |
| 190 | + | if (!result.second) { | |
| 191 | + | std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id << std::endl; | |
| 192 | + | } | |
| 193 | + | return result.second; | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | return false; | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | Spell* Spells::getSpellByName(const std::string& name) | |
| 200 | + | { | |
| 201 | + | Spell* spell = getRuneSpellByName(name); | |
| 202 | + | if (!spell) { | |
| 203 | + | spell = getInstantSpellByName(name); | |
| 204 | + | } | |
| 205 | + | return spell; | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | RuneSpell* Spells::getRuneSpell(uint32_t id) | |
| 209 | + | { | |
| 210 | + | auto it = runes.find(id); | |
| 211 | + | if (it == runes.end()) { | |
| 212 | + | for (auto& rune : runes) { | |
| 213 | + | if (rune.second.getId() == id) { | |
| 214 | + | return &rune.second; | |
| 215 | + | } | |
| 216 | + | } | |
| 217 | + | return nullptr; | |
| 218 | + | } | |
| 219 | + | return &it->second; | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | RuneSpell* Spells::getRuneSpellByName(const std::string& name) | |
| 223 | + | { | |
| 224 | + | for (auto& it : runes) { | |
| 225 | + | if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { | |
| 226 | + | return &it.second; | |
| 227 | + | } | |
| 228 | + | } | |
| 229 | + | return nullptr; | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | InstantSpell* Spells::getInstantSpell(const std::string& words) | |
| 233 | + | { | |
| 234 | + | InstantSpell* result = nullptr; | |
| 235 | + | ||
| 236 | + | for (auto& it : instants) { | |
| 237 | + | const std::string& instantSpellWords = it.second.getWords(); | |
| 238 | + | size_t spellLen = instantSpellWords.length(); | |
| 239 | + | if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { | |
| 240 | + | if (!result || spellLen > result->getWords().length()) { | |
| 241 | + | result = &it.second; | |
| 242 | + | if (words.length() == spellLen) { | |
| 243 | + | break; | |
| 244 | + | } | |
| 245 | + | } | |
| 246 | + | } | |
| 247 | + | } | |
| 248 | + | ||
| 249 | + | if (result) { | |
| 250 | + | const std::string& resultWords = result->getWords(); | |
| 251 | + | if (words.length() > resultWords.length()) { | |
| 252 | + | if (!result->getHasParam()) { | |
| 253 | + | return nullptr; | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | size_t spellLen = resultWords.length(); | |
| 257 | + | size_t paramLen = words.length() - spellLen; | |
| 258 | + | if (paramLen < 2 || words[spellLen] != ' ') { | |
| 259 | + | return nullptr; | |
| 260 | + | } | |
| 261 | + | } | |
| 262 | + | return result; | |
| 263 | + | } | |
| 264 | + | return nullptr; | |
| 265 | + | } | |
| 266 | + | ||
| 267 | + | InstantSpell* Spells::getInstantSpellById(uint32_t spellId) | |
| 268 | + | { | |
| 269 | + | for (auto& it : instants) { | |
| 270 | + | if (it.second.getId() == spellId) { | |
| 271 | + | return &it.second; | |
| 272 | + | } | |
| 273 | + | } | |
| 274 | + | return nullptr; | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | InstantSpell* Spells::getInstantSpellByName(const std::string& name) | |
| 278 | + | { | |
| 279 | + | for (auto& it : instants) { | |
| 280 | + | if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { | |
| 281 | + | return &it.second; | |
| 282 | + | } | |
| 283 | + | } | |
| 284 | + | return nullptr; | |
| 285 | + | } | |
| 286 | + | ||
| 287 | + | Position Spells::getCasterPosition(Creature* creature, Direction dir) | |
| 288 | + | { | |
| 289 | + | return getNextPosition(dir, creature->getPosition()); | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) : | |
| 293 | + | Event(&g_spells->getScriptInterface()), | |
| 294 | + | combat(combat), | |
| 295 | + | needDirection(needDirection), | |
| 296 | + | needTarget(needTarget) | |
| 297 | + | {} | |
| 298 | + | ||
| 299 | + | CombatSpell::~CombatSpell() | |
| 300 | + | { | |
| 301 | + | if (!scripted) { | |
| 302 | + | delete combat; | |
| 303 | + | } | |
| 304 | + | } | |
| 305 | + | ||
| 306 | + | bool CombatSpell::loadScriptCombat() | |
| 307 | + | { | |
| 308 | + | combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId); | |
| 309 | + | return combat != nullptr; | |
| 310 | + | } | |
| 311 | + | ||
| 312 | + | bool CombatSpell::castSpell(Creature* creature) | |
| 313 | + | { | |
| 314 | + | if (scripted) { | |
| 315 | + | LuaVariant var; | |
| 316 | + | var.type = VARIANT_POSITION; | |
| 317 | + | ||
| 318 | + | if (needDirection) { | |
| 319 | + | var.pos = Spells::getCasterPosition(creature, creature->getDirection()); | |
| 320 | + | } else { | |
| 321 | + | var.pos = creature->getPosition(); | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | return executeCastSpell(creature, var); | |
| 325 | + | } | |
| 326 | + | ||
| 327 | + | Position pos; | |
| 328 | + | if (needDirection) { | |
| 329 | + | pos = Spells::getCasterPosition(creature, creature->getDirection()); | |
| 330 | + | } else { | |
| 331 | + | pos = creature->getPosition(); | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | combat->doCombat(creature, pos); | |
| 335 | + | return true; | |
| 336 | + | } | |
| 337 | + | ||
| 338 | + | bool CombatSpell::castSpell(Creature* creature, Creature* target) | |
| 339 | + | { | |
| 340 | + | if (scripted) { | |
| 341 | + | LuaVariant var; | |
| 342 | + | ||
| 343 | + | if (combat->hasArea()) { | |
| 344 | + | var.type = VARIANT_POSITION; | |
| 345 | + | ||
| 346 | + | if (needTarget) { | |
| 347 | + | var.pos = target->getPosition(); | |
| 348 | + | } else if (needDirection) { | |
| 349 | + | var.pos = Spells::getCasterPosition(creature, creature->getDirection()); | |
| 350 | + | } else { | |
| 351 | + | var.pos = creature->getPosition(); | |
| 352 | + | } | |
| 353 | + | } else { | |
| 354 | + | var.type = VARIANT_NUMBER; | |
| 355 | + | var.number = target->getID(); | |
| 356 | + | } | |
| 357 | + | return executeCastSpell(creature, var); | |
| 358 | + | } | |
| 359 | + | ||
| 360 | + | if (combat->hasArea()) { | |
| 361 | + | if (needTarget) { | |
| 362 | + | combat->doCombat(creature, target->getPosition()); | |
| 363 | + | } else { | |
| 364 | + | return castSpell(creature); | |
| 365 | + | } | |
| 366 | + | } else { | |
| 367 | + | combat->doCombat(creature, target); | |
| 368 | + | } | |
| 369 | + | return true; | |
| 370 | + | } | |
| 371 | + | ||
| 372 | + | bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) | |
| 373 | + | { | |
| 374 | + | //onCastSpell(creature, var) | |
| 375 | + | if (!scriptInterface->reserveScriptEnv()) { | |
| 376 | + | std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl; | |
| 377 | + | return false; | |
| 378 | + | } | |
| 379 | + | ||
| 380 | + | ScriptEnvironment* env = scriptInterface->getScriptEnv(); | |
| 381 | + | env->setScriptId(scriptId, scriptInterface); | |
| 382 | + | ||
| 383 | + | lua_State* L = scriptInterface->getLuaState(); | |
| 384 | + | ||
| 385 | + | scriptInterface->pushFunction(scriptId); | |
| 386 | + | ||
| 387 | + | LuaScriptInterface::pushUserdata<Creature>(L, creature); | |
| 388 | + | LuaScriptInterface::setCreatureMetatable(L, -1, creature); | |
| 389 | + | ||
| 390 | + | LuaScriptInterface::pushVariant(L, var); | |
| 391 | + | ||
| 392 | + | return scriptInterface->callFunction(2); | |
| 393 | + | } | |
| 394 | + | ||
| 395 | + | bool Spell::configureSpell(const pugi::xml_node& node) | |
| 396 | + | { | |
| 397 | + | pugi::xml_attribute nameAttribute = node.attribute("name"); | |
| 398 | + | if (!nameAttribute) { | |
| 399 | + | std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl; | |
| 400 | + | return false; | |
| 401 | + | } | |
| 402 | + | ||
| 403 | + | name = nameAttribute.as_string(); | |
| 404 | + | ||
| 405 | + | static const char* reservedList[] = { | |
| 406 | + | "melee", | |
| 407 | + | "physical", | |
| 408 | + | "poison", | |
| 409 | + | "fire", | |
| 410 | + | "energy", | |
| 411 | + | "drown", | |
| 412 | + | "lifedrain", | |
| 413 | + | "manadrain", | |
| 414 | + | "healing", | |
| 415 | + | "speed", | |
| 416 | + | "outfit", | |
| 417 | + | "invisible", | |
| 418 | + | "drunk", | |
| 419 | + | "firefield", | |
| 420 | + | "poisonfield", | |
| 421 | + | "energyfield", | |
| 422 | + | "firecondition", | |
| 423 | + | "poisoncondition", | |
| 424 | + | "energycondition", | |
| 425 | + | "drowncondition", | |
| 426 | + | "freezecondition", | |
| 427 | + | "cursecondition", | |
| 428 | + | "dazzlecondition" | |
| 429 | + | }; | |
| 430 | + | ||
| 431 | + | //static size_t size = sizeof(reservedList) / sizeof(const char*); | |
| 432 | + | //for (size_t i = 0; i < size; ++i) { | |
| 433 | + | for (const char* reserved : reservedList) { | |
| 434 | + | if (strcasecmp(reserved, name.c_str()) == 0) { | |
| 435 | + | std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; | |
| 436 | + | return false; | |
| 437 | + | } | |
| 438 | + | } | |
| 439 | + | ||
| 440 | + | pugi::xml_attribute attr; | |
| 441 | + | if ((attr = node.attribute("spellid"))) { | |
| 442 | + | spellId = pugi::cast<uint16_t>(attr.value()); | |
| 443 | + | } | |
| 444 | + | ||
| 445 | + | if ((attr = node.attribute("group"))) { | |
| 446 | + | std::string tmpStr = asLowerCaseString(attr.as_string()); | |
| 447 | + | if (tmpStr == "none" || tmpStr == "0") { | |
| 448 | + | group = SPELLGROUP_NONE; | |
| 449 | + | } else if (tmpStr == "attack" || tmpStr == "1") { | |
| 450 | + | group = SPELLGROUP_ATTACK; | |
| 451 | + | } else if (tmpStr == "healing" || tmpStr == "2") { | |
| 452 | + | group = SPELLGROUP_HEALING; | |
| 453 | + | } else if (tmpStr == "support" || tmpStr == "3") { | |
| 454 | + | group = SPELLGROUP_SUPPORT; | |
| 455 | + | } else if (tmpStr == "special" || tmpStr == "4") { | |
| 456 | + | group = SPELLGROUP_SPECIAL; | |
| 457 | + | } else { | |
| 458 | + | std::cout << "[Warning - Spell::configureSpell] Unknown group: " << attr.as_string() << std::endl; | |
| 459 | + | } | |
| 460 | + | } | |
| 461 | + | ||
| 462 | + | if ((attr = node.attribute("groupcooldown"))) { | |
| 463 | + | groupCooldown = pugi::cast<uint32_t>(attr.value()); | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | if ((attr = node.attribute("secondarygroup"))) { | |
| 467 | + | std::string tmpStr = asLowerCaseString(attr.as_string()); | |
| 468 | + | if (tmpStr == "none" || tmpStr == "0") { | |
| 469 | + | secondaryGroup = SPELLGROUP_NONE; | |
| 470 | + | } else if (tmpStr == "attack" || tmpStr == "1") { | |
| 471 | + | secondaryGroup = SPELLGROUP_ATTACK; | |
| 472 | + | } else if (tmpStr == "healing" || tmpStr == "2") { | |
| 473 | + | secondaryGroup = SPELLGROUP_HEALING; | |
| 474 | + | } else if (tmpStr == "support" || tmpStr == "3") { | |
| 475 | + | secondaryGroup = SPELLGROUP_SUPPORT; | |
| 476 | + | } else if (tmpStr == "special" || tmpStr == "4") { | |
| 477 | + | secondaryGroup = SPELLGROUP_SPECIAL; | |
| 478 | + | } else { | |
| 479 | + | std::cout << "[Warning - Spell::configureSpell] Unknown secondarygroup: " << attr.as_string() << std::endl; | |
| 480 | + | } | |
| 481 | + | } | |
| 482 | + | ||
| 483 | + | if ((attr = node.attribute("secondarygroupcooldown"))) { | |
| 484 | + | secondaryGroupCooldown = pugi::cast<uint32_t>(attr.value()); | |
| 485 | + | } | |
| 486 | + | ||
| 487 | + | if ((attr = node.attribute("level")) || (attr = node.attribute("lvl"))) { | |
| 488 | + | level = pugi::cast<uint32_t>(attr.value()); | |
| 489 | + | } | |
| 490 | + | ||
| 491 | + | if ((attr = node.attribute("magiclevel")) || (attr = node.attribute("maglv"))) { | |
| 492 | + | magLevel = pugi::cast<uint32_t>(attr.value()); | |
| 493 | + | } | |
| 494 | + | ||
| 495 | + | if ((attr = node.attribute("mana"))) { | |
| 496 | + | mana = pugi::cast<uint32_t>(attr.value()); | |
| 497 | + | } | |
| 498 | + | ||
| 499 | + | if ((attr = node.attribute("manapercent"))) { | |
| 500 | + | manaPercent = pugi::cast<uint32_t>(attr.value()); | |
| 501 | + | } | |
| 502 | + | ||
| 503 | + | if ((attr = node.attribute("soul"))) { | |
| 504 | + | soul = pugi::cast<uint32_t>(attr.value()); | |
| 505 | + | } | |
| 506 | + | ||
| 507 | + | if ((attr = node.attribute("range"))) { | |
| 508 | + | range = pugi::cast<int32_t>(attr.value()); | |
| 509 | + | } | |
| 510 | + | ||
| 511 | + | if ((attr = node.attribute("cooldown")) || (attr = node.attribute("exhaustion"))) { | |
| 512 | + | cooldown = pugi::cast<uint32_t>(attr.value()); | |
| 513 | + | } | |
| 514 | + | ||
| 515 | + | if ((attr = node.attribute("premium")) || (attr = node.attribute("prem"))) { | |
| 516 | + | premium = attr.as_bool(); | |
| 517 | + | } | |
| 518 | + | ||
| 519 | + | if ((attr = node.attribute("enabled"))) { | |
| 520 | + | enabled = attr.as_bool(); | |
| 521 | + | } | |
| 522 | + | ||
| 523 | + | if ((attr = node.attribute("needtarget"))) { | |
| 524 | + | needTarget = attr.as_bool(); | |
| 525 | + | } | |
| 526 | + | ||
| 527 | + | if ((attr = node.attribute("needweapon"))) { | |
| 528 | + | needWeapon = attr.as_bool(); | |
| 529 | + | } | |
| 530 | + | ||
| 531 | + | if ((attr = node.attribute("selftarget"))) { | |
| 532 | + | selfTarget = attr.as_bool(); | |
| 533 | + | } | |
| 534 | + | ||
| 535 | + | if ((attr = node.attribute("needlearn"))) { | |
| 536 | + | learnable = attr.as_bool(); | |
| 537 | + | } | |
| 538 | + | ||
| 539 | + | if ((attr = node.attribute("blocking"))) { | |
| 540 | + | blockingSolid = attr.as_bool(); | |
| 541 | + | blockingCreature = blockingSolid; | |
| 542 | + | } | |
| 543 | + | ||
| 544 | + | if ((attr = node.attribute("blocktype"))) { | |
| 545 | + | std::string tmpStrValue = asLowerCaseString(attr.as_string()); | |
| 546 | + | if (tmpStrValue == "all") { | |
| 547 | + | blockingSolid = true; | |
| 548 | + | blockingCreature = true; | |
| 549 | + | } else if (tmpStrValue == "solid") { | |
| 550 | + | blockingSolid = true; | |
| 551 | + | } else if (tmpStrValue == "creature") { | |
| 552 | + | blockingCreature = true; | |
| 553 | + | } else { | |
| 554 | + | std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl; | |
| 555 | + | } | |
| 556 | + | } | |
| 557 | + | ||
| 558 | + | if ((attr = node.attribute("pzlock"))) { | |
| 559 | + | pzLock = booleanString(attr.as_string()); | |
| 560 | + | } | |
| 561 | + | ||
| 562 | + | if ((attr = node.attribute("aggressive"))) { | |
| 563 | + | aggressive = booleanString(attr.as_string()); | |
| 564 | + | } | |
| 565 | + | ||
| 566 | + | if (group == SPELLGROUP_NONE) { | |
| 567 | + | group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); | |
| 568 | + | } | |
| 569 | + | ||
| 570 | + | for (auto vocationNode : node.children()) { | |
| 571 | + | if (!(attr = vocationNode.attribute("name"))) { | |
| 572 | + | continue; | |
| 573 | + | } | |
| 574 | + | ||
| 575 | + | int32_t vocationId = g_vocations.getVocationId(attr.as_string()); | |
| 576 | + | if (vocationId != -1) { | |
| 577 | + | attr = vocationNode.attribute("showInDescription"); | |
| 578 | + | vocSpellMap[vocationId] = !attr || attr.as_bool(); | |
| 579 | + | } else { | |
| 580 | + | std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl; | |
| 581 | + | } | |
| 582 | + | } | |
| 583 | + | return true; | |
| 584 | + | } | |
| 585 | + | ||
| 586 | + | bool Spell::playerSpellCheck(Player* player) const | |
| 587 | + | { | |
| 588 | + | if (player->hasFlag(PlayerFlag_CannotUseSpells)) { | |
| 589 | + | return false; | |
| 590 | + | } | |
| 591 | + | ||
| 592 | + | if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { | |
| 593 | + | return true; | |
| 594 | + | } | |
| 595 | + | ||
| 596 | + | if (!enabled) { | |
| 597 | + | return false; | |
| 598 | + | } | |
| 599 | + | ||
| 600 | + | if ((aggressive || pzLock) && (range < 1 || (range > 0 && !player->getAttackedCreature())) && player->getSkull() == SKULL_BLACK) { | |
| 601 | + | player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); | |
| 602 | + | return false; | |
| 603 | + | } | |
| 604 | + | ||
| 605 | + | if ((aggressive || pzLock) && player->hasCondition(CONDITION_PACIFIED)) { | |
| 606 | + | player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); | |
| 607 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 608 | + | return false; | |
| 609 | + | } | |
| 610 | + | ||
| 611 | + | if ((aggressive || pzLock) && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { | |
| 612 | + | player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); | |
| 613 | + | return false; | |
| 614 | + | } | |
| 615 | + | ||
| 616 | + | if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { | |
| 617 | + | player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); | |
| 618 | + | ||
| 619 | + | if (isInstant()) { | |
| 620 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 621 | + | } | |
| 622 | + | ||
| 623 | + | return false; | |
| 624 | + | } | |
| 625 | + | ||
| 626 | + | if (player->getLevel() < level) { | |
| 627 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL); | |
| 628 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 629 | + | return false; | |
| 630 | + | } | |
| 631 | + | ||
| 632 | + | if (player->getMagicLevel() < magLevel) { | |
| 633 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL); | |
| 634 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 635 | + | return false; | |
| 636 | + | } | |
| 637 | + | ||
| 638 | + | if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) { | |
| 639 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); | |
| 640 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 641 | + | return false; | |
| 642 | + | } | |
| 643 | + | ||
| 644 | + | if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) { | |
| 645 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL); | |
| 646 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 647 | + | return false; | |
| 648 | + | } | |
| 649 | + | ||
| 650 | + | if (isInstant() && isLearnable()) { | |
| 651 | + | if (!player->hasLearnedInstantSpell(getName())) { | |
| 652 | + | player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL); | |
| 653 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 654 | + | return false; | |
| 655 | + | } | |
| 656 | + | } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) { | |
| 657 | + | player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); | |
| 658 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 659 | + | return false; | |
| 660 | + | } | |
| 661 | + | ||
| 662 | + | if (needWeapon) { | |
| 663 | + | switch (player->getWeaponType()) { | |
| 664 | + | case WEAPON_SWORD: | |
| 665 | + | case WEAPON_CLUB: | |
| 666 | + | case WEAPON_AXE: | |
| 667 | + | break; | |
| 668 | + | ||
| 669 | + | default: { | |
| 670 | + | player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); | |
| 671 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 672 | + | return false; | |
| 673 | + | } | |
| 674 | + | } | |
| 675 | + | } | |
| 676 | + | ||
| 677 | + | if (isPremium() && !player->isPremium()) { | |
| 678 | + | player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); | |
| 679 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 680 | + | return false; | |
| 681 | + | } | |
| 682 | + | ||
| 683 | + | return true; | |
| 684 | + | } | |
| 685 | + | ||
| 686 | + | bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) | |
| 687 | + | { | |
| 688 | + | if (toPos.x == 0xFFFF) { | |
| 689 | + | return true; | |
| 690 | + | } | |
| 691 | + | ||
| 692 | + | const Position& playerPos = player->getPosition(); | |
| 693 | + | if (playerPos.z > toPos.z) { | |
| 694 | + | player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); | |
| 695 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 696 | + | return false; | |
| 697 | + | } else if (playerPos.z < toPos.z) { | |
| 698 | + | player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); | |
| 699 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 700 | + | return false; | |
| 701 | + | } | |
| 702 | + | ||
| 703 | + | Tile* tile = g_game.map.getTile(toPos); | |
| 704 | + | if (!tile) { | |
| 705 | + | tile = new StaticTile(toPos.x, toPos.y, toPos.z); | |
| 706 | + | g_game.map.setTile(toPos, tile); | |
| 707 | + | } | |
| 708 | + | ||
| 709 | + | ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); | |
| 710 | + | if (ret != RETURNVALUE_NOERROR) { | |
| 711 | + | player->sendCancelMessage(ret); | |
| 712 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 713 | + | return false; | |
| 714 | + | } | |
| 715 | + | ||
| 716 | + | if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) { | |
| 717 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); | |
| 718 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 719 | + | return false; | |
| 720 | + | } | |
| 721 | + | ||
| 722 | + | if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { | |
| 723 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); | |
| 724 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 725 | + | return false; | |
| 726 | + | } | |
| 727 | + | ||
| 728 | + | return true; | |
| 729 | + | } | |
| 730 | + | ||
| 731 | + | bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) | |
| 732 | + | { | |
| 733 | + | if (!playerSpellCheck(player)) { | |
| 734 | + | return false; | |
| 735 | + | } | |
| 736 | + | ||
| 737 | + | if (toPos.x == 0xFFFF) { | |
| 738 | + | return true; | |
| 739 | + | } | |
| 740 | + | ||
| 741 | + | const Position& playerPos = player->getPosition(); | |
| 742 | + | if (playerPos.z > toPos.z) { | |
| 743 | + | player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); | |
| 744 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 745 | + | return false; | |
| 746 | + | } else if (playerPos.z < toPos.z) { | |
| 747 | + | player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); | |
| 748 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 749 | + | return false; | |
| 750 | + | } | |
| 751 | + | ||
| 752 | + | Tile* tile = g_game.map.getTile(toPos); | |
| 753 | + | if (!tile) { | |
| 754 | + | player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); | |
| 755 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 756 | + | return false; | |
| 757 | + | } | |
| 758 | + | ||
| 759 | + | if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { | |
| 760 | + | player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); | |
| 761 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 762 | + | return false; | |
| 763 | + | } | |
| 764 | + | ||
| 765 | + | ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); | |
| 766 | + | if (ret != RETURNVALUE_NOERROR) { | |
| 767 | + | player->sendCancelMessage(ret); | |
| 768 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 769 | + | return false; | |
| 770 | + | } | |
| 771 | + | ||
| 772 | + | const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player); | |
| 773 | + | if (blockingCreature && topVisibleCreature) { | |
| 774 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); | |
| 775 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 776 | + | return false; | |
| 777 | + | } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { | |
| 778 | + | player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); | |
| 779 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 780 | + | return false; | |
| 781 | + | } | |
| 782 | + | ||
| 783 | + | if (needTarget && !topVisibleCreature) { | |
| 784 | + | player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); | |
| 785 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 786 | + | return false; | |
| 787 | + | } | |
| 788 | + | ||
| 789 | + | if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { | |
| 790 | + | const Player* targetPlayer = topVisibleCreature->getPlayer(); | |
| 791 | + | if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { | |
| 792 | + | player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); | |
| 793 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 794 | + | return false; | |
| 795 | + | } | |
| 796 | + | } | |
| 797 | + | return true; | |
| 798 | + | } | |
| 799 | + | ||
| 800 | + | void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const | |
| 801 | + | { | |
| 802 | + | if (finishedCast) { | |
| 803 | + | if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { | |
| 804 | + | if (cooldown > 0) { | |
| 805 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); | |
| 806 | + | player->addCondition(condition); | |
| 807 | + | } | |
| 808 | + | ||
| 809 | + | if (groupCooldown > 0) { | |
| 810 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); | |
| 811 | + | player->addCondition(condition); | |
| 812 | + | } | |
| 813 | + | ||
| 814 | + | if (secondaryGroupCooldown > 0) { | |
| 815 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); | |
| 816 | + | player->addCondition(condition); | |
| 817 | + | } | |
| 818 | + | } | |
| 819 | + | ||
| 820 | + | if (aggressive) { | |
| 821 | + | player->addInFightTicks(); | |
| 822 | + | } | |
| 823 | + | } | |
| 824 | + | ||
| 825 | + | if (payCost) { | |
| 826 | + | Spell::postCastSpell(player, getManaCost(player), getSoulCost()); | |
| 827 | + | } | |
| 828 | + | } | |
| 829 | + | ||
| 830 | + | void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) | |
| 831 | + | { | |
| 832 | + | if (manaCost > 0) { | |
| 833 | + | player->addManaSpent(manaCost); | |
| 834 | + | player->changeMana(-static_cast<int32_t>(manaCost)); | |
| 835 | + | } | |
| 836 | + | ||
| 837 | + | if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) { | |
| 838 | + | if (soulCost > 0) { | |
| 839 | + | player->changeSoul(-static_cast<int32_t>(soulCost)); | |
| 840 | + | } | |
| 841 | + | } | |
| 842 | + | } | |
| 843 | + | ||
| 844 | + | uint32_t Spell::getManaCost(const Player* player) const | |
| 845 | + | { | |
| 846 | + | if (mana != 0) { | |
| 847 | + | return mana; | |
| 848 | + | } | |
| 849 | + | ||
| 850 | + | if (manaPercent != 0) { | |
| 851 | + | uint32_t maxMana = player->getMaxMana(); | |
| 852 | + | uint32_t manaCost = (maxMana * manaPercent) / 100; | |
| 853 | + | return manaCost; | |
| 854 | + | } | |
| 855 | + | ||
| 856 | + | return 0; | |
| 857 | + | } | |
| 858 | + | ||
| 859 | + | std::string InstantSpell::getScriptEventName() const | |
| 860 | + | { | |
| 861 | + | return "onCastSpell"; | |
| 862 | + | } | |
| 863 | + | ||
| 864 | + | bool InstantSpell::configureEvent(const pugi::xml_node& node) | |
| 865 | + | { | |
| 866 | + | if (!Spell::configureSpell(node)) { | |
| 867 | + | return false; | |
| 868 | + | } | |
| 869 | + | ||
| 870 | + | if (!TalkAction::configureEvent(node)) { | |
| 871 | + | return false; | |
| 872 | + | } | |
| 873 | + | ||
| 874 | + | spellType = SPELL_INSTANT; | |
| 875 | + | ||
| 876 | + | pugi::xml_attribute attr; | |
| 877 | + | if ((attr = node.attribute("params"))) { | |
| 878 | + | hasParam = attr.as_bool(); | |
| 879 | + | } | |
| 880 | + | ||
| 881 | + | if ((attr = node.attribute("playernameparam"))) { | |
| 882 | + | hasPlayerNameParam = attr.as_bool(); | |
| 883 | + | } | |
| 884 | + | ||
| 885 | + | if ((attr = node.attribute("direction"))) { | |
| 886 | + | needDirection = attr.as_bool(); | |
| 887 | + | } else if ((attr = node.attribute("casterTargetOrDirection"))) { | |
| 888 | + | casterTargetOrDirection = attr.as_bool(); | |
| 889 | + | } | |
| 890 | + | ||
| 891 | + | if ((attr = node.attribute("blockwalls"))) { | |
| 892 | + | checkLineOfSight = attr.as_bool(); | |
| 893 | + | } | |
| 894 | + | return true; | |
| 895 | + | } | |
| 896 | + | ||
| 897 | + | bool InstantSpell::playerCastInstant(Player* player, std::string& param) | |
| 898 | + | { | |
| 899 | + | if (!playerSpellCheck(player)) { | |
| 900 | + | return false; | |
| 901 | + | } | |
| 902 | + | ||
| 903 | + | LuaVariant var; | |
| 904 | + | ||
| 905 | + | if (selfTarget) { | |
| 906 | + | var.type = VARIANT_NUMBER; | |
| 907 | + | var.number = player->getID(); | |
| 908 | + | } else if (needTarget || casterTargetOrDirection) { | |
| 909 | + | Creature* target = nullptr; | |
| 910 | + | bool useDirection = false; | |
| 911 | + | ||
| 912 | + | if (hasParam) { | |
| 913 | + | Player* playerTarget = nullptr; | |
| 914 | + | ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); | |
| 915 | + | ||
| 916 | + | if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) { | |
| 917 | + | playerTarget = nullptr; | |
| 918 | + | } | |
| 919 | + | ||
| 920 | + | target = playerTarget; | |
| 921 | + | if (!target || target->getHealth() <= 0) { | |
| 922 | + | if (!casterTargetOrDirection) { | |
| 923 | + | if (cooldown > 0) { | |
| 924 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); | |
| 925 | + | player->addCondition(condition); | |
| 926 | + | } | |
| 927 | + | ||
| 928 | + | if (groupCooldown > 0) { | |
| 929 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); | |
| 930 | + | player->addCondition(condition); | |
| 931 | + | } | |
| 932 | + | ||
| 933 | + | if (secondaryGroupCooldown > 0) { | |
| 934 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); | |
| 935 | + | player->addCondition(condition); | |
| 936 | + | } | |
| 937 | + | ||
| 938 | + | player->sendCancelMessage(ret); | |
| 939 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 940 | + | return false; | |
| 941 | + | } | |
| 942 | + | ||
| 943 | + | useDirection = true; | |
| 944 | + | } | |
| 945 | + | ||
| 946 | + | if (playerTarget) { | |
| 947 | + | param = playerTarget->getName(); | |
| 948 | + | } | |
| 949 | + | } else { | |
| 950 | + | target = player->getAttackedCreature(); | |
| 951 | + | if (!target || target->getHealth() <= 0) { | |
| 952 | + | if (!casterTargetOrDirection) { | |
| 953 | + | player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES); | |
| 954 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 955 | + | return false; | |
| 956 | + | } | |
| 957 | + | ||
| 958 | + | useDirection = true; | |
| 959 | + | } | |
| 960 | + | } | |
| 961 | + | ||
| 962 | + | if (!useDirection) { | |
| 963 | + | if (!canThrowSpell(player, target)) { | |
| 964 | + | player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); | |
| 965 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 966 | + | return false; | |
| 967 | + | } | |
| 968 | + | ||
| 969 | + | var.type = VARIANT_NUMBER; | |
| 970 | + | var.number = target->getID(); | |
| 971 | + | } else { | |
| 972 | + | var.type = VARIANT_POSITION; | |
| 973 | + | var.pos = Spells::getCasterPosition(player, player->getDirection()); | |
| 974 | + | ||
| 975 | + | if (!playerInstantSpellCheck(player, var.pos)) { | |
| 976 | + | return false; | |
| 977 | + | } | |
| 978 | + | } | |
| 979 | + | } else if (hasParam) { | |
| 980 | + | var.type = VARIANT_STRING; | |
| 981 | + | ||
| 982 | + | if (getHasPlayerNameParam()) { | |
| 983 | + | Player* playerTarget = nullptr; | |
| 984 | + | ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); | |
| 985 | + | ||
| 986 | + | if (ret != RETURNVALUE_NOERROR) { | |
| 987 | + | if (cooldown > 0) { | |
| 988 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); | |
| 989 | + | player->addCondition(condition); | |
| 990 | + | } | |
| 991 | + | ||
| 992 | + | if (groupCooldown > 0) { | |
| 993 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); | |
| 994 | + | player->addCondition(condition); | |
| 995 | + | } | |
| 996 | + | ||
| 997 | + | if (secondaryGroupCooldown > 0) { | |
| 998 | + | Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); | |
| 999 | + | player->addCondition(condition); | |
| 1000 | + | } | |
| 1001 | + | ||
| 1002 | + | player->sendCancelMessage(ret); | |
| 1003 | + | g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); | |
| 1004 | + | return false; | |
| 1005 | + | } | |
| 1006 | + | ||
| 1007 | + | if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) { | |
| 1008 | + | param = playerTarget->getName(); | |
| 1009 | + | } | |
| 1010 | + | } | |
| 1011 | + | ||
| 1012 | + | var.text = param; | |
| 1013 | + | } else { | |
| 1014 | + | var.type = VARIANT_POSITION; | |
| 1015 | + | ||
| 1016 | + | if (needDirection) { | |
| 1017 | + | var.pos = Spells::getCasterPosition(player, player->getDirection()); | |
| 1018 | + | } else { | |
| 1019 | + | var.pos = player->getPosition(); | |
| 1020 | + | } | |
| 1021 | + | ||
| 1022 | + | if (!playerInstantSpellCheck(player, var.pos)) { | |
| 1023 | + | return false; | |
| 1024 | + | } | |
| 1025 | + | } | |
| 1026 | + | ||
| 1027 | + | bool result = internalCastSpell(player, var); | |
| 1028 | + | if (result) { | |
| 1029 | + | postCastSpell(player); | |
| 1030 | + | } | |
| 1031 | + | ||
| 1032 | + | return result; | |
| 1033 | + | } | |
| 1034 | + | ||
| 1035 | + | bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const | |
| 1036 | + | { | |
| 1037 | + | const Position& fromPos = creature->getPosition(); | |
| 1038 | + | const Position& toPos = target->getPosition(); | |
| 1039 | + | if (fromPos.z != toPos.z || | |
| 1040 | + | (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || | |
| 1041 | + | (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { | |
| 1042 | + | return false; | |
| 1043 | + | } | |
| 1044 | + | return true; | |
| 1045 | + | } | |
| 1046 | + | ||
| 1047 | + | bool InstantSpell::castSpell(Creature* creature) | |
| 1048 | + | { | |
| 1049 | + | LuaVariant var; | |
| 1050 | + | ||
| 1051 | + | if (casterTargetOrDirection) { | |
| 1052 | + | Creature* target = creature->getAttackedCreature(); | |
| 1053 | + | if (target && target->getHealth() > 0) { | |
| 1054 | + | if (!canThrowSpell(creature, target)) { | |
| 1055 | + | return false; | |
| 1056 | + | } | |
| 1057 | + | ||
| 1058 | + | var.type = VARIANT_NUMBER; | |
| 1059 | + | var.number = target->getID(); | |
| 1060 | + | return internalCastSpell(creature, var); | |
| 1061 | + | } | |
| 1062 | + | ||
| 1063 | + | return false; | |
| 1064 | + | } else if (needDirection) { | |
| 1065 | + | var.type = VARIANT_POSITION; | |
| 1066 | + | var.pos = Spells::getCasterPosition(creature, creature->getDirection()); | |
| 1067 | + | } else { | |
| 1068 | + | var.type = VARIANT_POSITION; | |
| 1069 | + | var.pos = creature->getPosition(); | |
| 1070 | + | } | |
| 1071 | + | ||
| 1072 | + | return internalCastSpell(creature, var); | |
| 1073 | + | } | |
| 1074 | + | ||
| 1075 | + | bool InstantSpell::castSpell(Creature* creature, Creature* target) | |
| 1076 | + | { | |
| 1077 | + | if (needTarget) { | |
| 1078 | + | LuaVariant var; | |
| 1079 | + | var.type = VARIANT_NUMBER; | |
| 1080 | + | var.number = target->getID(); | |
| 1081 | + | return internalCastSpell(creature, var); | |
| 1082 | + | } else { | |
| 1083 | + | return castSpell(creature); | |
| 1084 | + | } | |
| 1085 | + | } | |
| 1086 | + | ||
| 1087 | + | bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) | |
| 1088 | + | { | |
| 1089 | + | return executeCastSpell(creature, var); | |
| 1090 | + | } | |
| 1091 | + | ||
| 1092 | + | bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) | |
| 1093 | + | { | |
| 1094 | + | //onCastSpell(creature, var) | |
| 1095 | + | if (!scriptInterface->reserveScriptEnv()) { | |
| 1096 | + | std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl; | |
| 1097 | + | return false; | |
| 1098 | + | } | |
| 1099 | + | ||
| 1100 | + | ScriptEnvironment* env = scriptInterface->getScriptEnv(); | |
| 1101 | + | env->setScriptId(scriptId, scriptInterface); | |
| 1102 | + | ||
| 1103 | + | lua_State* L = scriptInterface->getLuaState(); | |
| 1104 | + | ||
| 1105 | + | scriptInterface->pushFunction(scriptId); | |
| 1106 | + | ||
| 1107 | + | LuaScriptInterface::pushUserdata<Creature>(L, creature); | |
| 1108 | + | LuaScriptInterface::setCreatureMetatable(L, -1, creature); | |
| 1109 | + | ||
| 1110 | + | LuaScriptInterface::pushVariant(L, var); | |
| 1111 | + | ||
| 1112 | + | return scriptInterface->callFunction(2); | |
| 1113 | + | } | |
| 1114 | + | ||
| 1115 | + | bool InstantSpell::canCast(const Player* player) const | |
| 1116 | + | { | |
| 1117 | + | if (player->hasFlag(PlayerFlag_CannotUseSpells)) { | |
| 1118 | + | return false; | |
| 1119 | + | } | |
| 1120 | + | ||
| 1121 | + | if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { | |
| 1122 | + | return true; | |
| 1123 | + | } | |
| 1124 | + | ||
| 1125 | + | if (isLearnable()) { | |
| 1126 | + | if (player->hasLearnedInstantSpell(getName())) { | |
| 1127 | + | return true; | |
| 1128 | + | } | |
| 1129 | + | } else { | |
| 1130 | + | if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) { | |
| 1131 | + | return true; | |
| 1132 | + | } | |
| 1133 | + | } | |
| 1134 | + | ||
| 1135 | + | return false; | |
| 1136 | + | } | |
| 1137 | + | ||
| 1138 | + | std::string RuneSpell::getScriptEventName() const | |
| 1139 | + | { | |
| 1140 | + | return "onCastSpell"; | |
| 1141 | + | } | |
| 1142 | + | ||
| 1143 | + | bool RuneSpell::configureEvent(const pugi::xml_node& node) | |
| 1144 | + | { | |
| 1145 | + | if (!Spell::configureSpell(node)) { | |
| 1146 | + | return false; | |
| 1147 | + | } | |
| 1148 | + | ||
| 1149 | + | if (!Action::configureEvent(node)) { | |
| 1150 | + | return false; | |
| 1151 | + | } | |
| 1152 | + | ||
| 1153 | + | spellType = SPELL_RUNE; | |
| 1154 | + | ||
| 1155 | + | pugi::xml_attribute attr; | |
| 1156 | + | if (!(attr = node.attribute("id"))) { | |
| 1157 | + | std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; | |
| 1158 | + | return false; | |
| 1159 | + | } | |
| 1160 | + | runeId = pugi::cast<uint16_t>(attr.value()); | |
| 1161 | + | ||
| 1162 | + | if ((attr = node.attribute("charges"))) { | |
| 1163 | + | charges = pugi::cast<uint32_t>(attr.value()); | |
| 1164 | + | } else { | |
| 1165 | + | charges = 0; | |
| 1166 | + | } | |
| 1167 | + | ||
| 1168 | + | hasCharges = (charges > 0); | |
| 1169 | + | if (magLevel != 0 || level != 0) { | |
| 1170 | + | //Change information in the ItemType to get accurate description | |
| 1171 | + | ItemType& iType = Item::items.getItemType(runeId); | |
| 1172 | + | iType.runeMagLevel = magLevel; | |
| 1173 | + | iType.runeLevel = level; | |
| 1174 | + | iType.charges = charges; | |
| 1175 | + | } | |
| 1176 | + | ||
| 1177 | + | return true; | |
| 1178 | + | } | |
| 1179 | + | ||
| 1180 | + | ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos) | |
| 1181 | + | { | |
| 1182 | + | if (player->hasFlag(PlayerFlag_CannotUseSpells)) { | |
| 1183 | + | return RETURNVALUE_CANNOTUSETHISOBJECT; | |
| 1184 | + | } | |
| 1185 | + | ||
| 1186 | + | ReturnValue ret = Action::canExecuteAction(player, toPos); | |
| 1187 | + | if (ret != RETURNVALUE_NOERROR) { | |
| 1188 | + | return ret; | |
| 1189 | + | } | |
| 1190 | + | ||
| 1191 | + | if (toPos.x == 0xFFFF) { | |
| 1192 | + | if (needTarget) { | |
| 1193 | + | return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES; | |
| 1194 | + | } else if (!selfTarget) { | |
| 1195 | + | return RETURNVALUE_NOTENOUGHROOM; | |
| 1196 | + | } | |
| 1197 | + | } | |
| 1198 | + | ||
| 1199 | + | return RETURNVALUE_NOERROR; | |
| 1200 | + | } | |
| 1201 | + | ||
| 1202 | + | bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey) | |
| 1203 | + | { | |
| 1204 | + | if (!playerRuneSpellCheck(player, toPosition)) { | |
| 1205 | + | return false; | |
| 1206 | + | } | |
| 1207 | + | ||
| 1208 | + | if (!scripted) { | |
| 1209 | + | return false; | |
| 1210 | + | } | |
| 1211 | + | ||
| 1212 | + | LuaVariant var; | |
| 1213 | + | ||
| 1214 | + | if (needTarget) { | |
| 1215 | + | var.type = VARIANT_NUMBER; | |
| 1216 | + | ||
| 1217 | + | if (target == nullptr) { | |
| 1218 | + | Tile* toTile = g_game.map.getTile(toPosition); | |
| 1219 | + | if (toTile) { | |
| 1220 | + | const Creature* visibleCreature = toTile->getBottomVisibleCreature(player); | |
| 1221 | + | if (visibleCreature) { | |
| 1222 | + | var.number = visibleCreature->getID(); | |
| 1223 | + | } | |
| 1224 | + | } | |
| 1225 | + | } else { | |
| 1226 | + | var.number = target->getCreature()->getID(); | |
| 1227 | + | } | |
| 1228 | + | } else { | |
| 1229 | + | var.type = VARIANT_POSITION; | |
| 1230 | + | var.pos = toPosition; | |
| 1231 | + | } | |
| 1232 | + | ||
| 1233 | + | if (!internalCastSpell(player, var, isHotkey)) { | |
| 1234 | + | return false; | |
| 1235 | + | } | |
| 1236 | + | ||
| 1237 | + | postCastSpell(player); | |
| 1238 | + | ||
| 1239 | + | target = g_game.getCreatureByID(var.number); | |
| 1240 | + | if (getPzLock() && target) { | |
| 1241 | + | player->onAttackedCreature(target->getCreature()); | |
| 1242 | + | } | |
| 1243 | + | ||
| 1244 | + | if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { | |
| 1245 | + | int32_t newCount = std::max<int32_t>(0, item->getItemCount() - 1); | |
| 1246 | + | g_game.transformItem(item, item->getID(), newCount); | |
| 1247 | + | } | |
| 1248 | + | return true; | |
| 1249 | + | } | |
| 1250 | + | ||
| 1251 | + | bool RuneSpell::castSpell(Creature* creature) | |
| 1252 | + | { | |
| 1253 | + | LuaVariant var; | |
| 1254 | + | var.type = VARIANT_NUMBER; | |
| 1255 | + | var.number = creature->getID(); | |
| 1256 | + | return internalCastSpell(creature, var, false); | |
| 1257 | + | } | |
| 1258 | + | ||
| 1259 | + | bool RuneSpell::castSpell(Creature* creature, Creature* target) | |
| 1260 | + | { | |
| 1261 | + | LuaVariant var; | |
| 1262 | + | var.type = VARIANT_NUMBER; | |
| 1263 | + | var.number = target->getID(); | |
| 1264 | + | return internalCastSpell(creature, var, false); | |
| 1265 | + | } | |
| 1266 | + | ||
| 1267 | + | bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) | |
| 1268 | + | { | |
| 1269 | + | bool result; | |
| 1270 | + | if (scripted) { | |
| 1271 | + | result = executeCastSpell(creature, var, isHotkey); | |
| 1272 | + | } else { | |
| 1273 | + | result = false; | |
| 1274 | + | } | |
| 1275 | + | return result; | |
| 1276 | + | } | |
| 1277 | + | ||
| 1278 | + | bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) | |
| 1279 | + | { | |
| 1280 | + | //onCastSpell(creature, var, isHotkey) | |
| 1281 | + | if (!scriptInterface->reserveScriptEnv()) { | |
| 1282 | + | std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl; | |
| 1283 | + | return false; | |
| 1284 | + | } | |
| 1285 | + | ||
| 1286 | + | ScriptEnvironment* env = scriptInterface->getScriptEnv(); | |
| 1287 | + | env->setScriptId(scriptId, scriptInterface); | |
| 1288 | + | ||
| 1289 | + | lua_State* L = scriptInterface->getLuaState(); | |
| 1290 | + | ||
| 1291 | + | scriptInterface->pushFunction(scriptId); | |
| 1292 | + | ||
| 1293 | + | LuaScriptInterface::pushUserdata<Creature>(L, creature); | |
| 1294 | + | LuaScriptInterface::setCreatureMetatable(L, -1, creature); | |
| 1295 | + | ||
| 1296 | + | LuaScriptInterface::pushVariant(L, var); | |
| 1297 | + | ||
| 1298 | + | LuaScriptInterface::pushBoolean(L, isHotkey); | |
| 1299 | + | ||
| 1300 | + | return scriptInterface->callFunction(3); | |
| 1301 | + | } |