diff options
Diffstat (limited to 'src')
120 files changed, 5854 insertions, 975 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 436620e2900..0428738f2dd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -71,7 +71,9 @@ target_link_libraries(common openssl valgrind threads - jemalloc) + jemalloc + PRIVATE + process) add_dependencies(common revision_data.h) diff --git a/src/common/Common.h b/src/common/Common.h index 8216c519746..aa04abacd30 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -44,6 +44,8 @@ #include <cerrno> #include <csignal> +#include <boost/optional.hpp> +#include <boost/utility/in_place_factory.hpp> #include <boost/functional/hash.hpp> #include "Debugging/Errors.h" @@ -152,6 +154,10 @@ typedef std::vector<std::string> StringVector; #define MAX_QUERY_LEN 32*1024 +//! Optional helper class to wrap optional values within. +template <typename T> +using Optional = boost::optional<T>; + namespace Trinity { //! std::make_unique implementation (TODO: remove this once C++14 is supported) diff --git a/src/common/Utilities/StartProcess.cpp b/src/common/Utilities/StartProcess.cpp new file mode 100644 index 00000000000..c47c02bbe87 --- /dev/null +++ b/src/common/Utilities/StartProcess.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "StartProcess.h" + +#include <atomic> +#include <thread> +#include <functional> + +#include <boost/algorithm/string/join.hpp> +#include <boost/iostreams/stream.hpp> +#include <boost/iostreams/copy.hpp> +#include <boost/iostreams/concepts.hpp> +#include <boost/iostreams/device/file_descriptor.hpp> +#include <boost/process.hpp> +#include <boost/system/system_error.hpp> + +#include "Common.h" +#include "Log.h" + +using namespace boost::process; +using namespace boost::process::initializers; +using namespace boost::iostreams; + +namespace Trinity { + +template<typename T> +class TCLogSink +{ + T callback_; + +public: + typedef char char_type; + typedef sink_tag category; + + // Requires a callback type which has a void(std::string) signature + TCLogSink(T callback) + : callback_(std::move(callback)) { } + + std::streamsize write(const char* str, std::streamsize size) + { + callback_(std::string(str, size)); + return size; + } +}; + +template<typename T> +auto MakeTCLogSink(T&& callback) + -> TCLogSink<typename std::decay<T>::type> +{ + return { std::forward<T>(callback) }; +} + +template<typename T> +static int CreateChildProcess(T waiter, std::string const& executable, + std::vector<std::string> const& args, + std::string const& logger, std::string const& input, + bool secure) +{ + auto outPipe = create_pipe(); + auto errPipe = create_pipe(); + + Optional<file_descriptor_source> inputSource; + + if (!secure) + { + TC_LOG_TRACE(logger.c_str(), "Starting process \"%s\" with arguments: \"%s\".", + executable.c_str(), boost::algorithm::join(args, " ").c_str()); + } + + // Start the child process + child c = [&] + { + if (!input.empty()) + { + inputSource = file_descriptor_source(input); + + // With binding stdin + return execute(run_exe(boost::filesystem::absolute(executable)), + set_args(args), + bind_stdin(*inputSource), + bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), + bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); + } + else + { + // Without binding stdin + return execute(run_exe(boost::filesystem::absolute(executable)), + set_args(args), + bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), + bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); + } + }(); + + file_descriptor_source outFd(outPipe.source, close_handle); + file_descriptor_source errFd(errPipe.source, close_handle); + + auto outInfo = MakeTCLogSink([&](std::string msg) + { + TC_LOG_INFO(logger.c_str(), "%s", msg.c_str()); + }); + + auto outError = MakeTCLogSink([&](std::string msg) + { + TC_LOG_ERROR(logger.c_str(), "%s", msg.c_str()); + }); + + copy(outFd, outInfo); + copy(errFd, outError); + + // Call the waiter in the current scope to prevent + // the streams from closing too early on leaving the scope. + int const result = waiter(c); + + if (!secure) + { + TC_LOG_TRACE(logger.c_str(), ">> Process \"%s\" finished with return value %i.", + executable.c_str(), result); + } + + if (inputSource) + inputSource->close(); + + return result; +} + +int StartProcess(std::string const& executable, std::vector<std::string> const& args, + std::string const& logger, std::string input_file, bool secure) +{ + return CreateChildProcess([](child& c) -> int + { + try + { + return wait_for_exit(c); + } + catch (...) + { + return EXIT_FAILURE; + } + }, executable, args, logger, input_file, secure); +} + +class AsyncProcessResultImplementation + : public AsyncProcessResult +{ + std::string const executable; + std::vector<std::string> const args; + std::string const logger; + std::string const input_file; + bool const is_secure; + + std::atomic<bool> was_terminated; + + // Workaround for missing move support in boost < 1.57 + Optional<std::shared_ptr<std::future<int>>> result; + Optional<std::reference_wrapper<child>> my_child; + +public: + explicit AsyncProcessResultImplementation(std::string executable_, std::vector<std::string> args_, + std::string logger_, std::string input_file_, + bool secure) + : executable(std::move(executable_)), args(std::move(args_)), + logger(std::move(logger_)), input_file(input_file_), + is_secure(secure), was_terminated(false) { } + + AsyncProcessResultImplementation(AsyncProcessResultImplementation const&) = delete; + AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation const&) = delete; + AsyncProcessResultImplementation(AsyncProcessResultImplementation&&) = delete; + AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation&&) = delete; + + int StartProcess() + { + ASSERT(!my_child, "Process started already!"); + + return CreateChildProcess([&](child& c) -> int + { + int result; + my_child = std::reference_wrapper<child>(c); + + try + { + result = wait_for_exit(c); + } + catch (...) + { + result = EXIT_FAILURE; + } + + my_child.reset(); + return was_terminated ? EXIT_FAILURE : result; + + }, executable, args, logger, input_file, is_secure); + } + + void SetFuture(std::future<int> result_) + { + result = std::make_shared<std::future<int>>(std::move(result_)); + } + + /// Returns the future which contains the result of the process + /// as soon it is finished. + std::future<int>& GetFutureResult() override + { + ASSERT(*result, "The process wasn't started!"); + return **result; + } + + /// Tries to terminate the process + void Terminate() override + { + if (!my_child) + { + was_terminated = true; + try + { + terminate(my_child->get()); + } + catch(...) + { + // Do nothing + } + } + } +}; + +TC_COMMON_API std::shared_ptr<AsyncProcessResult> + StartAsyncProcess(std::string executable, std::vector<std::string> args, + std::string logger, std::string input_file, bool secure) +{ + auto handle = std::make_shared<AsyncProcessResultImplementation>( + std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure); + + handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); })); + return handle; +} + +Optional<std::string> SearchExecutableInPath(std::string const& filename) +{ + try + { + auto result = search_path(filename); + if (result.empty()) + return boost::none; + else + return result; + } + catch (...) + { + return boost::none; + } +} + +} // namespace Trinity diff --git a/src/common/Utilities/StartProcess.h b/src/common/Utilities/StartProcess.h new file mode 100644 index 00000000000..3b380bd4f4e --- /dev/null +++ b/src/common/Utilities/StartProcess.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef Process_h__ +#define Process_h__ + +#include <future> +#include <memory> +#include "Common.h" + +namespace Trinity { + +/// Starts a process with the given arguments and parameters and will block +/// until the process is finished. +/// When an input path is given, the file will be routed to the processes stdin. +/// When the process is marked as secure no arguments are leaked to logs. +/// Note that most executables expect it's name as the first argument. +TC_COMMON_API int StartProcess(std::string const& executable, std::vector<std::string> const& args, + std::string const& logger, std::string input_file = "", + bool secure = false); + +/// Platform and library independent representation +/// of asynchronous process results +class AsyncProcessResult +{ +public: + virtual ~AsyncProcessResult() { } + + /// Returns the future which contains the result of the process + /// as soon it is finished. + virtual std::future<int>& GetFutureResult() = 0; + + /// Tries to terminate the process + virtual void Terminate() = 0; +}; + +/// Starts a process asynchronously with the given arguments and parameters and +/// returns an AsyncProcessResult immediately which is set, when the process exits. +/// When an input path is given, the file will be routed to the processes stdin. +/// When the process is marked as secure no arguments are leaked to logs. +/// Note that most executables expect it's name as the first argument. +TC_COMMON_API std::shared_ptr<AsyncProcessResult> + StartAsyncProcess(std::string executable, std::vector<std::string> args, + std::string logger, std::string input_file = "", + bool secure = false); + +/// Searches for the given executable in the PATH variable +/// and returns a present optional when it was found. +TC_COMMON_API Optional<std::string> SearchExecutableInPath(std::string const& filename); + +} // namespace Trinity + +#endif // Process_h__ diff --git a/src/server/database/CMakeLists.txt b/src/server/database/CMakeLists.txt index 236586075ee..bd2fa280ad6 100644 --- a/src/server/database/CMakeLists.txt +++ b/src/server/database/CMakeLists.txt @@ -52,7 +52,6 @@ add_definitions(-DTRINITY_API_EXPORT_DATABASE) target_link_libraries(database PUBLIC common - process mysql) set_target_properties(database diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index 5f5b519d2b3..3be81e85715 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -22,19 +22,11 @@ #include "DatabaseLoader.h" #include "Config.h" #include "BuiltInConfig.h" +#include "StartProcess.h" #include <fstream> #include <iostream> #include <unordered_map> -#include <boost/process.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/iostreams/copy.hpp> -#include <boost/iostreams/device/file_descriptor.hpp> -#include <boost/system/system_error.hpp> - -using namespace boost::process; -using namespace boost::process::initializers; -using namespace boost::iostreams; std::string DBUpdaterUtil::GetCorrectedMySQLExecutable() { @@ -51,19 +43,16 @@ bool DBUpdaterUtil::CheckExecutable() { exe.clear(); - try - { - exe = search_path("mysql"); - } - catch (std::runtime_error&) + if (auto path = Trinity::SearchExecutableInPath("mysql")) { - } + exe = std::move(*path); - if (!exe.empty() && exists(exe)) - { - // Correct the path to the cli - corrected_path() = absolute(exe).generic_string(); - return true; + if (!exe.empty() && exists(exe)) + { + // Correct the path to the cli + corrected_path() = absolute(exe).generic_string(); + return true; + } } TC_LOG_FATAL("sql.updates", "Didn't find executeable mysql binary at \'%s\' or in path, correct the path in the *.conf (\"Updates.MySqlCLIPath\").", @@ -387,44 +376,9 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos if (!database.empty()) args.push_back(database); - // ToDo: use the existing query in memory as virtual file if possible - file_descriptor_source source(path); - - uint32 ret; - try - { - boost::process::pipe outPipe = create_pipe(); - boost::process::pipe errPipe = create_pipe(); - - child c = execute(run_exe( - boost::filesystem::absolute(DBUpdaterUtil::GetCorrectedMySQLExecutable()).generic_string()), - set_args(args), bind_stdin(source), throw_on_error(), - bind_stdout(file_descriptor_sink(outPipe.sink, close_handle)), - bind_stderr(file_descriptor_sink(errPipe.sink, close_handle))); - - file_descriptor_source mysqlOutfd(outPipe.source, close_handle); - file_descriptor_source mysqlErrfd(errPipe.source, close_handle); - - stream<file_descriptor_source> mysqlOutStream(mysqlOutfd); - stream<file_descriptor_source> mysqlErrStream(mysqlErrfd); - - std::stringstream out; - std::stringstream err; - - copy(mysqlOutStream, out); - copy(mysqlErrStream, err); - - TC_LOG_INFO("sql.updates", "%s", out.str().c_str()); - TC_LOG_ERROR("sql.updates", "%s", err.str().c_str()); - - ret = wait_for_exit(c); - } - catch (boost::system::system_error&) - { - ret = EXIT_FAILURE; - } - - source.close(); + // Invokes a mysql process which doesn't leak credentials to logs + int const ret = Trinity::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), args, + "sql.updates", path.generic_string(), true); if (ret != EXIT_SUCCESS) { diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index bbf4f4e02ce..68554722db6 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -245,9 +245,9 @@ void PetAI::UpdateAI(uint32 diff) } // Update speed as needed to prevent dropping too far behind and despawning - me->UpdateSpeed(MOVE_RUN, true); - me->UpdateSpeed(MOVE_WALK, true); - me->UpdateSpeed(MOVE_FLIGHT, true); + me->UpdateSpeed(MOVE_RUN); + me->UpdateSpeed(MOVE_WALK); + me->UpdateSpeed(MOVE_FLIGHT); } diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index ebc814405bf..87f5ff6ce5c 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -1533,7 +1533,7 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) //! Since no common attributes were found, (not even in titleRewardFlags field) //! we explicitly check by ID. Maybe in the future we could move the achievement_reward //! condition fields to the condition system. - if (uint32 titleId = reward->titleId[achievement->ID == 1793 ? GetPlayer()->GetByteValue(PLAYER_BYTES_3, 0) : (GetPlayer()->GetTeam() == ALLIANCE ? 0 : 1)]) + if (uint32 titleId = reward->titleId[achievement->ID == 1793 ? GetPlayer()->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER) : (GetPlayer()->GetTeam() == ALLIANCE ? 0 : 1)]) if (CharTitlesEntry const* titleEntry = sCharTitlesStore.LookupEntry(titleId)) GetPlayer()->SetTitle(titleEntry); diff --git a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp index 5acb56b5173..fd105c5b25f 100644 --- a/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp +++ b/src/server/game/AuctionHouseBot/AuctionHouseBotSeller.cpp @@ -582,13 +582,13 @@ void AuctionBotSeller::LoadSellerValues(SellerConfiguration& config) break; } - config.SetPriceRatioPerQuality(AUCTION_QUALITY_GRAY, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_GRAY_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_WHITE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_WHITE_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_GREEN, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_GREEN_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_BLUE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_BLUE_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_PURPLE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_PURPLE_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_ORANGE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_ORANGE_PRICE_RATIO) / 100); - config.SetPriceRatioPerQuality(AUCTION_QUALITY_YELLOW, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_YELLOW_PRICE_RATIO) / 100); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_GRAY, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_GRAY_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_WHITE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_WHITE_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_GREEN, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_GREEN_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_BLUE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_BLUE_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_PURPLE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_PURPLE_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_ORANGE, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_ORANGE_PRICE_RATIO)); + config.SetPriceRatioPerQuality(AUCTION_QUALITY_YELLOW, PriceRatio * sAuctionBotConfig->GetConfig(CONFIG_AHBOT_ITEM_YELLOW_PRICE_RATIO)); config.SetPriceRatioPerClass(ITEM_CLASS_CONSUMABLE, sAuctionBotConfig->GetConfig(CONFIG_AHBOT_CLASS_CONSUMABLE_PRICE_RATIO)); config.SetPriceRatioPerClass(ITEM_CLASS_CONTAINER, sAuctionBotConfig->GetConfig(CONFIG_AHBOT_CLASS_CONTAINER_PRICE_RATIO)); @@ -701,10 +701,10 @@ void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConf { uint32 classRatio = config.GetPriceRatioPerClass(ItemClass(itemProto->Class)); uint32 qualityRatio = config.GetPriceRatioPerQuality(AuctionQuality(itemProto->Quality)); - uint32 priceRatio = (classRatio * qualityRatio) / 100; + float priceRatio = (classRatio * qualityRatio) / 10000.0f; - uint32 buyPrice = itemProto->BuyPrice; - uint32 sellPrice = itemProto->SellPrice; + float buyPrice = itemProto->BuyPrice; + float sellPrice = itemProto->SellPrice; if (buyPrice == 0) { @@ -712,11 +712,11 @@ void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConf buyPrice = sellPrice * GetSellModifier(itemProto); else { - uint32 divisor = ((itemProto->Class == 2 || itemProto->Class == 4) ? 284 : 80); - uint32 tempLevel = (itemProto->ItemLevel == 0 ? 1 : itemProto->ItemLevel); - uint32 tempQuality = (itemProto->Quality == 0 ? 1 : itemProto->Quality); + float divisor = ((itemProto->Class == ITEM_CLASS_WEAPON || itemProto->Class == ITEM_CLASS_ARMOR) ? 284.0f : 80.0f); + float tempLevel = (itemProto->ItemLevel == 0 ? 1.0f : itemProto->ItemLevel); + float tempQuality = (itemProto->Quality == 0 ? 1.0f : itemProto->Quality); - buyPrice = tempLevel * tempQuality * GetBuyModifier(itemProto)* tempLevel / divisor; + buyPrice = tempLevel * tempQuality * static_cast<float>(GetBuyModifier(itemProto))* tempLevel / divisor; } } @@ -725,15 +725,16 @@ void AuctionBotSeller::SetPricesOfItem(ItemTemplate const* itemProto, SellerConf if (sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_SELLER)) buyPrice = sellPrice; - - uint32 basePrice = (buyPrice * stackCount * priceRatio) / (itemProto->Class == 6 ? 200 : itemProto->BuyCount) / 100; - uint32 range = basePrice * 0.04; - - buyp = urand(basePrice - range, basePrice + range) + 1; - - basePrice = buyp * .5; + + float basePriceFloat = (buyPrice * stackCount * priceRatio) / (itemProto->Class == 6 ? 200.0f : static_cast<float>(itemProto->BuyCount)) / 100.0f; + float range = basePriceFloat * 0.04f; + + buyp = static_cast<uint32>(frand(basePriceFloat - range, basePriceFloat + range) + 0.5f); + if (buyp == 0) + buyp = 1; + uint32 basePrice = buyp * .5; range = buyp * .4; - bidp = urand(basePrice - range, basePrice + range) + 1; + bidp = urand(static_cast<uint32>(basePrice - range + 0.5f), static_cast<uint32>(basePrice + range + 0.5f)) + 1; } // Determines the stack size to use for the item diff --git a/src/server/game/CMakeLists.txt b/src/server/game/CMakeLists.txt index 60e86093f89..239e59d6dbb 100644 --- a/src/server/game/CMakeLists.txt +++ b/src/server/game/CMakeLists.txt @@ -23,27 +23,36 @@ GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) add_definitions(-DTRINITY_API_EXPORT_GAME) -add_library(game - ${PRIVATE_PCH_SOURCE} - ${PRIVATE_SOURCES} -) - CollectIncludeDirectories( ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC_INCLUDES # Exclude ${CMAKE_CURRENT_SOURCE_DIR}/PrecompiledHeaders) +# Provide an interface target for the game project to allow +# dependent projects to build meanwhile. +add_library(game-interface INTERFACE) + +target_include_directories(game-interface + INTERFACE + ${PUBLIC_INCLUDES}) + +target_link_libraries(game-interface + INTERFACE + shared + Detour) + +add_library(game + ${PRIVATE_PCH_SOURCE} + ${PRIVATE_SOURCES}) + target_include_directories(game - PUBLIC - ${PUBLIC_INCLUDES} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(game PUBLIC - shared - Detour + game-interface PRIVATE efsw) diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index f0e98ee8c1f..ec90a5f7efb 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -32,18 +32,19 @@ #include "ScriptMgr.h" #include "ChatLink.h" -bool ChatHandler::load_command_table = true; +// Lazy loading of the command table cache from commands and the +// ScriptMgr should be thread safe since the player commands, +// cli commands and ScriptMgr updates are all dispatched one after +// one inside the world update loop. +static Optional<std::vector<ChatCommand>> commandTableCache; std::vector<ChatCommand> const& ChatHandler::getCommandTable() { - static std::vector<ChatCommand> commandTableCache; - - if (LoadCommandTable()) + if (!commandTableCache) { - SetLoadCommandTable(false); - - std::vector<ChatCommand> cmds = sScriptMgr->GetChatCommands(); - commandTableCache.swap(cmds); + // We need to initialize this at top since SetDataForCommandInTable + // calls getCommandTable() recursively. + commandTableCache = sScriptMgr->GetChatCommands(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS); PreparedQueryResult result = WorldDatabase.Query(stmt); @@ -54,13 +55,18 @@ std::vector<ChatCommand> const& ChatHandler::getCommandTable() Field* fields = result->Fetch(); std::string name = fields[0].GetString(); - SetDataForCommandInTable(commandTableCache, name.c_str(), fields[1].GetUInt16(), fields[2].GetString(), name); + SetDataForCommandInTable(*commandTableCache, name.c_str(), fields[1].GetUInt16(), fields[2].GetString(), name); } while (result->NextRow()); } } - return commandTableCache; + return *commandTableCache; +} + +void ChatHandler::invalidateCommandTable() +{ + commandTableCache.reset(); } char const* ChatHandler::GetTrinityString(uint32 entry) const diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index a061ff63b00..1c9368275ad 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -96,6 +96,7 @@ class TC_GAME_API ChatHandler bool ParseCommands(const char* text); static std::vector<ChatCommand> const& getCommandTable(); + static void invalidateCommandTable(); bool isValidChatMessage(const char* msg); void SendGlobalSysMessage(const char *str); @@ -143,8 +144,6 @@ class TC_GAME_API ChatHandler GameObject* GetObjectGlobalyWithGuidOrNearWithDbGuid(ObjectGuid::LowType lowguid, uint32 entry); bool HasSentErrorMessage() const { return sentErrorMessage; } void SetSentErrorMessage(bool val){ sentErrorMessage = val; } - static bool LoadCommandTable() { return load_command_table; } - static void SetLoadCommandTable(bool val) { load_command_table = val; } bool ShowHelpForCommand(std::vector<ChatCommand> const& table, const char* cmd); protected: @@ -157,7 +156,6 @@ class TC_GAME_API ChatHandler WorldSession* m_session; // != NULL for chat command call and NULL for CLI command // common global flag - static bool load_command_table; bool sentErrorMessage; }; diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 0e9b50d64c5..5ede70da2a3 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -27,6 +27,9 @@ #include <boost/regex.hpp> #include <map> +#include <fstream> +#include <iostream> +#include <iomanip> typedef std::map<uint16, uint32> AreaFlagByAreaID; typedef std::map<uint32, uint32> AreaFlagByMapID; @@ -73,6 +76,7 @@ DBCStorage <CharTitlesEntry> sCharTitlesStore(CharTitlesEntryfmt); DBCStorage <ChatChannelsEntry> sChatChannelsStore(ChatChannelsEntryfmt); DBCStorage <ChrClassesEntry> sChrClassesStore(ChrClassesEntryfmt); DBCStorage <ChrRacesEntry> sChrRacesStore(ChrRacesEntryfmt); +DBCStorage <CinematicCameraEntry> sCinematicCameraStore(CinematicCameraEntryfmt); DBCStorage <CinematicSequencesEntry> sCinematicSequencesStore(CinematicSequencesEntryfmt); DBCStorage <CreatureDisplayInfoEntry> sCreatureDisplayInfoStore(CreatureDisplayInfofmt); DBCStorage <CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore(CreatureDisplayInfoExtrafmt); @@ -217,6 +221,8 @@ DBCStorage <WorldMapAreaEntry> sWorldMapAreaStore(WorldMapAreaEntryfmt); DBCStorage <WorldMapOverlayEntry> sWorldMapOverlayStore(WorldMapOverlayEntryfmt); DBCStorage <WorldSafeLocsEntry> sWorldSafeLocsStore(WorldSafeLocsEntryfmt); +std::unordered_map<uint32, FlyByCameraCollection> sFlyByCameraStore; + typedef std::list<std::string> StoreProblemList; uint32 DBCFileCount = 0; @@ -311,6 +317,7 @@ void LoadDBCStores(const std::string& dataPath) LoadDBC(availableDbcLocales, bad_dbc_files, sChatChannelsStore, dbcPath, "ChatChannels.dbc"); LoadDBC(availableDbcLocales, bad_dbc_files, sChrClassesStore, dbcPath, "ChrClasses.dbc"); LoadDBC(availableDbcLocales, bad_dbc_files, sChrRacesStore, dbcPath, "ChrRaces.dbc"); + LoadDBC(availableDbcLocales, bad_dbc_files, sCinematicCameraStore, dbcPath, "CinematicCamera.dbc"); LoadDBC(availableDbcLocales, bad_dbc_files, sCinematicSequencesStore, dbcPath, "CinematicSequences.dbc"); LoadDBC(availableDbcLocales, bad_dbc_files, sCreatureDisplayInfoStore, dbcPath, "CreatureDisplayInfo.dbc"); LoadDBC(availableDbcLocales, bad_dbc_files, sCreatureDisplayInfoExtraStore, dbcPath, "CreatureDisplayInfoExtra.dbc"); @@ -717,10 +724,250 @@ void LoadDBCStores(const std::string& dataPath) exit(1); } + LoadM2Cameras(dataPath); + TC_LOG_INFO("server.loading", ">> Initialized %d data stores in %u ms", DBCFileCount, GetMSTimeDiffToNow(oldMSTime)); } +// Convert the geomoetry from a spline value, to an actual WoW XYZ +G3D::Vector3 TranslateLocation(G3D::Vector4 const* DBCPosition, G3D::Vector3 const* basePosition, G3D::Vector3 const* splineVector) +{ + G3D::Vector3 work; + float x = basePosition->x + splineVector->x; + float y = basePosition->y + splineVector->y; + float z = basePosition->z + splineVector->z; + float const distance = sqrt((x * x) + (y * y)); + float angle = std::atan2(x, y) - DBCPosition->w; + + if (angle < 0) + angle += 2 * float(M_PI); + + work.x = DBCPosition->x + (distance * sin(angle)); + work.y = DBCPosition->y + (distance * cos(angle)); + work.z = DBCPosition->z + z; + return work; +} + +// Number of cameras not used. Multiple cameras never used in 3.3.5 +bool readCamera(M2Camera const* cam, uint32 buffSize, M2Header const* header, CinematicCameraEntry const* dbcentry) +{ + char const* buffer = reinterpret_cast<char const*>(header); + + FlyByCameraCollection cameras; + FlyByCameraCollection targetcam; + + G3D::Vector4 DBCData; + DBCData.x = dbcentry->base_x; + DBCData.y = dbcentry->base_y; + DBCData.z = dbcentry->base_z; + DBCData.w = dbcentry->base_o; + + // Read target locations, only so that we can calculate orientation + for (uint32 k = 0; k < cam->target_positions.timestamps.number; ++k) + { + // Extract Target positions + if (cam->target_positions.timestamps.offset_elements + sizeof(M2Array) > buffSize) + return false; + M2Array const* targTsArray = reinterpret_cast<M2Array const*>(buffer + cam->target_positions.timestamps.offset_elements); + if (targTsArray->offset_elements + sizeof(uint32) > buffSize || cam->target_positions.values.offset_elements + sizeof(M2Array) > buffSize) + return false; + uint32 const* targTimestamps = reinterpret_cast<uint32 const*>(buffer + targTsArray->offset_elements); + M2Array const* targArray = reinterpret_cast<M2Array const*>(buffer + cam->target_positions.values.offset_elements); + + if (targArray->offset_elements + sizeof(M2SplineKey<G3D::Vector3>) > buffSize) + return false; + M2SplineKey<G3D::Vector3> const* targPositions = reinterpret_cast<M2SplineKey<G3D::Vector3> const*>(buffer + targArray->offset_elements); + + // Read the data for this set + uint32 currPos = targArray->offset_elements; + for (uint32 i = 0; i < targTsArray->number; ++i) + { + if (currPos + sizeof(M2SplineKey<G3D::Vector3>) > buffSize) + return false; + // Translate co-ordinates + G3D::Vector3 newPos = TranslateLocation(&DBCData, &cam->target_position_base, &targPositions->p0); + + // Add to vector + FlyByCamera thisCam; + thisCam.timeStamp = targTimestamps[i]; + thisCam.locations.x = newPos.x; + thisCam.locations.y = newPos.y; + thisCam.locations.z = newPos.z; + thisCam.locations.w = 0.0f; + targetcam.push_back(thisCam); + targPositions++; + currPos += sizeof(M2SplineKey<G3D::Vector3>); + } + } + + // Read camera positions and timestamps (translating first position of 3 only, we don't need to translate the whole spline) + for (uint32 k = 0; k < cam->positions.timestamps.number; ++k) + { + // Extract Camera positions for this set + if (cam->positions.timestamps.offset_elements + sizeof(M2Array) > buffSize) + return false; + M2Array const* posTsArray = reinterpret_cast<M2Array const*>(buffer + cam->positions.timestamps.offset_elements); + if (posTsArray->offset_elements + sizeof(uint32) > buffSize || cam->positions.values.offset_elements + sizeof(M2Array) > buffSize) + return false; + uint32 const* posTimestamps = reinterpret_cast<uint32 const*>(buffer + posTsArray->offset_elements); + M2Array const* posArray = reinterpret_cast<M2Array const*>(buffer + cam->positions.values.offset_elements); + if (posArray->offset_elements + sizeof(M2SplineKey<G3D::Vector3>) > buffSize) + return false; + M2SplineKey<G3D::Vector3> const* positions = reinterpret_cast<M2SplineKey<G3D::Vector3> const*>(buffer + posArray->offset_elements); + + // Read the data for this set + uint32 currPos = posArray->offset_elements; + for (uint32 i = 0; i < posTsArray->number; ++i) + { + if (currPos + sizeof(M2SplineKey<G3D::Vector3>) > buffSize) + return false; + // Translate co-ordinates + G3D::Vector3 newPos = TranslateLocation(&DBCData, &cam->position_base, &positions->p0); + + // Add to vector + FlyByCamera thisCam; + thisCam.timeStamp = posTimestamps[i]; + thisCam.locations.x = newPos.x; + thisCam.locations.y = newPos.y; + thisCam.locations.z = newPos.z; + + if (targetcam.size() > 0) + { + // Find the target camera before and after this camera + FlyByCamera lastTarget; + FlyByCamera nextTarget; + + // Pre-load first item + lastTarget = targetcam[0]; + nextTarget = targetcam[0]; + for (uint32 j = 0; j < targetcam.size(); ++j) + { + nextTarget = targetcam[j]; + if (targetcam[j].timeStamp > posTimestamps[i]) + break; + + lastTarget = targetcam[j]; + } + + float x = lastTarget.locations.x; + float y = lastTarget.locations.y; + float z = lastTarget.locations.z; + + // Now, the timestamps for target cam and position can be different. So, if they differ we interpolate + if (lastTarget.timeStamp != posTimestamps[i]) + { + uint32 timeDiffTarget = nextTarget.timeStamp - lastTarget.timeStamp; + uint32 timeDiffThis = posTimestamps[i] - lastTarget.timeStamp; + float xDiff = nextTarget.locations.x - lastTarget.locations.x; + float yDiff = nextTarget.locations.y - lastTarget.locations.y; + float zDiff = nextTarget.locations.z - lastTarget.locations.z; + x = lastTarget.locations.x + (xDiff * (float(timeDiffThis) / float(timeDiffTarget))); + y = lastTarget.locations.y + (yDiff * (float(timeDiffThis) / float(timeDiffTarget))); + z = lastTarget.locations.z + (zDiff * (float(timeDiffThis) / float(timeDiffTarget))); + } + float xDiff = x - thisCam.locations.x; + float yDiff = y - thisCam.locations.y; + thisCam.locations.w = std::atan2(yDiff, xDiff); + + if (thisCam.locations.w < 0) + thisCam.locations.w += 2 * float(M_PI); + } + + cameras.push_back(thisCam); + positions++; + currPos += sizeof(M2SplineKey<G3D::Vector3>); + } + } + + sFlyByCameraStore[dbcentry->id] = cameras; + return true; +} + +void LoadM2Cameras(const std::string& dataPath) +{ + sFlyByCameraStore.clear(); + TC_LOG_INFO("server.loading", ">> Loading Cinematic Camera files"); + + uint32 oldMSTime = getMSTime(); + for (uint32 i = 0; i < sCinematicCameraStore.GetNumRows(); ++i) + { + if (CinematicCameraEntry const* dbcentry = sCinematicCameraStore.LookupEntry(i)) + { + std::string filename = dataPath.c_str(); + filename.append(dbcentry->filename); + + // Replace slashes + size_t loc = filename.find("\\"); + while (loc != std::string::npos) + { + filename.replace(loc, 1, "/"); + loc = filename.find("\\"); + } + + // Replace mdx to .m2 + loc = filename.find(".mdx"); + if (loc != std::string::npos) + filename.replace(loc, 4, ".m2"); + + std::ifstream m2file(filename.c_str(), std::ios::in | std::ios::binary); + if (!m2file.is_open()) + continue; + + // Get file size + m2file.seekg(0, std::ios::end); + std::streamoff const fileSize = m2file.tellg(); + + // Reject if not at least the size of the header + if (static_cast<uint32 const>(fileSize) < sizeof(M2Header)) + { + TC_LOG_ERROR("server.loading", "Camera file %s is damaged. File is smaller than header size", filename.c_str()); + m2file.close(); + continue; + } + + // Read 4 bytes (signature) + m2file.seekg(0, std::ios::beg); + char fileCheck[5]; + m2file.read(fileCheck, 4); + fileCheck[4] = 0; + + // Check file has correct magic (MD20) + if (strcmp(fileCheck, "MD20")) + { + TC_LOG_ERROR("server.loading", "Camera file %s is damaged. File identifier not found", filename.c_str()); + m2file.close(); + continue; + } + + // Now we have a good file, read it all into a vector of char's, then close the file. + std::vector<char> buffer(fileSize); + m2file.seekg(0, std::ios::beg); + if (!m2file.read(buffer.data(), fileSize)) + { + m2file.close(); + continue; + } + m2file.close(); + + // Read header + M2Header const* header = reinterpret_cast<M2Header const*>(buffer.data()); + + if (header->ofsCameras + sizeof(M2Camera) > static_cast<uint32 const>(fileSize)) + { + TC_LOG_ERROR("server.loading", "Camera file %s is damaged. Camera references position beyond file end", filename.c_str()); + continue; + } + + // Get camera(s) - Main header, then dump them. + M2Camera const* cam = reinterpret_cast<M2Camera const*>(buffer.data() + header->ofsCameras); + if (!readCamera(cam, fileSize, header, dbcentry)) + TC_LOG_ERROR("server.loading", "Camera file %s is damaged. Camera references position beyond file end", filename.c_str()); + } + } + TC_LOG_INFO("server.loading", ">> Loaded %u cinematic waypoint sets in %u ms", (uint32)sFlyByCameraStore.size(), GetMSTimeDiffToNow(oldMSTime)); +} + SimpleFactionsList const* GetFactionTeamList(uint32 faction) { FactionTeamMap::const_iterator itr = sFactionTeamMap.find(faction); diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index beb0bb514cc..6cad13fdf9a 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -27,6 +27,8 @@ #include <list> typedef std::list<uint32> SimpleFactionsList; +typedef std::vector<FlyByCamera> FlyByCameraCollection; + TC_GAME_API SimpleFactionsList const* GetFactionTeamList(uint32 faction); TC_GAME_API char* GetPetName(uint32 petfamily, uint32 dbclang); @@ -96,6 +98,7 @@ TC_GAME_API extern DBCStorage <CharSectionsEntry> sCharSectionsStore; TC_GAME_API extern DBCStorage <CharTitlesEntry> sCharTitlesStore; TC_GAME_API extern DBCStorage <ChrClassesEntry> sChrClassesStore; TC_GAME_API extern DBCStorage <ChrRacesEntry> sChrRacesStore; +TC_GAME_API extern DBCStorage <CinematicCameraEntry> sCinematicCameraStore; TC_GAME_API extern DBCStorage <CinematicSequencesEntry> sCinematicSequencesStore; TC_GAME_API extern DBCStorage <CreatureDisplayInfoEntry> sCreatureDisplayInfoStore; TC_GAME_API extern DBCStorage <CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore; @@ -193,7 +196,9 @@ TC_GAME_API extern DBCStorage <WMOAreaTableEntry> sWMOAreaTableStore; //TC_GAME_API extern DBCStorage <WorldMapAreaEntry> sWorldMapAreaStore; -- use Zone2MapCoordinates and Map2ZoneCoordinates TC_GAME_API extern DBCStorage <WorldMapOverlayEntry> sWorldMapOverlayStore; TC_GAME_API extern DBCStorage <WorldSafeLocsEntry> sWorldSafeLocsStore; +TC_GAME_API extern std::unordered_map<uint32, FlyByCameraCollection> sFlyByCameraStore; TC_GAME_API void LoadDBCStores(const std::string& dataPath); +TC_GAME_API void LoadM2Cameras(const std::string& dataPath); #endif diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h index b5dc4489148..51b3dcbd38b 100644 --- a/src/server/game/DataStores/DBCStructure.h +++ b/src/server/game/DataStores/DBCStructure.h @@ -725,24 +725,22 @@ struct ChrRacesEntry uint32 expansion; // 68 (0 - original race, 1 - tbc addon, ...) }; -/* not used struct CinematicCameraEntry { uint32 id; // 0 index char* filename; // 1 uint32 soundid; // 2 in SoundEntries.dbc or 0 - float start_x; // 3 - float start_y; // 4 - float start_z; // 5 - float unk6; // 6 speed? + float base_x; // 3 + float base_y; // 4 + float base_z; // 5 + float base_o; // 6 }; -*/ struct CinematicSequencesEntry { uint32 Id; // 0 index //uint32 unk1; // 1 always 0 - //uint32 cinematicCamera; // 2 id in CinematicCamera.dbc + uint32 cinematicCamera; // 2 id in CinematicCamera.dbc // 3-9 always 0 }; diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h index c61ec997bc2..1accc81714b 100644 --- a/src/server/game/DataStores/DBCfmt.h +++ b/src/server/game/DataStores/DBCfmt.h @@ -38,7 +38,8 @@ char const CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi"; char const ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx"; char const ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii"; char const ChrRacesEntryfmt[] = "niixiixixxxxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxi"; -char const CinematicSequencesEntryfmt[] = "nxxxxxxxxx"; +char const CinematicCameraEntryfmt[] = "nsiffff"; +char const CinematicSequencesEntryfmt[] = "nxixxxxxxx"; char const CreatureDisplayInfofmt[] = "nixifxxxxxxxxxxx"; char const CreatureDisplayInfoExtrafmt[] = "diixxxxxxxxxxxxxxxxxx"; char const CreatureFamilyfmt[] = "nfifiiiiixssssssssssssssssxx"; diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 02fc69b8365..e72859c23e7 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -40,8 +40,6 @@ namespace lfg LFGMgr::LFGMgr(): m_QueueTimer(0), m_lfgProposalId(1), m_options(sWorld->getIntConfig(CONFIG_LFG_OPTIONSMASK)) { - new LFGPlayerScript(); - new LFGGroupScript(); } LFGMgr::~LFGMgr() diff --git a/src/server/game/DungeonFinding/LFGScripts.cpp b/src/server/game/DungeonFinding/LFGScripts.cpp index 79d36055870..1d2b963b254 100644 --- a/src/server/game/DungeonFinding/LFGScripts.cpp +++ b/src/server/game/DungeonFinding/LFGScripts.cpp @@ -241,4 +241,10 @@ void LFGGroupScript::OnInviteMember(Group* group, ObjectGuid guid) sLFGMgr->LeaveLfg(leader); } +void AddSC_LFGScripts() +{ + new LFGPlayerScript(); + new LFGGroupScript(); +} + } // namespace lfg diff --git a/src/server/game/DungeonFinding/LFGScripts.h b/src/server/game/DungeonFinding/LFGScripts.h index ec64604a282..9f52668ea61 100644 --- a/src/server/game/DungeonFinding/LFGScripts.h +++ b/src/server/game/DungeonFinding/LFGScripts.h @@ -53,4 +53,6 @@ class TC_GAME_API LFGGroupScript : public GroupScript void OnInviteMember(Group* group, ObjectGuid guid) override; }; +/*keep private*/ void AddSC_LFGScripts(); + } // namespace lfg diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 61d5d7d9459..d3843c86aa4 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -342,10 +342,10 @@ bool Creature::InitEntry(uint32 entry, CreatureData const* data /*= nullptr*/) m_creatureInfo = cinfo; // map mode related always // equal to player Race field, but creature does not have race - SetByteValue(UNIT_FIELD_BYTES_0, 0, 0); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE, 0); // known valid are: CLASS_WARRIOR, CLASS_PALADIN, CLASS_ROGUE, CLASS_MAGE - SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class)); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(cinfo->unit_class)); // Cancel load if no model defined if (!(cinfo->GetFirstValidModelId())) @@ -364,7 +364,7 @@ bool Creature::InitEntry(uint32 entry, CreatureData const* data /*= nullptr*/) SetDisplayId(displayID); SetNativeDisplayId(displayID); - SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); // Load creature equipment if (data && data->equipmentId != 0) @@ -379,10 +379,10 @@ bool Creature::InitEntry(uint32 entry, CreatureData const* data /*= nullptr*/) SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f); - SetSpeed(MOVE_WALK, cinfo->speed_walk); - SetSpeed(MOVE_RUN, cinfo->speed_run); - SetSpeed(MOVE_SWIM, 1.0f); // using 1.0 rate - SetSpeed(MOVE_FLIGHT, 1.0f); // using 1.0 rate + SetSpeedRate(MOVE_WALK, cinfo->speed_walk); + SetSpeedRate(MOVE_RUN, cinfo->speed_run); + SetSpeedRate(MOVE_SWIM, 1.0f); // using 1.0 rate + SetSpeedRate(MOVE_FLIGHT, 1.0f); // using 1.0 rate // Will set UNIT_FIELD_BOUNDINGRADIUS and UNIT_FIELD_COMBATREACH SetObjectScale(cinfo->scale); @@ -780,7 +780,7 @@ void Creature::DoFleeToGetAssistance() cell.Visit(p, grid_creature_searcher, *GetMap(), *this, radius); SetNoSearchAssistance(true); - UpdateSpeed(MOVE_RUN, false); + UpdateSpeed(MOVE_RUN); if (!creature) //SetFeared(true, EnsureVictim()->GetGUID(), 0, sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY)); @@ -791,6 +791,24 @@ void Creature::DoFleeToGetAssistance() } } +bool Creature::AIM_Destroy() +{ + if (m_AI_locked) + { + TC_LOG_DEBUG("scripts", "AIM_Destroy: failed to destroy, locked."); + return false; + } + + ASSERT(!i_disabledAI, + "The disabled AI wasn't cleared!"); + + delete i_AI; + i_AI = nullptr; + + IsAIEnabled = false; + return true; +} + bool Creature::AIM_Initialize(CreatureAI* ai) { // make sure nothing can change the AI during AI update @@ -800,12 +818,12 @@ bool Creature::AIM_Initialize(CreatureAI* ai) return false; } - UnitAI* oldAI = i_AI; + AIM_Destroy(); Motion_Initialize(); i_AI = ai ? ai : FactorySelector::selectAI(this); - delete oldAI; + IsAIEnabled = true; i_AI->InitializeAI(); // Initialize vehicle @@ -889,7 +907,7 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u { SetDisplayId(displayID); SetNativeDisplayId(displayID); - SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); } LastUsedScriptID = GetCreatureTemplate()->ScriptID; @@ -1618,7 +1636,7 @@ void Creature::setDeathState(DeathState s) if (HasSearchedAssistance()) { SetNoSearchAssistance(false); - UpdateSpeed(MOVE_RUN, false); + UpdateSpeed(MOVE_RUN); } //Dismiss group if is leader @@ -1705,7 +1723,7 @@ void Creature::Respawn(bool force) { SetDisplayId(displayID); SetNativeDisplayId(displayID); - SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); } GetMotionMaster()->InitDefault(); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index c6b19dbb939..8466dad9e95 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -476,6 +476,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma bool IsInEvadeMode() const { return HasUnitState(UNIT_STATE_EVADE); } + bool AIM_Destroy(); bool AIM_Initialize(CreatureAI* ai = NULL); void Motion_Initialize(); diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 3fcd249c9be..7f922f89572 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -71,9 +71,15 @@ GameObject::~GameObject() // CleanupsBeforeDelete(); } -bool GameObject::AIM_Initialize() +void GameObject::AIM_Destroy() { delete m_AI; + m_AI = nullptr; +} + +bool GameObject::AIM_Initialize() +{ + AIM_Destroy(); m_AI = FactorySelector::SelectGameObjectAI(this); diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 49f94013ac2..2b092427178 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -849,8 +849,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void UpdateModelPosition(); - protected: + void AIM_Destroy(); bool AIM_Initialize(); + + protected: GameObjectModel* CreateModel(); void UpdateModel(); // updates model in case displayId were changed uint32 m_spellId; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 824f42ad671..45952ba51ac 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -928,6 +928,11 @@ bool Object::HasByteFlag(uint16 index, uint8 offset, uint8 flag) const return (((uint8*)&m_uint32Values[index])[offset] & flag) != 0; } +void Object::ApplyModByteFlag(uint16 index, uint8 offset, uint8 flag, bool apply) +{ + if (apply) SetByteFlag(index, offset, flag); else RemoveByteFlag(index, offset, flag); +} + void Object::SetFlag64(uint16 index, uint64 newFlag) { uint64 oldval = GetUInt64Value(index); @@ -1465,9 +1470,20 @@ void WorldObject::UpdateAllowedPositionZ(float x, float y, float &z) const float WorldObject::GetGridActivationRange() const { if (ToPlayer()) + { + if (ToPlayer()->IsOnCinematic()) + return DEFAULT_VISIBILITY_INSTANCE; return GetMap()->GetVisibilityRange(); + } else if (ToCreature()) return ToCreature()->m_SightDistance; + else if (ToDynObject()) + { + if (isActiveObject()) + return GetMap()->GetVisibilityRange(); + else + return 0.0f; + } else return 0.0f; } @@ -1488,6 +1504,8 @@ float WorldObject::GetSightRange(const WorldObject* target) const { if (target && target->isActiveObject() && !target->ToPlayer()) return MAX_VISIBILITY_DISTANCE; + else if (ToPlayer()->IsOnCinematic()) + return DEFAULT_VISIBILITY_INSTANCE; else return GetMap()->GetVisibilityRange(); } @@ -1497,6 +1515,11 @@ float WorldObject::GetSightRange(const WorldObject* target) const return SIGHT_RANGE_UNIT; } + if (ToDynObject() && isActiveObject()) + { + return GetMap()->GetVisibilityRange(); + } + return 0.0f; } diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 029a8c08fd7..d4c8fc35451 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -160,6 +160,7 @@ class TC_GAME_API Object void RemoveByteFlag(uint16 index, uint8 offset, uint8 newFlag); void ToggleByteFlag(uint16 index, uint8 offset, uint8 flag); bool HasByteFlag(uint16 index, uint8 offset, uint8 flag) const; + void ApplyModByteFlag(uint16 index, uint8 offset, uint8 flag, bool apply); void SetFlag64(uint16 index, uint64 newFlag); void RemoveFlag64(uint16 index, uint64 oldFlag); diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 0da63c5e500..37fb23b38f5 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -213,12 +213,14 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c case SUMMON_PET: petlevel = owner->getLevel(); - SetUInt32Value(UNIT_FIELD_BYTES_0, 0x800); // class = mage + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(CLASS_MAGE)); SetUInt32Value(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); // this enables popup window (pet dismiss, cancel) break; case HUNTER_PET: - SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); // class = warrior, gender = none, power = focus + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_WARRIOR); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, GENDER_NONE); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, POWER_FOCUS); SetSheath(SHEATH_STATE_MELEE); SetByteFlag(UNIT_FIELD_BYTES_2, 2, fields[9].GetBool() ? UNIT_CAN_BE_ABANDONED : UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED); @@ -226,7 +228,6 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c // this enables popup window (pet abandon, cancel) SetMaxPower(POWER_HAPPINESS, GetCreatePowers(POWER_HAPPINESS)); SetPower(POWER_HAPPINESS, fields[12].GetUInt32()); - setPowerType(POWER_FOCUS); break; default: if (!IsPetGhoul()) @@ -822,7 +823,9 @@ bool Pet::CreateBaseAtTamed(CreatureTemplate const* cinfo, Map* map, uint32 phas if (cinfo->type == CREATURE_TYPE_BEAST) { - SetUInt32Value(UNIT_FIELD_BYTES_0, 0x02020100); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_WARRIOR); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, GENDER_NONE); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, POWER_FOCUS); SetSheath(SHEATH_STATE_MELEE); SetByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED | UNIT_CAN_BE_ABANDONED); } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 0af1fc87f6b..c58f2c940fc 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -96,6 +96,9 @@ #define SKILL_PERM_BONUS(x) int16(PAIR32_HIPART(x)) #define MAKE_SKILL_BONUS(t, p) MAKE_PAIR32(t, p) +#define CINEMATIC_LOOKAHEAD (2 * IN_MILLISECONDS) +#define CINEMATIC_UPDATEDIFF 500 + enum CharacterFlags { CHARACTER_FLAG_NONE = 0x00000000, @@ -535,6 +538,14 @@ Player::Player(WorldSession* session): Unit(true) _activeCheats = CHEAT_NONE; healthBeforeDuel = 0; manaBeforeDuel = 0; + + m_cinematicDiff = 0; + m_lastCinematicCheck = 0; + m_activeCinematicCameraId = 0; + m_cinematicCamera = nullptr; + m_remoteSightPosition = Position(0.0f, 0.0f, 0.0f); + m_CinematicObject = nullptr; + m_achievementMgr = new AchievementMgr(this); m_reputationMgr = new ReputationMgr(this); } @@ -643,9 +654,10 @@ bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo return false; } - uint32 RaceClassGender = (createInfo->Race) | (createInfo->Class << 8) | (createInfo->Gender << 16); - - SetUInt32Value(UNIT_FIELD_BYTES_0, (RaceClassGender | (powertype << 24))); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE, createInfo->Race); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, createInfo->Class); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, createInfo->Gender); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, powertype); InitDisplayIds(); if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP) { @@ -658,13 +670,14 @@ bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, uint32(-1)); // -1 is default value - SetUInt32Value(PLAYER_BYTES, (createInfo->Skin | (createInfo->Face << 8) | (createInfo->HairStyle << 16) | (createInfo->HairColor << 24))); - SetUInt32Value(PLAYER_BYTES_2, (createInfo->FacialHair | - (0x00 << 8) | - (0x00 << 16) | - (((GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NOT_RAF_LINKED) << 24))); - SetByteValue(PLAYER_BYTES_3, 0, createInfo->Gender); - SetByteValue(PLAYER_BYTES_3, 3, 0); // BattlefieldArenaFaction (0 or 1) + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID, createInfo->Skin); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID, createInfo->Face); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID, createInfo->HairStyle); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID, createInfo->HairColor); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE, createInfo->FacialHair); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE, (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0) ? REST_STATE_RAF_LINKED : REST_STATE_NOT_RAF_LINKED); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER, createInfo->Gender); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_ARENA_FACTION, 0); SetUInt32Value(PLAYER_GUILDID, 0); SetUInt32Value(PLAYER_GUILDRANK, 0); @@ -1179,7 +1192,7 @@ void Player::SetDrunkValue(uint8 newDrunkValue, uint32 itemId /*= 0*/) m_invisibilityDetect.DelFlag(INVISIBILITY_DRUNK); uint32 newDrunkenState = Player::GetDrunkenstateByValue(newDrunkValue); - SetByteValue(PLAYER_BYTES_3, 1, newDrunkValue); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_INEBRIATION, newDrunkValue); UpdateObjectVisibility(); if (!isSobering) @@ -1222,7 +1235,15 @@ void Player::Update(uint32 p_time) m_spellModTakingSpell = nullptr; } - //used to implement delayed far teleport + // Update cinematic location, if 500ms have passed and we're doing a cinematic now. + m_cinematicDiff += p_time; + if (m_cinematicCamera && m_activeCinematicCameraId && GetMSTimeDiffToNow(m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF) + { + m_lastCinematicCheck = getMSTime(); + UpdateCinematicLocation(p_time); + } + + //used to implement delayed far teleports SetCanDelayTeleport(true); Unit::Update(p_time); SetCanDelayTeleport(false); @@ -2822,8 +2843,8 @@ void Player::GiveLevel(uint8 level) { ++m_grantableLevels; - if (!HasByteFlag(PLAYER_FIELD_BYTES, 1, 0x01)) - SetByteFlag(PLAYER_FIELD_BYTES, 1, 0x01); + if (!HasByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_RAF_GRANTABLE_LEVEL, 0x01)) + SetByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_RAF_GRANTABLE_LEVEL, 0x01); } sScriptMgr->OnPlayerLevelChanged(this, oldLevel); @@ -4789,7 +4810,7 @@ Corpse* Player::CreateCorpse() // prevent the existence of 2 corpses for one player SpawnCorpseBones(); - uint32 _pb, _pb2, _cfb1, _cfb2; + uint32 _cfb1, _cfb2; Corpse* corpse = new Corpse((m_ExtraFlags & PLAYER_EXTRA_PVP_DEATH) ? CORPSE_RESURRECTABLE_PVP : CORPSE_RESURRECTABLE_PVE); SetPvPDeath(false); @@ -4802,16 +4823,13 @@ Corpse* Player::CreateCorpse() _corpseLocation.WorldRelocate(*this); - _pb = GetUInt32Value(PLAYER_BYTES); - _pb2 = GetUInt32Value(PLAYER_BYTES_2); + uint8 skin = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); + uint8 face = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID); + uint8 hairstyle = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID); + uint8 haircolor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + uint8 facialhair = GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE); - uint8 skin = (uint8)(_pb); - uint8 face = (uint8)(_pb >> 8); - uint8 hairstyle = (uint8)(_pb >> 16); - uint8 haircolor = (uint8)(_pb >> 24); - uint8 facialhair = (uint8)(_pb2); - - _cfb1 = ((0x00) | (getRace() << 8) | (GetByteValue(PLAYER_BYTES_3, 0) << 16) | (skin << 24)); + _cfb1 = ((0x00) | (getRace() << 8) | (GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER) << 16) | (skin << 24)); _cfb2 = ((face) | (hairstyle << 8) | (haircolor << 16) | (facialhair << 24)); corpse->SetUInt32Value(CORPSE_FIELD_BYTES_1, _cfb1); @@ -6399,11 +6417,13 @@ void Player::SendDirectMessage(WorldPacket const* data) const m_session->SendPacket(data); } -void Player::SendCinematicStart(uint32 CinematicSequenceId) const +void Player::SendCinematicStart(uint32 CinematicSequenceId) { WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4); data << uint32(CinematicSequenceId); SendDirectMessage(&data); + if (const CinematicSequencesEntry* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId)) + SetActiveCinematicCamera(sequence->cinematicCamera); } void Player::SendMovieStart(uint32 MovieId) const @@ -16859,12 +16879,9 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) return false; } - // overwrite some data fields - uint32 bytes0 = 0; - bytes0 |= fields[3].GetUInt8(); // race - bytes0 |= fields[4].GetUInt8() << 8; // class - bytes0 |= gender << 16; // gender - SetUInt32Value(UNIT_FIELD_BYTES_0, bytes0); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE, fields[3].GetUInt8()); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, fields[4].GetUInt8()); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, gender); // check if race/class combination is valid PlayerInfo const* info = sObjectMgr->GetPlayerInfo(getRace(), getClass()); @@ -16891,25 +16908,25 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) money = MAX_MONEY_AMOUNT; SetMoney(money); - SetByteValue(PLAYER_BYTES, 0, fields[9].GetUInt8()); - SetByteValue(PLAYER_BYTES, 1, fields[10].GetUInt8()); - SetByteValue(PLAYER_BYTES, 2, fields[11].GetUInt8()); - SetByteValue(PLAYER_BYTES, 3, fields[12].GetUInt8()); - SetByteValue(PLAYER_BYTES_2, 0, fields[13].GetUInt8()); - SetByteValue(PLAYER_BYTES_2, 2, fields[14].GetUInt8()); - SetByteValue(PLAYER_BYTES_2, 3, fields[15].GetUInt8()); - SetByteValue(PLAYER_BYTES_3, 0, fields[5].GetUInt8()); - SetByteValue(PLAYER_BYTES_3, 1, fields[54].GetUInt8()); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID, fields[9].GetUInt8()); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID, fields[10].GetUInt8()); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID, fields[11].GetUInt8()); + SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID, fields[12].GetUInt8()); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE, fields[13].GetUInt8()); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS, fields[14].GetUInt8()); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE, fields[15].GetUInt8()); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER, fields[5].GetUInt8()); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_INEBRIATION, fields[54].GetUInt8()); if (!ValidateAppearance( fields[3].GetUInt8(), // race fields[4].GetUInt8(), // class gender, - GetByteValue(PLAYER_BYTES, 2), - GetByteValue(PLAYER_BYTES, 3), - GetByteValue(PLAYER_BYTES, 1), - GetByteValue(PLAYER_BYTES_2, 0), - GetByteValue(PLAYER_BYTES, 0))) + GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID), + GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID), + GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID), + GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE), + GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID))) { TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player (%s) has wrong Appearance values (Hair/Skin/Color), can't load.", guid.ToString().c_str()); return false; @@ -16923,7 +16940,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) SetUInt32Value(PLAYER_AMMO_ID, fields[68].GetUInt32()); // set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise) - SetByteValue(PLAYER_FIELD_BYTES, 2, fields[70].GetUInt8()); + SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES, fields[70].GetUInt8()); InitDisplayIds(); @@ -17506,7 +17523,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_REFER_A_FRIEND); if (m_grantableLevels > 0) - SetByteValue(PLAYER_FIELD_BYTES, 1, 0x01); + SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_RAF_GRANTABLE_LEVEL, 0x01); _LoadDeclinedNames(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES)); @@ -17713,7 +17730,7 @@ void Player::LoadCorpse(PreparedQueryResult result) { Field* fields = result->Fetch(); _corpseLocation.WorldRelocate(fields[0].GetUInt16(), fields[1].GetFloat(), fields[2].GetFloat(), fields[3].GetFloat(), fields[4].GetFloat()); - ApplyModFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTE_RELEASE_TIMER, !sMapStore.LookupEntry(_corpseLocation.GetMapId())->Instanceable()); + ApplyModByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_RELEASE_TIMER, !sMapStore.LookupEntry(_corpseLocation.GetMapId())->Instanceable()); } else ResurrectPlayer(0.5f); @@ -18947,17 +18964,17 @@ void Player::SaveToDB(bool create /*=false*/) stmt->setString(index++, GetName()); stmt->setUInt8(index++, getRace()); stmt->setUInt8(index++, getClass()); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_3, 0)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect stmt->setUInt8(index++, getLevel()); stmt->setUInt32(index++, GetUInt32Value(PLAYER_XP)); stmt->setUInt32(index++, GetMoney()); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 0)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 1)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 2)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 3)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 0)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 2)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 3)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE)); stmt->setUInt32(index++, GetUInt32Value(PLAYER_FLAGS)); stmt->setUInt16(index++, (uint16)GetMapId()); stmt->setUInt32(index++, (uint32)GetInstanceId()); @@ -19047,7 +19064,7 @@ void Player::SaveToDB(bool create /*=false*/) ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << ' '; stmt->setString(index++, ss.str()); - stmt->setUInt8(index++, GetByteValue(PLAYER_FIELD_BYTES, 2)); + stmt->setUInt8(index++, GetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES)); stmt->setUInt32(index, m_grantableLevels); } else @@ -19057,17 +19074,17 @@ void Player::SaveToDB(bool create /*=false*/) stmt->setString(index++, GetName()); stmt->setUInt8(index++, getRace()); stmt->setUInt8(index++, getClass()); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_3, 0)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)); // save gender from PLAYER_BYTES_3, UNIT_BYTES_0 changes with every transform effect stmt->setUInt8(index++, getLevel()); stmt->setUInt32(index++, GetUInt32Value(PLAYER_XP)); stmt->setUInt32(index++, GetMoney()); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 0)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 1)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 2)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, 3)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 0)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 2)); - stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, 3)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS)); + stmt->setUInt8(index++, GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE)); stmt->setUInt32(index++, GetUInt32Value(PLAYER_FLAGS)); if (!IsBeingTeleported()) @@ -19172,7 +19189,7 @@ void Player::SaveToDB(bool create /*=false*/) ss << GetUInt32Value(PLAYER__FIELD_KNOWN_TITLES + i) << ' '; stmt->setString(index++, ss.str()); - stmt->setUInt8(index++, GetByteValue(PLAYER_FIELD_BYTES, 2)); + stmt->setUInt8(index++, GetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES)); stmt->setUInt32(index++, m_grantableLevels); stmt->setUInt8(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0); @@ -20892,13 +20909,13 @@ void Player::SetRestBonus(float rest_bonus_new) // update data for client if ((GetsRecruitAFriendBonus(true) && (GetSession()->IsARecruiter() || GetSession()->GetRecruiterId() != 0))) - SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RAF_LINKED); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE, REST_STATE_RAF_LINKED); else { if (m_rest_bonus > 10) - SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RESTED); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE, REST_STATE_RESTED); else if (m_rest_bonus <= 1) - SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NOT_RAF_LINKED); + SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_REST_STATE, REST_STATE_NOT_RAF_LINKED); } //RestTickUpdate @@ -21232,7 +21249,7 @@ void Player::InitDisplayIds() return; } - uint8 gender = GetByteValue(PLAYER_BYTES_3, 0); + uint8 gender = GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER); switch (gender) { case GENDER_FEMALE: @@ -21840,7 +21857,7 @@ void Player::SetBattlegroundEntryPoint() void Player::SetBGTeam(uint32 team) { m_bgData.bgTeam = team; - SetByteValue(PLAYER_BYTES_3, 3, uint8(team == ALLIANCE ? 1 : 0)); + SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_ARENA_FACTION, uint8(team == ALLIANCE ? 1 : 0)); } uint32 Player::GetBGTeam() const @@ -24061,10 +24078,10 @@ uint32 Player::GetBarberShopCost(uint8 newhairstyle, uint8 newhaircolor, uint8 n if (level > GT_MAX_LEVEL) level = GT_MAX_LEVEL; // max level in this dbc - uint8 hairstyle = GetByteValue(PLAYER_BYTES, 2); - uint8 haircolor = GetByteValue(PLAYER_BYTES, 3); - uint8 facialhair = GetByteValue(PLAYER_BYTES_2, 0); - uint8 skincolor = GetByteValue(PLAYER_BYTES, 0); + uint8 hairstyle = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID); + uint8 haircolor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); + uint8 facialhair = GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE); + uint8 skincolor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); if ((hairstyle == newhairstyle) && (haircolor == newhaircolor) && (facialhair == newfacialhair) && (!newSkin || (newSkin->hair_id == skincolor))) return 0; @@ -26094,6 +26111,9 @@ bool Player::SetCanFly(bool apply, bool packetOnly /*= false*/) if (!packetOnly && !Unit::SetCanFly(apply)) return false; + if (!apply) + SetFallInformation(0, GetPositionZ()); + WorldPacket data(apply ? SMSG_MOVE_SET_CAN_FLY : SMSG_MOVE_UNSET_CAN_FLY, 12); data << GetPackGUID(); data << uint32(0); //! movement counter @@ -26190,6 +26210,125 @@ float Player::GetCollisionHeight(bool mounted) const } } +void Player::BeginCinematic() +{ + // Sanity check for active camera set + if (m_activeCinematicCameraId == 0) + return; + + auto itr = sFlyByCameraStore.find(m_activeCinematicCameraId); + if (itr != sFlyByCameraStore.end()) + { + // Initialize diff, and set camera + m_cinematicDiff = 0; + m_cinematicCamera = &itr->second; + + auto camitr = m_cinematicCamera->begin(); + if (camitr != m_cinematicCamera->end()) + { + Position pos(camitr->locations.x, camitr->locations.y, camitr->locations.z, camitr->locations.w); + if (!pos.IsPositionValid()) + return; + + m_mapRef->LoadGrid(camitr->locations.x, camitr->locations.y); + m_CinematicObject = SummonCreature(VISUAL_WAYPOINT, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.0f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 120000); + if (m_CinematicObject) + { + m_CinematicObject->setActive(true); + SetViewpoint(m_CinematicObject, true); + } + } + } +} + +void Player::EndCinematic() +{ + m_cinematicDiff = 0; + m_cinematicCamera = nullptr; + m_activeCinematicCameraId = 0; + if (m_CinematicObject) + { + if (m_seer && m_seer == m_CinematicObject) + SetViewpoint(m_CinematicObject, false); + m_CinematicObject->AddObjectToRemoveList(); + } +} + +void Player::UpdateCinematicLocation(uint32 /*diff*/) +{ + Position lastPosition; + uint32 lastTimestamp = 0; + Position nextPosition; + uint32 nextTimestamp = 0; + + if (m_cinematicCamera->size() == 0) + return; + + // Obtain direction of travel + for (FlyByCamera cam : *m_cinematicCamera) + { + if (cam.timeStamp > m_cinematicDiff) + { + nextPosition = Position(cam.locations.x, cam.locations.y, cam.locations.z, cam.locations.w); + nextTimestamp = cam.timeStamp; + break; + } + lastPosition = Position(cam.locations.x, cam.locations.y, cam.locations.z, cam.locations.w); + lastTimestamp = cam.timeStamp; + } + float angle = lastPosition.GetAngle(&nextPosition); + angle -= lastPosition.GetOrientation(); + if (angle < 0) + angle += 2 * float(M_PI); + + // Look for position around 2 second ahead of us. + int32 workDiff = m_cinematicDiff; + + // Modify result based on camera direction (Humans for example, have the camera point behind) + workDiff += static_cast<int32>(float(CINEMATIC_LOOKAHEAD) * cos(angle)); + + // Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end + FlyByCameraCollection::const_reverse_iterator endItr = m_cinematicCamera->rbegin(); + if (endItr != m_cinematicCamera->rend() && workDiff > static_cast<int32>(endItr->timeStamp)) + workDiff = endItr->timeStamp; + + // Never try to go back in time before the start of cinematic! + if (workDiff < 0) + workDiff = m_cinematicDiff; + + // Obtain the previous and next waypoint based on timestamp + for (FlyByCamera cam : *m_cinematicCamera) + { + if (static_cast<int32>(cam.timeStamp) >= workDiff) + { + nextPosition = Position(cam.locations.x, cam.locations.y, cam.locations.z, cam.locations.w); + nextTimestamp = cam.timeStamp; + break; + } + lastPosition = Position(cam.locations.x, cam.locations.y, cam.locations.z, cam.locations.w); + lastTimestamp = cam.timeStamp; + } + + // Never try to go beyond the end of the cinematic + if (workDiff > static_cast<int32>(nextTimestamp)) + workDiff = static_cast<int32>(nextTimestamp); + + // Interpolate the position for this moment in time (or the adjusted moment in time) + uint32 timeDiff = nextTimestamp - lastTimestamp; + uint32 interDiff = workDiff - lastTimestamp; + float xDiff = nextPosition.m_positionX - lastPosition.m_positionX; + float yDiff = nextPosition.m_positionY - lastPosition.m_positionY; + float zDiff = nextPosition.m_positionZ - lastPosition.m_positionZ; + Position interPosition(lastPosition.m_positionX + (xDiff * (float(interDiff)/float(timeDiff))), lastPosition.m_positionY + + (yDiff * (float(interDiff) / float(timeDiff))), lastPosition.m_positionZ + (zDiff * (float(interDiff) / float(timeDiff)))); + + // Advance (at speed) to this position. The remote sight object is used + // to send update information to player in cinematic + if (m_CinematicObject && interPosition.IsPositionValid()) + m_CinematicObject->MonsterMoveWithSpeed(interPosition.m_positionX, interPosition.m_positionY, interPosition.m_positionZ, 200.0f, false, true); +} + + std::string Player::GetMapAreaAndZoneString() const { uint32 areaId = GetAreaId(); @@ -26285,7 +26424,7 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy case SUMMON_PET: // this enables pet details window (Shift+P) pet->GetCharmInfo()->SetPetNumber(pet_number, true); - pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 2048); + pet->SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, CLASS_MAGE); pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); pet->SetFullHealth(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index d817969f6da..fd0fac69674 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -415,50 +415,48 @@ enum PlayerFlags PLAYER_FLAGS_UNK31 = 0x80000000 }; -// used for PLAYER__FIELD_KNOWN_TITLES field (uint64), (1<<bit_index) without (-1) -// can't use enum for uint64 values -#define PLAYER_TITLE_DISABLED UI64LIT(0x0000000000000000) -#define PLAYER_TITLE_NONE UI64LIT(0x0000000000000001) -#define PLAYER_TITLE_PRIVATE UI64LIT(0x0000000000000002) // 1 -#define PLAYER_TITLE_CORPORAL UI64LIT(0x0000000000000004) // 2 -#define PLAYER_TITLE_SERGEANT_A UI64LIT(0x0000000000000008) // 3 -#define PLAYER_TITLE_MASTER_SERGEANT UI64LIT(0x0000000000000010) // 4 -#define PLAYER_TITLE_SERGEANT_MAJOR UI64LIT(0x0000000000000020) // 5 -#define PLAYER_TITLE_KNIGHT UI64LIT(0x0000000000000040) // 6 -#define PLAYER_TITLE_KNIGHT_LIEUTENANT UI64LIT(0x0000000000000080) // 7 -#define PLAYER_TITLE_KNIGHT_CAPTAIN UI64LIT(0x0000000000000100) // 8 -#define PLAYER_TITLE_KNIGHT_CHAMPION UI64LIT(0x0000000000000200) // 9 -#define PLAYER_TITLE_LIEUTENANT_COMMANDER UI64LIT(0x0000000000000400) // 10 -#define PLAYER_TITLE_COMMANDER UI64LIT(0x0000000000000800) // 11 -#define PLAYER_TITLE_MARSHAL UI64LIT(0x0000000000001000) // 12 -#define PLAYER_TITLE_FIELD_MARSHAL UI64LIT(0x0000000000002000) // 13 -#define PLAYER_TITLE_GRAND_MARSHAL UI64LIT(0x0000000000004000) // 14 -#define PLAYER_TITLE_SCOUT UI64LIT(0x0000000000008000) // 15 -#define PLAYER_TITLE_GRUNT UI64LIT(0x0000000000010000) // 16 -#define PLAYER_TITLE_SERGEANT_H UI64LIT(0x0000000000020000) // 17 -#define PLAYER_TITLE_SENIOR_SERGEANT UI64LIT(0x0000000000040000) // 18 -#define PLAYER_TITLE_FIRST_SERGEANT UI64LIT(0x0000000000080000) // 19 -#define PLAYER_TITLE_STONE_GUARD UI64LIT(0x0000000000100000) // 20 -#define PLAYER_TITLE_BLOOD_GUARD UI64LIT(0x0000000000200000) // 21 -#define PLAYER_TITLE_LEGIONNAIRE UI64LIT(0x0000000000400000) // 22 -#define PLAYER_TITLE_CENTURION UI64LIT(0x0000000000800000) // 23 -#define PLAYER_TITLE_CHAMPION UI64LIT(0x0000000001000000) // 24 -#define PLAYER_TITLE_LIEUTENANT_GENERAL UI64LIT(0x0000000002000000) // 25 -#define PLAYER_TITLE_GENERAL UI64LIT(0x0000000004000000) // 26 -#define PLAYER_TITLE_WARLORD UI64LIT(0x0000000008000000) // 27 -#define PLAYER_TITLE_HIGH_WARLORD UI64LIT(0x0000000010000000) // 28 -#define PLAYER_TITLE_GLADIATOR UI64LIT(0x0000000020000000) // 29 -#define PLAYER_TITLE_DUELIST UI64LIT(0x0000000040000000) // 30 -#define PLAYER_TITLE_RIVAL UI64LIT(0x0000000080000000) // 31 -#define PLAYER_TITLE_CHALLENGER UI64LIT(0x0000000100000000) // 32 -#define PLAYER_TITLE_SCARAB_LORD UI64LIT(0x0000000200000000) // 33 -#define PLAYER_TITLE_CONQUEROR UI64LIT(0x0000000400000000) // 34 -#define PLAYER_TITLE_JUSTICAR UI64LIT(0x0000000800000000) // 35 -#define PLAYER_TITLE_CHAMPION_OF_THE_NAARU UI64LIT(0x0000001000000000) // 36 -#define PLAYER_TITLE_MERCILESS_GLADIATOR UI64LIT(0x0000002000000000) // 37 -#define PLAYER_TITLE_OF_THE_SHATTERED_SUN UI64LIT(0x0000004000000000) // 38 -#define PLAYER_TITLE_HAND_OF_ADAL UI64LIT(0x0000008000000000) // 39 -#define PLAYER_TITLE_VENGEFUL_GLADIATOR UI64LIT(0x0000010000000000) // 40 +enum PlayerBytesOffsets +{ + PLAYER_BYTES_OFFSET_SKIN_ID = 0, + PLAYER_BYTES_OFFSET_FACE_ID = 1, + PLAYER_BYTES_OFFSET_HAIR_STYLE_ID = 2, + PLAYER_BYTES_OFFSET_HAIR_COLOR_ID = 3 +}; + +enum PlayerBytes2Offsets +{ + PLAYER_BYTES_2_OFFSET_FACIAL_STYLE = 0, + PLAYER_BYTES_2_OFFSET_PARTY_TYPE = 1, + PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS = 2, + PLAYER_BYTES_2_OFFSET_REST_STATE = 3 +}; + +enum PlayerBytes3Offsets +{ + PLAYER_BYTES_3_OFFSET_GENDER = 0, + PLAYER_BYTES_3_OFFSET_INEBRIATION = 1, + PLAYER_BYTES_3_OFFSET_PVP_TITLE = 2, + PLAYER_BYTES_3_OFFSET_ARENA_FACTION = 3 +}; + +enum PlayerFieldBytesOffsets +{ + PLAYER_FIELD_BYTES_OFFSET_FLAGS = 0, + PLAYER_FIELD_BYTES_OFFSET_RAF_GRANTABLE_LEVEL = 1, + PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES = 2, + PLAYER_FIELD_BYTES_OFFSET_LIFETIME_MAX_PVP_RANK = 3 +}; + +enum PlayerFieldBytes2Offsets +{ + PLAYER_FIELD_BYTES_2_OFFSET_OVERRIDE_SPELLS_ID = 0, // uint16! + PLAYER_FIELD_BYTES_2_OFFSET_IGNORE_POWER_REGEN_PREDICTION_MASK = 2, + PLAYER_FIELD_BYTES_2_OFFSET_AURA_VISION = 3 +}; + +static_assert((PLAYER_FIELD_BYTES_2_OFFSET_OVERRIDE_SPELLS_ID & 1) == 0, "PLAYER_FIELD_BYTES_2_OFFSET_OVERRIDE_SPELLS_ID must be aligned to 2 byte boundary"); + +#define PLAYER_BYTES_2_OVERRIDE_SPELLS_UINT16_OFFSET (PLAYER_FIELD_BYTES_2_OFFSET_OVERRIDE_SPELLS_ID / 2) #define KNOWN_TITLES_SIZE 3 #define MAX_TITLE_INDEX (KNOWN_TITLES_SIZE*64) // 3 uint64 fields @@ -1185,8 +1183,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> static bool IsBankPos(uint8 bag, uint8 slot); bool IsValidPos(uint16 pos, bool explicit_pos) const { return IsValidPos(pos >> 8, pos & 255, explicit_pos); } bool IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const; - uint8 GetBankBagSlotCount() const { return GetByteValue(PLAYER_BYTES_2, 2); } - void SetBankBagSlotCount(uint8 count) { SetByteValue(PLAYER_BYTES_2, 2, count); } + uint8 GetBankBagSlotCount() const { return GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS); } + void SetBankBagSlotCount(uint8 count) { SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_BANK_BAG_SLOTS, count); } bool HasItemCount(uint32 item, uint32 count = 1, bool inBankAlso = false) const; bool HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item const* ignoreItem = nullptr) const; bool CanNoReagentCast(SpellInfo const* spellInfo) const; @@ -1911,7 +1909,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> //End of PvP System void SetDrunkValue(uint8 newDrunkValue, uint32 itemId = 0); - uint8 GetDrunkValue() const { return GetByteValue(PLAYER_BYTES_3, 1); } + uint8 GetDrunkValue() const { return GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_INEBRIATION); } static DrunkenState GetDrunkenstateByValue(uint8 value); uint32 GetDeathTimer() const { return m_deathTimer; } @@ -2131,7 +2129,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void ResummonPetTemporaryUnSummonedIfAny(); bool IsPetNeedBeTemporaryUnsummoned() const; - void SendCinematicStart(uint32 CinematicSequenceId) const; + void SendCinematicStart(uint32 CinematicSequenceId); void SendMovieStart(uint32 MovieId) const; /*********************************************************/ @@ -2267,6 +2265,17 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> std::string GetMapAreaAndZoneString() const; std::string GetCoordsMapAreaAndZoneString() const; + // Cinematic camera data and remote sight functions + uint32 GetActiveCinematicCamera() const { return m_activeCinematicCameraId; } + void SetActiveCinematicCamera(uint32 cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; } + bool IsOnCinematic() const { return (m_cinematicCamera != nullptr); } + void BeginCinematic(); + void EndCinematic(); + void UpdateCinematicLocation(uint32 diff); + + std::string GetMapAreaAndZoneString(); + std::string GetCoordsMapAreaAndZoneString(); + protected: // Gamemaster whisper whitelist GuidList WhisperList; @@ -2592,6 +2601,14 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> uint32 manaBeforeDuel; WorldLocation _corpseLocation; + + // Remote location information + uint32 m_cinematicDiff; + uint32 m_lastCinematicCheck; + uint32 m_activeCinematicCameraId; + FlyByCameraCollection* m_cinematicCamera; + Position m_remoteSightPosition; + Creature* m_CinematicObject; }; TC_GAME_API void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 319b939d899..b7f05a7fefa 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -8715,7 +8715,7 @@ void Unit::setPowerType(Powers new_powertype) if (getPowerType() == new_powertype) return; - SetByteValue(UNIT_FIELD_BYTES_0, 3, new_powertype); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, new_powertype); if (GetTypeId() == TYPEID_PLAYER) { @@ -9106,7 +9106,7 @@ bool Unit::AttackStop() if (creature->HasSearchedAssistance()) { creature->SetNoSearchAssistance(false); - UpdateSpeed(MOVE_RUN, false); + UpdateSpeed(MOVE_RUN); } } @@ -9440,7 +9440,7 @@ void Unit::SetMinion(Minion *minion, bool apply) // FIXME: hack, speed must be set only at follow if (GetTypeId() == TYPEID_PLAYER && minion->IsPet()) for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) - minion->SetSpeed(UnitMoveType(i), m_speed_rate[i], true); + minion->SetSpeedRate(UnitMoveType(i), m_speed_rate[i]); // Ghoul pets have energy instead of mana (is anywhere better place for this code?) if (minion->IsPetGhoul() || minion->IsRisenAlly()) @@ -11870,9 +11870,9 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy) if (IsPet()) { - UpdateSpeed(MOVE_RUN, true); - UpdateSpeed(MOVE_SWIM, true); - UpdateSpeed(MOVE_FLIGHT, true); + UpdateSpeed(MOVE_RUN); + UpdateSpeed(MOVE_SWIM); + UpdateSpeed(MOVE_FLIGHT); } if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_MOUNTED_COMBAT)) @@ -11916,7 +11916,7 @@ void Unit::ClearInCombat() if (Unit* owner = GetOwner()) for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) if (owner->GetSpeedRate(UnitMoveType(i)) > GetSpeedRate(UnitMoveType(i))) - SetSpeed(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i)), true); + SetSpeedRate(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i))); } else if (!IsCharmed()) return; @@ -12003,25 +12003,26 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo || target->GetReactionTo(this) > REP_NEUTRAL) return false; + Player const* playerAffectingAttacker = HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) ? GetAffectingPlayer() : nullptr; + Player const* playerAffectingTarget = target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) ? target->GetAffectingPlayer() : nullptr; + // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar) - if (GetReactionTo(target) == REP_NEUTRAL && - target->GetReactionTo(this) <= REP_NEUTRAL) + if ( + (playerAffectingAttacker && !playerAffectingTarget) || + (!playerAffectingAttacker && playerAffectingTarget) + ) { - if (!(target->GetTypeId() == TYPEID_PLAYER && GetTypeId() == TYPEID_PLAYER) && - !(target->GetTypeId() == TYPEID_UNIT && GetTypeId() == TYPEID_UNIT)) - { - Player const* player = target->GetTypeId() == TYPEID_PLAYER ? target->ToPlayer() : ToPlayer(); - Unit const* creature = target->GetTypeId() == TYPEID_UNIT ? target : this; + Player const* player = playerAffectingAttacker ? playerAffectingAttacker : playerAffectingTarget; + Unit const* creature = playerAffectingAttacker ? target : this; - if (FactionTemplateEntry const* factionTemplate = creature->GetFactionTemplateEntry()) - { - if (!(player->GetReputationMgr().GetForcedRankIfAny(factionTemplate))) - if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplate->faction)) - if (FactionState const* repState = player->GetReputationMgr().GetState(factionEntry)) - if (!(repState->Flags & FACTION_FLAG_AT_WAR)) - return false; + if (FactionTemplateEntry const* factionTemplate = creature->GetFactionTemplateEntry()) + { + if (!(player->GetReputationMgr().GetForcedRankIfAny(factionTemplate))) + if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplate->faction)) + if (FactionState const* repState = player->GetReputationMgr().GetState(factionEntry)) + if (!(repState->Flags & FACTION_FLAG_AT_WAR)) + return false; - } } } @@ -12029,9 +12030,6 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo if (creatureAttacker && creatureAttacker->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_PARTY_MEMBER) return false; - Player const* playerAffectingAttacker = HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) ? GetAffectingPlayer() : NULL; - Player const* playerAffectingTarget = target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) ? target->GetAffectingPlayer() : NULL; - // check duel - before sanctuary checks if (playerAffectingAttacker && playerAffectingTarget) if (playerAffectingAttacker->duel && playerAffectingAttacker->duel->opponent == playerAffectingTarget && playerAffectingAttacker->duel->startTime != 0) @@ -12302,7 +12300,7 @@ void Unit::SetVisible(bool x) UpdateObjectVisibility(); } -void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) +void Unit::UpdateSpeed(UnitMoveType mtype) { int32 main_speed_mod = 0; float stack_bonus = 1.0f; @@ -12365,7 +12363,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) // Update speed for vehicle if available if (GetTypeId() == TYPEID_PLAYER && GetVehicle()) - GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT, true); + GetVehicleBase()->UpdateSpeed(MOVE_FLIGHT); break; } default: @@ -12447,7 +12445,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) speed = min_speed; } - SetSpeed(mtype, speed, forced); + SetSpeedRate(mtype, speed); } float Unit::GetSpeed(UnitMoveType mtype) const @@ -12455,7 +12453,12 @@ float Unit::GetSpeed(UnitMoveType mtype) const return m_speed_rate[mtype]*(IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype]); } -void Unit::SetSpeed(UnitMoveType mtype, float rate, bool forced) +void Unit::SetSpeed(UnitMoveType mtype, float newValue) +{ + SetSpeedRate(mtype, newValue / (IsControlledByPlayer() ? playerBaseMoveSpeed[mtype] : baseMoveSpeed[mtype])); +} + +void Unit::SetSpeedRate(UnitMoveType mtype, float rate) { if (rate < 0) rate = 0.0f; @@ -12468,100 +12471,59 @@ void Unit::SetSpeed(UnitMoveType mtype, float rate, bool forced) propagateSpeedChange(); - WorldPacket data; - if (!forced) + // Spline packets are for units controlled by AI. "Force speed change" (wrongly named opcodes) and "move set speed" packets are for units controlled by a player. + static Opcodes const moveTypeToOpcode[MAX_MOVE_TYPE][3] = + { + {SMSG_SPLINE_SET_WALK_SPEED, SMSG_FORCE_WALK_SPEED_CHANGE, MSG_MOVE_SET_WALK_SPEED }, + {SMSG_SPLINE_SET_RUN_SPEED, SMSG_FORCE_RUN_SPEED_CHANGE, MSG_MOVE_SET_RUN_SPEED }, + {SMSG_SPLINE_SET_RUN_BACK_SPEED, SMSG_FORCE_RUN_BACK_SPEED_CHANGE, MSG_MOVE_SET_RUN_BACK_SPEED }, + {SMSG_SPLINE_SET_SWIM_SPEED, SMSG_FORCE_SWIM_SPEED_CHANGE, MSG_MOVE_SET_SWIM_SPEED }, + {SMSG_SPLINE_SET_SWIM_BACK_SPEED, SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, MSG_MOVE_SET_SWIM_BACK_SPEED }, + {SMSG_SPLINE_SET_TURN_RATE, SMSG_FORCE_TURN_RATE_CHANGE, MSG_MOVE_SET_TURN_RATE }, + {SMSG_SPLINE_SET_FLIGHT_SPEED, SMSG_FORCE_FLIGHT_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_SPEED }, + {SMSG_SPLINE_SET_FLIGHT_BACK_SPEED, SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, MSG_MOVE_SET_FLIGHT_BACK_SPEED }, + {SMSG_SPLINE_SET_PITCH_RATE, SMSG_FORCE_PITCH_RATE_CHANGE, MSG_MOVE_SET_PITCH_RATE }, + }; + + if (GetTypeId() == TYPEID_PLAYER) { - switch (mtype) - { - case MOVE_WALK: - data.Initialize(MSG_MOVE_SET_WALK_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_RUN: - data.Initialize(MSG_MOVE_SET_RUN_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_RUN_BACK: - data.Initialize(MSG_MOVE_SET_RUN_BACK_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_SWIM: - data.Initialize(MSG_MOVE_SET_SWIM_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_SWIM_BACK: - data.Initialize(MSG_MOVE_SET_SWIM_BACK_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_TURN_RATE: - data.Initialize(MSG_MOVE_SET_TURN_RATE, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_FLIGHT: - data.Initialize(MSG_MOVE_SET_FLIGHT_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_FLIGHT_BACK: - data.Initialize(MSG_MOVE_SET_FLIGHT_BACK_SPEED, 8+4+2+4+4+4+4+4+4+4); - break; - case MOVE_PITCH_RATE: - data.Initialize(MSG_MOVE_SET_PITCH_RATE, 8+4+2+4+4+4+4+4+4+4); - break; - default: - TC_LOG_ERROR("entities.unit", "Unit::SetSpeed: Unsupported move type (%d), data not sent to client.", mtype); - return; - } + // register forced speed changes for WorldSession::HandleForceSpeedChangeAck + // and do it only for real sent packets and use run for run/mounted as client expected + ++ToPlayer()->m_forced_speed_changes[mtype]; + if (!IsInCombat()) + if (Pet* pet = ToPlayer()->GetPet()) + pet->SetSpeedRate(mtype, m_speed_rate[mtype]); + } + + if (m_movedPlayer) // unit controlled by a player. + { + // Send notification to self. this packet is only sent to one client (the client of the player concerned by the change). + WorldPacket self; + self.Initialize(moveTypeToOpcode[mtype][1], mtype != MOVE_RUN ? 8 + 4 + 4 : 8 + 4 + 1 + 4); + self << GetPackGUID(); + self << (uint32)0; // Movement counter. Unimplemented at the moment! NUM_PMOVE_EVTS = 0x39Z. + if (mtype == MOVE_RUN) + self << uint8(1); // unknown byte added in 2.1.0 + self << float(GetSpeed(mtype)); + m_movedPlayer->GetSession()->SendPacket(&self); + + // Send notification to other players. sent to every clients (if in range) except one: the client of the player concerned by the change. + WorldPacket data; + data.Initialize(moveTypeToOpcode[mtype][2], 8 + 30 + 4); data << GetPackGUID(); BuildMovementPacket(&data); data << float(GetSpeed(mtype)); - SendMessageToSet(&data, true); + SendMessageToSet(&data, false); } - else + else // unit controlled by AI. { - if (GetTypeId() == TYPEID_PLAYER) - { - // register forced speed changes for WorldSession::HandleForceSpeedChangeAck - // and do it only for real sent packets and use run for run/mounted as client expected - ++ToPlayer()->m_forced_speed_changes[mtype]; - - if (!IsInCombat()) - if (Pet* pet = ToPlayer()->GetPet()) - pet->SetSpeed(mtype, m_speed_rate[mtype], forced); - } - - switch (mtype) - { - case MOVE_WALK: - data.Initialize(SMSG_FORCE_WALK_SPEED_CHANGE, 16); - break; - case MOVE_RUN: - data.Initialize(SMSG_FORCE_RUN_SPEED_CHANGE, 17); - break; - case MOVE_RUN_BACK: - data.Initialize(SMSG_FORCE_RUN_BACK_SPEED_CHANGE, 16); - break; - case MOVE_SWIM: - data.Initialize(SMSG_FORCE_SWIM_SPEED_CHANGE, 16); - break; - case MOVE_SWIM_BACK: - data.Initialize(SMSG_FORCE_SWIM_BACK_SPEED_CHANGE, 16); - break; - case MOVE_TURN_RATE: - data.Initialize(SMSG_FORCE_TURN_RATE_CHANGE, 16); - break; - case MOVE_FLIGHT: - data.Initialize(SMSG_FORCE_FLIGHT_SPEED_CHANGE, 16); - break; - case MOVE_FLIGHT_BACK: - data.Initialize(SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE, 16); - break; - case MOVE_PITCH_RATE: - data.Initialize(SMSG_FORCE_PITCH_RATE_CHANGE, 16); - break; - default: - TC_LOG_ERROR("entities.unit", "Unit::SetSpeed: Unsupported move type (%d), data not sent to client.", mtype); - return; - } + // send notification to every clients. + WorldPacket data; + data.Initialize(moveTypeToOpcode[mtype][0], 8 + 4); data << GetPackGUID(); - data << (uint32)0; // moveEvent, NUM_PMOVE_EVTS = 0x39 - if (mtype == MOVE_RUN) - data << uint8(0); // new 2.1.0 data << float(GetSpeed(mtype)); - SendMessageToSet(&data, true); + SendMessageToSet(&data, false); } } @@ -14769,7 +14731,7 @@ void Unit::SetDisplayId(uint32 modelId) SetUInt32Value(UNIT_FIELD_DISPLAYID, modelId); // Set Gender by modelId if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) - SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, minfo->gender); } void Unit::RestoreDisplayId() @@ -16080,7 +16042,7 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) { // to prevent client crash - SetByteValue(UNIT_FIELD_BYTES_0, 1, (uint8)CLASS_MAGE); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, (uint8)CLASS_MAGE); // just to enable stat window if (GetCharmInfo()) @@ -16182,7 +16144,7 @@ void Unit::RemoveCharmedBy(Unit* charmer) CreatureTemplate const* cinfo = ToCreature()->GetCreatureTemplate(); if (cinfo && cinfo->type == CREATURE_TYPE_DEMON) { - SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class)); + SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS, uint8(cinfo->unit_class)); if (GetCharmInfo()) GetCharmInfo()->SetPetNumber(0, true); else @@ -16700,7 +16662,7 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const // Based on Hair color if (getRace() == RACE_NIGHTELF) { - uint8 hairColor = GetByteValue(PLAYER_BYTES, 3); + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); switch (hairColor) { case 7: // Violet @@ -16721,7 +16683,7 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const // Based on Skin color else if (getRace() == RACE_TAUREN) { - uint8 skinColor = GetByteValue(PLAYER_BYTES, 0); + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); // Male if (getGender() == GENDER_MALE) { @@ -16780,7 +16742,7 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const // Based on Hair color if (getRace() == RACE_NIGHTELF) { - uint8 hairColor = GetByteValue(PLAYER_BYTES, 3); + uint8 hairColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID); switch (hairColor) { case 0: // Green @@ -16800,7 +16762,7 @@ uint32 Unit::GetModelForForm(ShapeshiftForm form) const // Based on Skin color else if (getRace() == RACE_TAUREN) { - uint8 skinColor = GetByteValue(PLAYER_BYTES, 0); + uint8 skinColor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID); // Male if (getGender() == GENDER_MALE) { diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index cc1f6465766..a0973aac279 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -190,6 +190,30 @@ enum UnitStandFlags UNIT_STAND_FLAGS_ALL = 0xFF }; +enum UnitBytes0Offsets +{ + UNIT_BYTES_0_OFFSET_RACE = 0, + UNIT_BYTES_0_OFFSET_CLASS = 1, + UNIT_BYTES_0_OFFSET_GENDER = 2, + UNIT_BYTES_0_OFFSET_POWER_TYPE = 3, +}; + +enum UnitBytes1Offsets +{ + UNIT_BYTES_1_OFFSET_STAND_STATE = 0, + UNIT_BYTES_1_OFFSET_PET_TALENTS = 1, + UNIT_BYTES_1_OFFSET_VIS_FLAG = 2, + UNIT_BYTES_1_OFFSET_ANIM_TIER = 3 +}; + +enum UnitBytes2Offsets +{ + UNIT_BYTES_2_OFFSET_SHEATH_STATE = 0, + UNIT_BYTES_2_OFFSET_PVP_FLAG = 1, + UNIT_BYTES_2_OFFSET_PET_FLAG = 2, + UNIT_BYTES_2_OFFSET_SHAPESHIFT = 3, +}; + // byte flags value (UNIT_FIELD_BYTES_1, 3) enum UnitBytes1_Flags { @@ -1314,11 +1338,11 @@ class TC_GAME_API Unit : public WorldObject uint8 getLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); } uint8 getLevelForTarget(WorldObject const* /*target*/) const override { return getLevel(); } void SetLevel(uint8 lvl); - uint8 getRace() const { return GetByteValue(UNIT_FIELD_BYTES_0, 0); } + uint8 getRace() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE); } uint32 getRaceMask() const { return 1 << (getRace()-1); } - uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, 1); } + uint8 getClass() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_CLASS); } uint32 getClassMask() const { return 1 << (getClass()-1); } - uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, 2); } + uint8 getGender() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER); } float GetStat(Stats stat) const { return float(GetUInt32Value(UNIT_FIELD_STAT0+stat)); } void SetStat(Stats stat, int32 val) { SetStatInt32Value(UNIT_FIELD_STAT0+stat, val); } @@ -1347,7 +1371,7 @@ class TC_GAME_API Unit : public WorldObject int32 ModifyHealth(int32 val); int32 GetHealthGain(int32 dVal); - Powers getPowerType() const { return Powers(GetByteValue(UNIT_FIELD_BYTES_0, 3)); } + Powers getPowerType() const { return Powers(GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE)); } void setPowerType(Powers power); uint32 GetPower(Powers power) const { return GetUInt32Value(UNIT_FIELD_POWER1 +power); } uint32 GetMaxPower(Powers power) const { return GetUInt32Value(UNIT_FIELD_MAXPOWER1+power); } @@ -1994,10 +2018,11 @@ class TC_GAME_API Unit : public WorldObject void CalcAbsorbResist(Unit* victim, SpellSchoolMask schoolMask, DamageEffectType damagetype, uint32 const damage, uint32* absorb, uint32* resist, SpellInfo const* spellInfo = NULL); void CalcHealAbsorb(Unit* victim, SpellInfo const* spellInfo, uint32& healAmount, uint32& absorb); - void UpdateSpeed(UnitMoveType mtype, bool forced); + void UpdateSpeed(UnitMoveType mtype); float GetSpeed(UnitMoveType mtype) const; float GetSpeedRate(UnitMoveType mtype) const { return m_speed_rate[mtype]; } - void SetSpeed(UnitMoveType mtype, float rate, bool forced = false); + void SetSpeed(UnitMoveType mtype, float newValue); + void SetSpeedRate(UnitMoveType mtype, float rate); float ApplyEffectModifiers(SpellInfo const* spellProto, uint8 effect_index, float value) const; int32 CalculateSpellDamage(Unit const* target, SpellInfo const* spellProto, uint8 effect_index, int32 const* basePoints = NULL) const; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index fc7d668ae0b..3efeb1ca273 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -4715,7 +4715,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) if (tableName.empty()) return; - if (sScriptMgr->IsScriptScheduled()) // function cannot be called when scripts are in use. + if (sMapMgr->IsScriptScheduled()) // function cannot be called when scripts are in use. return; TC_LOG_INFO("server.loading", "Loading %s...", tableName.c_str()); @@ -5162,7 +5162,7 @@ void ObjectMgr::LoadSpellScriptNames() while (spellInfo) { - _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, GetScriptId(scriptName))); + _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true))); spellInfo = spellInfo->GetNextRankSpell(); } } @@ -5171,7 +5171,7 @@ void ObjectMgr::LoadSpellScriptNames() if (spellInfo->IsRanked()) TC_LOG_ERROR("sql.sql", "Scriptname: `%s` spell (Id: %d) is ranked spell. Perhaps not all ranks are assigned to this script.", scriptName.c_str(), spellId); - _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, GetScriptId(scriptName))); + _spellScriptsStore.insert(SpellScriptsContainer::value_type(spellInfo->Id, std::make_pair(GetScriptId(scriptName), true))); } ++count; @@ -5193,45 +5193,59 @@ void ObjectMgr::ValidateSpellScripts() uint32 count = 0; - for (SpellScriptsContainer::iterator itr = _spellScriptsStore.begin(); itr != _spellScriptsStore.end();) + for (auto spell : _spellScriptsStore) { - SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(itr->first); - std::vector<std::pair<SpellScriptLoader *, SpellScriptsContainer::iterator> > SpellScriptLoaders; - sScriptMgr->CreateSpellScriptLoaders(itr->first, SpellScriptLoaders); - itr = _spellScriptsStore.upper_bound(itr->first); + SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(spell.first); - for (std::vector<std::pair<SpellScriptLoader *, SpellScriptsContainer::iterator> >::iterator sitr = SpellScriptLoaders.begin(); sitr != SpellScriptLoaders.end(); ++sitr) + auto const bounds = sObjectMgr->GetSpellScriptsBounds(spell.first); + + for (auto itr = bounds.first; itr != bounds.second; ++itr) { - SpellScript* spellScript = sitr->first->GetSpellScript(); - AuraScript* auraScript = sitr->first->GetAuraScript(); - bool valid = true; - if (!spellScript && !auraScript) - { - TC_LOG_ERROR("scripts", "Functions GetSpellScript() and GetAuraScript() of script `%s` do not return objects - script skipped", GetScriptName(sitr->second->second).c_str()); - valid = false; - } - if (spellScript) - { - spellScript->_Init(&sitr->first->GetName(), spellEntry->Id); - spellScript->_Register(); - if (!spellScript->_Validate(spellEntry)) - valid = false; - delete spellScript; - } - if (auraScript) + if (SpellScriptLoader* spellScriptLoader = sScriptMgr->GetSpellScriptLoader(itr->second.first)) { - auraScript->_Init(&sitr->first->GetName(), spellEntry->Id); - auraScript->_Register(); - if (!auraScript->_Validate(spellEntry)) - valid = false; - delete auraScript; - } - if (!valid) - { - _spellScriptsStore.erase(sitr->second); + ++count; + + std::unique_ptr<SpellScript> spellScript(spellScriptLoader->GetSpellScript()); + std::unique_ptr<AuraScript> auraScript(spellScriptLoader->GetAuraScript()); + + if (!spellScript && !auraScript) + { + TC_LOG_ERROR("scripts", "Functions GetSpellScript() and GetAuraScript() of script `%s` do not return objects - script skipped", GetScriptName(itr->second.first).c_str()); + + itr->second.second = false; + continue; + } + + if (spellScript) + { + spellScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id); + spellScript->_Register(); + + if (!spellScript->_Validate(spellEntry)) + { + itr->second.second = false; + continue; + } + } + + if (auraScript) + { + auraScript->_Init(&spellScriptLoader->GetName(), spellEntry->Id); + auraScript->_Register(); + + if (!auraScript->_Validate(spellEntry)) + { + itr->second.second = false; + continue; + } + } + + // Enable the script when all checks passed + itr->second.second = true; } + else + itr->second.second = false; } - ++count; } TC_LOG_INFO("server.loading", ">> Validated %u scripts in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); @@ -8571,6 +8585,8 @@ void ObjectMgr::LoadScriptNames() { uint32 oldMSTime = getMSTime(); + // We insert an empty placeholder here so we can use the + // script id 0 as dummy for "no script found". _scriptNamesStore.emplace_back(""); QueryResult result = WorldDatabase.Query( @@ -8612,18 +8628,18 @@ void ObjectMgr::LoadScriptNames() std::sort(_scriptNamesStore.begin(), _scriptNamesStore.end()); -#ifdef SCRIPTS - for (size_t i = 1; i < _scriptNamesStore.size(); ++i) - UnusedScriptNames.push_back(_scriptNamesStore[i]); -#endif - TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " ScriptNames in %u ms", _scriptNamesStore.size(), GetMSTimeDiffToNow(oldMSTime)); } +ObjectMgr::ScriptNameContainer const& ObjectMgr::GetAllScriptNames() const +{ + return _scriptNamesStore; +} + std::string const& ObjectMgr::GetScriptName(uint32 id) const { static std::string const empty = ""; - return id < _scriptNamesStore.size() ? _scriptNamesStore[id] : empty; + return (id < _scriptNamesStore.size()) ? _scriptNamesStore[id] : empty; } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 73c03849699..576a8d2ccac 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -370,7 +370,7 @@ struct ScriptInfo typedef std::multimap<uint32, ScriptInfo> ScriptMap; typedef std::map<uint32, ScriptMap > ScriptMapMap; -typedef std::multimap<uint32, uint32> SpellScriptsContainer; +typedef std::multimap<uint32 /*spell id*/, std::pair<uint32 /*script id*/, bool /*enabled*/>> SpellScriptsContainer; typedef std::pair<SpellScriptsContainer::iterator, SpellScriptsContainer::iterator> SpellScriptsBounds; TC_GAME_API extern ScriptMapMap sSpellScripts; TC_GAME_API extern ScriptMapMap sEventScripts; @@ -1272,6 +1272,7 @@ class TC_GAME_API ObjectMgr bool IsVendorItemValid(uint32 vendor_entry, uint32 item, int32 maxcount, uint32 ptime, uint32 ExtendedCost, Player* player = NULL, std::set<uint32>* skip_vendors = NULL, uint32 ORnpcflag = 0) const; void LoadScriptNames(); + ScriptNameContainer const& GetAllScriptNames() const; std::string const& GetScriptName(uint32 id) const; uint32 GetScriptId(std::string const& name); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index c3c78a4cf66..19638ec1bf8 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -642,7 +642,7 @@ void WorldSession::HandleCharCreateCallback(PreparedQueryResult result, Characte TC_LOG_INFO("entities.player.character", "Account: %d (IP: %s) Create Character:[%s] (GUID: %u)", GetAccountId(), GetRemoteAddress().c_str(), createInfo->Name.c_str(), newChar.GetGUID().GetCounter()); sScriptMgr->OnPlayerCreate(&newChar); - sWorld->AddCharacterInfo(newChar.GetGUID(), GetAccountId(), newChar.GetName(), newChar.GetByteValue(PLAYER_BYTES_3, 0), newChar.getRace(), newChar.getClass(), newChar.getLevel()); + sWorld->AddCharacterInfo(newChar.GetGUID(), GetAccountId(), newChar.GetName(), newChar.GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER), newChar.getRace(), newChar.getClass(), newChar.getLevel()); newChar.CleanupsBeforeDelete(); delete createInfo; @@ -1242,20 +1242,25 @@ void WorldSession::HandleAlterAppearance(WorldPacket& recvData) BarberShopStyleEntry const* bs_hair = sBarberShopStyleStore.LookupEntry(Hair); - if (!bs_hair || bs_hair->type != 0 || bs_hair->race != _player->getRace() || bs_hair->gender != _player->GetByteValue(PLAYER_BYTES_3, 0)) + if (!bs_hair || bs_hair->type != 0 || bs_hair->race != _player->getRace() || bs_hair->gender != _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)) return; BarberShopStyleEntry const* bs_facialHair = sBarberShopStyleStore.LookupEntry(FacialHair); - if (!bs_facialHair || bs_facialHair->type != 2 || bs_facialHair->race != _player->getRace() || bs_facialHair->gender != _player->GetByteValue(PLAYER_BYTES_3, 0)) + if (!bs_facialHair || bs_facialHair->type != 2 || bs_facialHair->race != _player->getRace() || bs_facialHair->gender != _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)) return; BarberShopStyleEntry const* bs_skinColor = sBarberShopStyleStore.LookupEntry(SkinColor); - if (bs_skinColor && (bs_skinColor->type != 3 || bs_skinColor->race != _player->getRace() || bs_skinColor->gender != _player->GetByteValue(PLAYER_BYTES_3, 0))) + if (bs_skinColor && (bs_skinColor->type != 3 || bs_skinColor->race != _player->getRace() || bs_skinColor->gender != _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER))) return; - if (!Player::ValidateAppearance(_player->getRace(), _player->getClass(), _player->GetByteValue(PLAYER_BYTES_3, 0), bs_hair->hair_id, Color, _player->GetByteValue(PLAYER_BYTES, 1), bs_facialHair->hair_id, bs_skinColor ? bs_skinColor->hair_id : _player->GetByteValue(PLAYER_BYTES, 0))) + if (!Player::ValidateAppearance(_player->getRace(), _player->getClass(), _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER), + bs_hair->hair_id, + Color, + _player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID), + bs_facialHair->hair_id, + bs_skinColor ? bs_skinColor->hair_id : _player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID))) return; GameObject* go = _player->FindNearestGameObjectOfType(GAMEOBJECT_TYPE_BARBER_CHAIR, 5.0f); @@ -1287,11 +1292,11 @@ void WorldSession::HandleAlterAppearance(WorldPacket& recvData) _player->ModifyMoney(-int32(cost)); // it isn't free _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_AT_BARBER, cost); - _player->SetByteValue(PLAYER_BYTES, 2, uint8(bs_hair->hair_id)); - _player->SetByteValue(PLAYER_BYTES, 3, uint8(Color)); - _player->SetByteValue(PLAYER_BYTES_2, 0, uint8(bs_facialHair->hair_id)); + _player->SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID, uint8(bs_hair->hair_id)); + _player->SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID, uint8(Color)); + _player->SetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE, uint8(bs_facialHair->hair_id)); if (bs_skinColor) - _player->SetByteValue(PLAYER_BYTES, 0, uint8(bs_skinColor->hair_id)); + _player->SetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID, uint8(bs_skinColor->hair_id)); _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_VISIT_BARBER_SHOP, 1); diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 8ae37065726..467d3730ab2 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -284,7 +284,7 @@ void WorldSession::HandleWhoOpcode(WorldPacket& recvData) continue; uint32 pzoneid = target->GetZoneId(); - uint8 gender = target->GetByteValue(PLAYER_BYTES_3, 0); + uint8 gender = target->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER); bool z_show = true; for (uint32 i = 0; i < zones_count; ++i) @@ -1056,12 +1056,14 @@ void WorldSession::HandleSetActionButtonOpcode(WorldPacket& recvData) void WorldSession::HandleCompleteCinematic(WorldPacket& /*recvData*/) { - TC_LOG_DEBUG("network", "WORLD: Received CMSG_COMPLETE_CINEMATIC"); + // If player has sight bound to visual waypoint NPC we should remove it + GetPlayer()->EndCinematic(); } void WorldSession::HandleNextCinematicCamera(WorldPacket& /*recvData*/) { - TC_LOG_DEBUG("network", "WORLD: Received CMSG_NEXT_CINEMATIC_CAMERA"); + // Sent by client when cinematic actually begun. So we begin the server side process + GetPlayer()->BeginCinematic(); } void WorldSession::HandleMoveTimeSkippedOpcode(WorldPacket& recvData) @@ -1157,7 +1159,7 @@ void WorldSession::HandleSetActionBarToggles(WorldPacket& recvData) return; } - GetPlayer()->SetByteValue(PLAYER_FIELD_BYTES, 2, actionBar); + GetPlayer()->SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES, actionBar); } void WorldSession::HandlePlayedTime(WorldPacket& recvData) diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index 02702fc5622..64f927a987f 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -473,7 +473,7 @@ void WorldSession::HandleForceSpeedChangeAck(WorldPacket &recvData) { TC_LOG_ERROR("network", "%sSpeedChange player %s is NOT correct (must be %f instead %f), force set to correct value", move_type_name[move_type], _player->GetName().c_str(), _player->GetSpeed(move_type), newspeed); - _player->SetSpeed(move_type, _player->GetSpeedRate(move_type), true); + _player->SetSpeedRate(move_type, _player->GetSpeedRate(move_type)); } else // must be lesser - cheating { diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 6be1fd30ae3..e07e10ab00c 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -594,11 +594,11 @@ void WorldSession::HandleMirrorImageDataRequest(WorldPacket& recvData) if (creator->GetTypeId() == TYPEID_PLAYER) { Player* player = creator->ToPlayer(); - data << uint8(player->GetByteValue(PLAYER_BYTES, 0)); // skin - data << uint8(player->GetByteValue(PLAYER_BYTES, 1)); // face - data << uint8(player->GetByteValue(PLAYER_BYTES, 2)); // hair - data << uint8(player->GetByteValue(PLAYER_BYTES, 3)); // haircolor - data << uint8(player->GetByteValue(PLAYER_BYTES_2, 0)); // facialhair + data << uint8(player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID)); + data << uint8(player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID)); + data << uint8(player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID)); + data << uint8(player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID)); + data << uint8(player->GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE)); data << uint32(player->GetGuildId()); // unk static EquipmentSlots const itemSlots[] = diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 1575b50098f..0887d183a8b 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -29,6 +29,8 @@ #include "Pet.h" #include "WorldSession.h" #include "Opcodes.h" +#include "ScriptReloadMgr.h" +#include "ScriptMgr.h" BossBoundaryData::~BossBoundaryData() { @@ -36,6 +38,18 @@ BossBoundaryData::~BossBoundaryData() delete it->Boundary; } +InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0) +{ +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + uint32 scriptId = sObjectMgr->GetInstanceTemplate(map->GetId())->ScriptId; + auto const scriptname = sObjectMgr->GetScriptName(scriptId); + ASSERT(!scriptname.empty()); + // Acquire a strong reference from the script module + // to keep it loaded until this object is destroyed. + module_reference = sScriptMgr->AcquireModuleReferenceOfScriptName(scriptname); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING +} + void InstanceScript::SaveToDB() { std::string data = GetSaveData(); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 949cdf5abcb..3814afe2f4b 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -36,6 +36,7 @@ class Unit; class Player; class GameObject; class Creature; +class ModuleReference; enum EncounterFrameType { @@ -137,7 +138,7 @@ typedef std::map<uint32 /*entry*/, uint32 /*type*/> ObjectInfoMap; class TC_GAME_API InstanceScript : public ZoneScript { public: - explicit InstanceScript(Map* map) : instance(map), completedEncounters(0) { } + explicit InstanceScript(Map* map); virtual ~InstanceScript() { } @@ -289,6 +290,11 @@ class TC_GAME_API InstanceScript : public ZoneScript ObjectInfoMap _gameObjectInfo; ObjectGuidMap _objectGuids; uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets + + #ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Strong reference to the associated script module + std::shared_ptr<ModuleReference> module_reference; + #endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING }; template<class AI, class T> diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 8640150d86b..794be12ee7c 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -17,6 +17,7 @@ */ #include "Map.h" +#include "MapManager.h" #include "Battleground.h" #include "MMapFactory.h" #include "CellImpl.h" @@ -64,7 +65,7 @@ Map::~Map() } if (!m_scriptSchedule.empty()) - sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size()); + sMapMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size()); MMAP::MMapFactory::createOrGetMMapManager()->unloadMapInstance(GetId(), i_InstanceId); } @@ -713,6 +714,15 @@ void Map::Update(const uint32 t_diff) VisitNearbyCellsOf(player, grid_object_update, world_object_update); + // If player is using far sight, visit that object too + if (WorldObject* viewPoint = player->GetViewpoint()) + { + if (Creature* viewCreature = viewPoint->ToCreature()) + VisitNearbyCellsOf(viewCreature, grid_object_update, world_object_update); + else if (DynamicObject* viewObject = viewPoint->ToDynObject()) + VisitNearbyCellsOf(viewObject, grid_object_update, world_object_update); + } + // Handle updates for creatures in combat with player and are more than 60 yards away if (player->IsInCombat()) { @@ -2335,9 +2345,9 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float { // we have mapheight and vmapheight and must select more appropriate - // we are already under the surface or vmap height above map heigt + // vmap height above map height // or if the distance of the vmap height is less the land height distance - if (z < mapHeight || vmapHeight > mapHeight || std::fabs(mapHeight - z) > std::fabs(vmapHeight - z)) + if (vmapHeight > mapHeight || std::fabs(mapHeight - z) > std::fabs(vmapHeight - z)) return vmapHeight; else return mapHeight; // better use .map surface height @@ -2657,8 +2667,8 @@ void Map::UpdateObjectsVisibilityFor(Player* player, Cell cell, CellCoord cellpa cell.SetNoCreate(); TypeContainerVisitor<Trinity::VisibleNotifier, WorldTypeMapContainer > world_notifier(notifier); TypeContainerVisitor<Trinity::VisibleNotifier, GridTypeMapContainer > grid_notifier(notifier); - cell.Visit(cellpair, world_notifier, *this, *player, player->GetSightRange()); - cell.Visit(cellpair, grid_notifier, *this, *player, player->GetSightRange()); + cell.Visit(cellpair, world_notifier, *this, *player->m_seer, player->GetSightRange()); + cell.Visit(cellpair, grid_notifier, *this, *player->m_seer, player->GetSightRange()); // send data notifier.SendToSelf(); diff --git a/src/server/game/Maps/MapManager.cpp b/src/server/game/Maps/MapManager.cpp index c1882b3dc75..79a8b3855e8 100644 --- a/src/server/game/Maps/MapManager.cpp +++ b/src/server/game/Maps/MapManager.cpp @@ -38,10 +38,10 @@ #include "AchievementMgr.h" MapManager::MapManager() + : _nextInstanceId(0), _scheduledScripts(0) { i_gridCleanUpDelay = sWorld->getIntConfig(CONFIG_INTERVAL_GRIDCLEAN); i_timer.SetInterval(sWorld->getIntConfig(CONFIG_INTERVAL_MAPUPDATE)); - _nextInstanceId = 0; } MapManager::~MapManager() { } diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h index e74453a1196..a7fdc37d324 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -126,6 +126,11 @@ class TC_GAME_API MapManager template<typename Worker> void DoForAllMapsWithMapId(uint32 mapId, Worker&& worker); + uint32 IncreaseScheduledScriptsCount() { return ++_scheduledScripts; } + uint32 DecreaseScheduledScriptCount() { return --_scheduledScripts; } + uint32 DecreaseScheduledScriptCount(size_t count) { return _scheduledScripts -= count; } + bool IsScriptScheduled() const { return _scheduledScripts > 0; } + private: typedef std::unordered_map<uint32, Map*> MapMapType; typedef std::vector<bool> InstanceIds; @@ -150,6 +155,9 @@ class TC_GAME_API MapManager InstanceIds _instanceIds; uint32 _nextInstanceId; MapUpdater m_updater; + + // atomic op counter for active scripts amount + std::atomic<uint32> _scheduledScripts; }; template<typename Worker> diff --git a/src/server/game/Scripting/MapScripts.cpp b/src/server/game/Maps/MapScripts.cpp index a01c109b9ca..c5d7bdc64ce 100644 --- a/src/server/game/Scripting/MapScripts.cpp +++ b/src/server/game/Maps/MapScripts.cpp @@ -21,6 +21,7 @@ #include "GridNotifiersImpl.h" #include "GossipDef.h" #include "Map.h" +#include "MapManager.h" #include "ObjectMgr.h" #include "Pet.h" #include "Item.h" @@ -58,7 +59,7 @@ void Map::ScriptsStart(ScriptMapMap const& scripts, uint32 id, Object* source, O if (iter->first == 0) immedScript = true; - sScriptMgr->IncreaseScheduledScriptsCount(); + sMapMgr->IncreaseScheduledScriptsCount(); } ///- If one of the effects should be immediate, launch the script execution if (/*start &&*/ immedScript && !i_scriptLock) @@ -86,7 +87,7 @@ void Map::ScriptCommandStart(ScriptInfo const& script, uint32 delay, Object* sou sa.script = &script; m_scriptSchedule.insert(ScriptScheduleMap::value_type(time_t(sWorld->GetGameTime() + delay), sa)); - sScriptMgr->IncreaseScheduledScriptsCount(); + sMapMgr->IncreaseScheduledScriptsCount(); ///- If effects should be immediate, launch the script execution if (delay == 0 && !i_scriptLock) @@ -878,6 +879,6 @@ void Map::ScriptsProcess() m_scriptSchedule.erase(iter); iter = m_scriptSchedule.begin(); - sScriptMgr->DecreaseScheduledScriptCount(); + sMapMgr->DecreaseScheduledScriptCount(); } } diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index b595059557a..d3c6c70c4ed 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -265,9 +265,9 @@ void FollowMovementGenerator<Creature>::_updateSpeed(Creature* owner) if (!owner->IsPet() || !owner->IsInWorld() || !i_target.isValid() || i_target->GetGUID() != owner->GetOwnerGUID()) return; - owner->UpdateSpeed(MOVE_RUN, true); - owner->UpdateSpeed(MOVE_WALK, true); - owner->UpdateSpeed(MOVE_SWIM, true); + owner->UpdateSpeed(MOVE_RUN); + owner->UpdateSpeed(MOVE_WALK); + owner->UpdateSpeed(MOVE_SWIM); } template<> diff --git a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp index 7f0695e16b3..aa00d211d6e 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp @@ -33,8 +33,14 @@ void OutdoorPvPMgr::Die() for (OutdoorPvPSet::iterator itr = m_OutdoorPvPSet.begin(); itr != m_OutdoorPvPSet.end(); ++itr) delete *itr; + m_OutdoorPvPSet.clear(); + for (OutdoorPvPDataMap::iterator itr = m_OutdoorPvPDatas.begin(); itr != m_OutdoorPvPDatas.end(); ++itr) delete itr->second; + + m_OutdoorPvPDatas.clear(); + + m_OutdoorPvPMap.clear(); } OutdoorPvPMgr* OutdoorPvPMgr::instance() diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 4218746893f..ca1cd71363e 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -17,6 +17,7 @@ */ #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "Config.h" #include "DatabaseEnv.h" #include "DBCStores.h" @@ -34,11 +35,9 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "Chat.h" - -// namespace -// { - UnusedScriptNamesContainer UnusedScriptNames; -// } +#include "MapManager.h" +#include "LFGScripts.h" +#include "InstanceScript.h" // Trait which indicates whether this script type // must be assigned in the database. @@ -67,6 +66,10 @@ struct is_script_database_bound<GameObjectScript> : std::true_type { }; template<> +struct is_script_database_bound<VehicleScript> + : std::true_type { }; + +template<> struct is_script_database_bound<AreaTriggerScript> : std::true_type { }; @@ -94,143 +97,846 @@ template<> struct is_script_database_bound<AchievementCriteriaScript> : std::true_type { }; +enum Spells +{ + SPELL_HOTSWAP_VISUAL_SPELL_EFFECT = 40162 // 59084 +}; + +class ScriptRegistryInterface +{ +public: + ScriptRegistryInterface() { } + virtual ~ScriptRegistryInterface() { } + + ScriptRegistryInterface(ScriptRegistryInterface const&) = delete; + ScriptRegistryInterface(ScriptRegistryInterface&&) = delete; + + ScriptRegistryInterface& operator= (ScriptRegistryInterface const&) = delete; + ScriptRegistryInterface& operator= (ScriptRegistryInterface&&) = delete; + + /// Removes all scripts associated with the given script context. + /// Requires ScriptRegistryBase::SwapContext to be called after all transfers have finished. + virtual void ReleaseContext(std::string const& context) = 0; + + /// Injects and updates the changed script objects. + virtual void SwapContext(bool initialize) = 0; + + /// Removes the scripts used by this registry from the given container. + /// Used to find unused script names. + virtual void RemoveUsedScriptsFromContainer(std::unordered_set<std::string>& scripts) = 0; + + /// Unloads the script registry. + virtual void Unload() = 0; +}; + +template<class> +class ScriptRegistry; + +class ScriptRegistryCompositum + : public ScriptRegistryInterface +{ + ScriptRegistryCompositum() { } + + template<class> + friend class ScriptRegistry; + + /// Type erasure wrapper for objects + class DeleteableObjectBase + { + public: + DeleteableObjectBase() { } + virtual ~DeleteableObjectBase() { } + + DeleteableObjectBase(DeleteableObjectBase const&) = delete; + DeleteableObjectBase& operator= (DeleteableObjectBase const&) = delete; + }; + + template<typename T> + class DeleteableObject + : public DeleteableObjectBase + { + public: + DeleteableObject(T&& object) + : _object(std::forward<T>(object)) { } + + private: + T _object; + }; + +public: + void SetScriptNameInContext(std::string const& scriptname, std::string const& context) + { + ASSERT(_scriptnames_to_context.find(scriptname) == _scriptnames_to_context.end(), + "Scriptname was assigned to this context already!"); + _scriptnames_to_context.insert(std::make_pair(scriptname, context)); + } + + std::string const& GetScriptContextOfScriptName(std::string const& scriptname) const + { + auto itr = _scriptnames_to_context.find(scriptname); + ASSERT(itr != _scriptnames_to_context.end() && + "Given scriptname doesn't exist!"); + return itr->second; + } + + void ReleaseContext(std::string const& context) final override + { + for (auto const registry : _registries) + registry->ReleaseContext(context); + + // Clear the script names in context after calling the release hooks + // since it's possible that new references to a shared library + // are acquired when releasing. + for (auto itr = _scriptnames_to_context.begin(); + itr != _scriptnames_to_context.end();) + if (itr->second == context) + itr = _scriptnames_to_context.erase(itr); + else + ++itr; + } + + void SwapContext(bool initialize) final override + { + for (auto const registry : _registries) + registry->SwapContext(initialize); + + DoDelayedDelete(); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set<std::string>& scripts) final override + { + for (auto const registry : _registries) + registry->RemoveUsedScriptsFromContainer(scripts); + } + + void Unload() final override + { + for (auto const registry : _registries) + registry->Unload(); + } + + template<typename T> + void QueueForDelayedDelete(T&& any) + { + _delayed_delete_queue.push_back( + Trinity::make_unique< + DeleteableObject<typename std::decay<T>::type> + >(std::forward<T>(any)) + ); + } + + static ScriptRegistryCompositum* Instance() + { + static ScriptRegistryCompositum instance; + return &instance; + } + +private: + void Register(ScriptRegistryInterface* registry) + { + _registries.insert(registry); + } + + void DoDelayedDelete() + { + _delayed_delete_queue.clear(); + } + + std::unordered_set<ScriptRegistryInterface*> _registries; + + std::vector<std::unique_ptr<DeleteableObjectBase>> _delayed_delete_queue; + + std::unordered_map< + std::string /*script name*/, + std::string /*context*/ + > _scriptnames_to_context; +}; + +#define sScriptRegistryCompositum ScriptRegistryCompositum::Instance() + +template<typename /*ScriptType*/, bool /*IsDatabaseBound*/> +class SpecializedScriptRegistry; + // This is the global static registry of scripts. -template<class TScript> -class ScriptRegistry +template<class ScriptType> +class ScriptRegistry final + : public SpecializedScriptRegistry< + ScriptType, is_script_database_bound<ScriptType>::value> +{ + ScriptRegistry() + { + sScriptRegistryCompositum->Register(this); + } + +public: + static ScriptRegistry* Instance() + { + static ScriptRegistry instance; + return &instance; + } + + void LogDuplicatedScriptPointerError(ScriptType const* first, ScriptType const* second) + { + // See if the script is using the same memory as another script. If this happens, it means that + // someone forgot to allocate new memory for a script. + TC_LOG_ERROR("scripts", "Script '%s' has same memory pointer as '%s'.", + first->GetName().c_str(), second->GetName().c_str()); + } +}; + +class ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHookBase() { } + virtual ~ScriptRegistrySwapHookBase() { } + + ScriptRegistrySwapHookBase(ScriptRegistrySwapHookBase const&) = delete; + ScriptRegistrySwapHookBase(ScriptRegistrySwapHookBase&&) = delete; + + ScriptRegistrySwapHookBase& operator= (ScriptRegistrySwapHookBase const&) = delete; + ScriptRegistrySwapHookBase& operator= (ScriptRegistrySwapHookBase&&) = delete; + + /// Called before the actual context release happens + virtual void BeforeReleaseContext(std::string const& /*context*/) { } + + /// Called before SwapContext + virtual void BeforeSwapContext(bool /*initialize*/) { } + + /// Called before Unload + virtual void BeforeUnload() { } +}; + +template<typename ScriptType, typename Base> +class ScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +}; + +/// This hook is responsible for swapping OutdoorPvP's +template<typename Base> +class UnsupportedScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase +{ +public: + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + ASSERT(bounds.first == bounds.second); + } +}; + +/// This hook is responsible for swapping Creature and GameObject AI's +template<typename ObjectType, typename ScriptType, typename Base> +class CreatureGameObjectScriptRegistrySwapHooks + : public ScriptRegistrySwapHookBase { + template<typename W> + class AIFunctionMapWorker + { + public: + template<typename T> + AIFunctionMapWorker(T&& worker) + : _worker(std::forward<T>(worker)) { } + + void Visit(std::unordered_map<ObjectGuid, ObjectType*>& objects) + { + _worker(objects); + } + + template<typename O> + void Visit(std::unordered_map<ObjectGuid, O*>&) { } + + private: + W _worker; + }; + + class AsyncCastHotswapEffectEvent : public BasicEvent + { public: + explicit AsyncCastHotswapEffectEvent(Unit* owner) : owner_(owner) { } + + bool Execute(uint64 /*e_time*/, uint32 /*p_time*/) override + { + owner_->CastSpell(owner_, SPELL_HOTSWAP_VISUAL_SPELL_EFFECT, true); + return true; + } + + private: + Unit* owner_; + }; + + // Hook which is called before a creature is swapped + static void UnloadStage1(Creature* creature) + { + if (creature->IsCharmed()) + creature->RemoveCharmedBy(nullptr); + + ASSERT(!creature->IsCharmed(), + "There is a disabled AI which is still loaded."); - typedef std::map<uint32, TScript*> ScriptMap; - typedef typename ScriptMap::iterator ScriptMapIterator; + creature->AI()->EnterEvadeMode(); + } + + static void UnloadStage2(Creature* creature) + { + bool const destroyed = creature->AIM_Destroy(); + ASSERT(destroyed, + "Destroying the AI should never fail here!"); + (void)destroyed; - // The actual list of scripts. This will be accessed concurrently, so it must not be modified - // after server startup. - static ScriptMap ScriptPointerList; - static std::vector<TScript*> Scripts; + ASSERT(!creature->AI(), + "The AI should be null here!"); + } - static void AddScript(TScript* const script, bool addToDeleteContainer = true) + // Hook which is called before a gameobject is swapped + static void UnloadStage1(GameObject* gameobject) + { + gameobject->AI()->Reset(); + } + + static void UnloadStage2(GameObject* gameobject) + { + gameobject->AIM_Destroy(); + + ASSERT(!gameobject->AI(), + "The AI should be null here!"); + } + + // Hook which is called after a creature was swapped + static void LoadStage1(Creature* creature) + { + ASSERT(!creature->AI(), + "The AI should be null here!"); + + if (creature->IsAlive()) + creature->ClearUnitState(UNIT_STATE_EVADE); + + bool const created = creature->AIM_Initialize(); + ASSERT(created, + "Creating the AI should never fail here!"); + (void)created; + } + + static void LoadStage2(Creature* creature) + { + if (!creature->IsAlive()) + return; + + creature->AI()->EnterEvadeMode(); + + // Cast a dummy visual spell asynchronously here to signal + // that the AI was hot swapped + creature->m_Events.AddEvent(new AsyncCastHotswapEffectEvent(creature), + creature->m_Events.CalculateTime(0)); + } + + // Hook which is called after a gameobject was swapped + static void LoadStage1(GameObject* gameobject) + { + ASSERT(!gameobject->AI(), + "The AI should be null here!"); + + gameobject->AIM_Initialize(); + } + + static void LoadStage2(GameObject* gameobject) + { + gameobject->AI()->Reset(); + } + + template<typename T> + void RunOverAllEntities(T fn) + { + auto evaluator = [&](std::unordered_map<ObjectGuid, ObjectType*>& objects) { - ASSERT(script); + for (auto object : objects) + fn(object.second); + }; - // See if the script is using the same memory as another script. If this happens, it means that - // someone forgot to allocate new memory for a script. - for (ScriptMapIterator it = ScriptPointerList.begin(); it != ScriptPointerList.end(); ++it) + AIFunctionMapWorker<typename std::decay<decltype(evaluator)>::type> worker(std::move(evaluator)); + TypeContainerVisitor<decltype(worker), MapStoredObjectTypesContainer> visitor(worker); + + sMapMgr->DoForAllMaps([&](Map* map) + { + // Run the worker over all maps + visitor.Visit(map->GetObjectsStore()); + }); + } + +public: + void BeforeReleaseContext(std::string const& context) final override + { + auto ids_to_remove = static_cast<Base*>(this)->GetScriptIDsToRemove(context); + + std::vector<ObjectType*> stage2; + + RunOverAllEntities([&](ObjectType* object) + { + if (ids_to_remove.find(object->GetScriptId()) != ids_to_remove.end()) { - if (it->second == script) - { - TC_LOG_ERROR("scripts", "Script '%s' has same memory pointer as '%s'.", - script->GetName().c_str(), it->second->GetName().c_str()); + UnloadStage1(object); + stage2.push_back(object); + } + }); - return; + for (auto object : stage2) + UnloadStage2(object); + + // Add the new ids which are removed to the global ids to remove set + ids_removed_.insert(ids_to_remove.begin(), ids_to_remove.end()); + } + + void BeforeSwapContext(bool initialize) override + { + // Never swap creature or gameobject scripts when initializing + if (initialize) + return; + + // Add the recently added scripts to the deleted scripts to replace + // default AI's with recently added core scripts. + ids_removed_.insert(static_cast<Base*>(this)->GetRecentlyAddedScriptIDs().begin(), + static_cast<Base*>(this)->GetRecentlyAddedScriptIDs().end()); + + std::vector<ObjectType*> remove; + std::vector<ObjectType*> stage2; + + RunOverAllEntities([&](ObjectType* object) + { + if (ids_removed_.find(object->GetScriptId()) != ids_removed_.end()) + { + if (object->AI()) + { + // Overwrite existing (default) AI's which are replaced by a new script + UnloadStage1(object); + remove.push_back(object); } + + stage2.push_back(object); } + }); + + for (auto object : remove) + UnloadStage2(object); - AddScript(is_script_database_bound<TScript>{}, script); - if (addToDeleteContainer) - Scripts.push_back(script); + for (auto object : stage2) + LoadStage1(object); + + for (auto object : stage2) + LoadStage2(object); + + ids_removed_.clear(); + } + + void BeforeUnload() final override + { + ASSERT(ids_removed_.empty()); + } + +private: + std::unordered_set<uint32> ids_removed_; +}; + +// This hook is responsible for swapping CreatureAI's +template<typename Base> +class ScriptRegistrySwapHooks<CreatureScript, Base> + : public CreatureGameObjectScriptRegistrySwapHooks< + Creature, CreatureScript, Base + > { }; + +// This hook is responsible for swapping GameObjectAI's +template<typename Base> +class ScriptRegistrySwapHooks<GameObjectScript, Base> + : public CreatureGameObjectScriptRegistrySwapHooks< + GameObject, GameObjectScript, Base + > { }; + +/// This hook is responsible for swapping BattlegroundScript's +template<typename Base> +class ScriptRegistrySwapHooks<BattlegroundScript, Base> + : public UnsupportedScriptRegistrySwapHooks<Base> { }; + +/// This hook is responsible for swapping OutdoorPvP's +template<typename Base> +class ScriptRegistrySwapHooks<OutdoorPvPScript, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + + if ((!swapped) && (bounds.first != bounds.second)) + { + swapped = true; + sOutdoorPvPMgr->Die(); } + } - // Gets a script by its ID (assigned by ObjectMgr). - static TScript* GetScriptById(uint32 id) + void BeforeSwapContext(bool initialize) override + { + // Never swap outdoor pvp scripts when initializing + if ((!initialize) && swapped) { - ScriptMapIterator it = ScriptPointerList.find(id); - if (it != ScriptPointerList.end()) - return it->second; + sOutdoorPvPMgr->InitOutdoorPvP(); + swapped = false; + } + } + + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +/// This hook is responsible for swapping InstanceMapScript's +template<typename Base> +class ScriptRegistrySwapHooks<InstanceMapScript, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } - return NULL; + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + if (bounds.first != bounds.second) + swapped = true; + } + + void BeforeSwapContext(bool /*initialize*/) override + { + swapped = false; + } + + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +/// This hook is responsible for swapping SpellScriptLoader's +template<typename Base> +class ScriptRegistrySwapHooks<SpellScriptLoader, Base> + : public ScriptRegistrySwapHookBase +{ +public: + ScriptRegistrySwapHooks() : swapped(false) { } + + void BeforeReleaseContext(std::string const& context) final override + { + auto const bounds = static_cast<Base*>(this)->_ids_of_contexts.equal_range(context); + + if (bounds.first != bounds.second) + swapped = true; + } + + void BeforeSwapContext(bool /*initialize*/) override + { + if (swapped) + { + sObjectMgr->ValidateSpellScripts(); + swapped = false; } + } - private: + void BeforeUnload() final override + { + ASSERT(!swapped); + } + +private: + bool swapped; +}; + +// Database bound script registry +template<typename ScriptType> +class SpecializedScriptRegistry<ScriptType, true> + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks<ScriptType, ScriptRegistry<ScriptType>> +{ + template<typename> + friend class UnsupportedScriptRegistrySwapHooks; + + template<typename, typename> + friend class ScriptRegistrySwapHooks; + + template<typename, typename, typename> + friend class CreatureGameObjectScriptRegistrySwapHooks; + +public: + SpecializedScriptRegistry() { } + + typedef std::unordered_map< + uint32 /*script id*/, + std::unique_ptr<ScriptType> + > ScriptStoreType; + + typedef typename ScriptStoreType::iterator ScriptStoreIteratorType; + + void ReleaseContext(std::string const& context) final override + { + this->BeforeReleaseContext(context); + + auto const bounds = _ids_of_contexts.equal_range(context); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + _scripts.erase(itr->second); + } + + void SwapContext(bool initialize) final override + { + this->BeforeSwapContext(initialize); + + _recently_added_ids.clear(); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set<std::string>& scripts) final override + { + for (auto const& script : _scripts) + scripts.erase(script.second->GetName()); + } + + void Unload() final override + { + this->BeforeUnload(); + + ASSERT(_recently_added_ids.empty(), + "Recently added script ids should be empty here!"); + + _scripts.clear(); + _ids_of_contexts.clear(); + } + + // Adds a database bound script + void AddScript(ScriptType* script) + { + ASSERT(script, + "Tried to call AddScript with a nullpointer!"); + ASSERT(!sScriptMgr->GetCurrentScriptContext().empty(), + "Tried to register a script without being in a valid script context!"); - // Adds a database bound script - static void AddScript(std::true_type, TScript* const script) + std::unique_ptr<ScriptType> script_ptr(script); + + // Get an ID for the script. An ID only exists if it's a script that is assigned in the database + // through a script name (or similar). + if (uint32 const id = sObjectMgr->GetScriptId(script->GetName())) { - // Get an ID for the script. An ID only exists if it's a script that is assigned in the database - // through a script name (or similar). - uint32 id = sObjectMgr->GetScriptId(script->GetName()); - if (id) + // Try to find an existing script. + for (auto const& stored_script : _scripts) { - // Try to find an existing script. - bool existing = false; - for (ScriptMapIterator it = ScriptPointerList.begin(); it != ScriptPointerList.end(); ++it) - { - // If the script names match... - if (it->second->GetName() == script->GetName()) - { - // ... It exists. - existing = true; - break; - } - } - - // If the script isn't assigned -> assign it! - if (!existing) - { - ScriptPointerList[id] = script; - sScriptMgr->IncrementScriptCount(); - - #ifdef SCRIPTS - UnusedScriptNamesContainer::iterator itr = std::lower_bound(UnusedScriptNames.begin(), UnusedScriptNames.end(), script->GetName()); - if (itr != UnusedScriptNames.end() && *itr == script->GetName()) - UnusedScriptNames.erase(itr); - #endif - } - else + // If the script names match... + if (stored_script.second->GetName() == script->GetName()) { // If the script is already assigned -> delete it! - TC_LOG_ERROR("scripts", "Script '%s' already assigned with the same script name, so the script can't work.", - script->GetName().c_str()); + TC_LOG_ERROR("scripts", "Script '%s' already assigned with the same script name, " + "so the script can't work.", script->GetName().c_str()); - ABORT(); // Error that should be fixed ASAP. + // Error that should be fixed ASAP. + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + ABORT(); + return; } } - else - { - // The script uses a script name from database, but isn't assigned to anything. - TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", script->GetName().c_str()); - } - } - // Adds a non database bound script - static void AddScript(std::false_type, TScript* const script) + // If the script isn't assigned -> assign it! + _scripts.insert(std::make_pair(id, std::move(script_ptr))); + _ids_of_contexts.insert(std::make_pair(sScriptMgr->GetCurrentScriptContext(), id)); + _recently_added_ids.insert(id); + + sScriptRegistryCompositum->SetScriptNameInContext(script->GetName(), + sScriptMgr->GetCurrentScriptContext()); + } + else { - // We're dealing with a code-only script; just add it. - ScriptPointerList[_scriptIdCounter++] = script; - sScriptMgr->IncrementScriptCount(); + // The script uses a script name from database, but isn't assigned to anything. + TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", + script->GetName().c_str()); + + // Avoid calling "delete script;" because we are currently in the script constructor + // In a valid scenario this will not happen because every script has a name assigned in the database + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + return; } + } + + // Gets a script by its ID (assigned by ObjectMgr). + ScriptType* GetScriptById(uint32 id) + { + auto const itr = _scripts.find(id); + if (itr != _scripts.end()) + return itr->second.get(); + + return nullptr; + } + + ScriptStoreType& GetScripts() + { + return _scripts; + } - // Counter used for code-only scripts. - static uint32 _scriptIdCounter; +protected: + // Returns the script id's which are registered to a certain context + std::unordered_set<uint32> GetScriptIDsToRemove(std::string const& context) const + { + // Create a set of all ids which are removed + std::unordered_set<uint32> scripts_to_remove; + + auto const bounds = _ids_of_contexts.equal_range(context); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + scripts_to_remove.insert(itr->second); + + return scripts_to_remove; + } + + std::unordered_set<uint32> const& GetRecentlyAddedScriptIDs() const + { + return _recently_added_ids; + } + +private: + ScriptStoreType _scripts; + + // Scripts of a specific context + std::unordered_multimap<std::string /*context*/, uint32 /*id*/> _ids_of_contexts; + + // Script id's which were registered recently + std::unordered_set<uint32> _recently_added_ids; +}; + +/// This hook is responsible for swapping CommandScript's +template<typename Base> +class ScriptRegistrySwapHooks<CommandScript, Base> + : public ScriptRegistrySwapHookBase +{ +public: + void BeforeReleaseContext(std::string const& /*context*/) final override + { + ChatHandler::invalidateCommandTable(); + } + + void BeforeSwapContext(bool /*initialize*/) override + { + ChatHandler::invalidateCommandTable(); + } + + void BeforeUnload() final override + { + ChatHandler::invalidateCommandTable(); + } +}; + +// Database unbound script registry +template<typename ScriptType> +class SpecializedScriptRegistry<ScriptType, false> + : public ScriptRegistryInterface, + public ScriptRegistrySwapHooks<ScriptType, ScriptRegistry<ScriptType>> +{ + template<typename, typename> + friend class ScriptRegistrySwapHooks; + +public: + typedef std::unordered_multimap<std::string /*context*/, std::unique_ptr<ScriptType>> ScriptStoreType; + typedef typename ScriptStoreType::iterator ScriptStoreIteratorType; + + SpecializedScriptRegistry() { } + + void ReleaseContext(std::string const& context) final override + { + this->BeforeReleaseContext(context); + + _scripts.erase(context); + } + + void SwapContext(bool initialize) final override + { + this->BeforeSwapContext(initialize); + } + + void RemoveUsedScriptsFromContainer(std::unordered_set<std::string>& scripts) final override + { + for (auto const& script : _scripts) + scripts.erase(script.second->GetName()); + } + + void Unload() final override + { + this->BeforeUnload(); + + _scripts.clear(); + } + + // Adds a non database bound script + void AddScript(ScriptType* script) + { + ASSERT(script, + "Tried to call AddScript with a nullpointer!"); + ASSERT(!sScriptMgr->GetCurrentScriptContext().empty(), + "Tried to register a script without being in a valid script context!"); + + std::unique_ptr<ScriptType> script_ptr(script); + + for (auto const& entry : _scripts) + if (entry.second.get() == script) + { + static_cast<ScriptRegistry<ScriptType>*>(this)-> + LogDuplicatedScriptPointerError(script, entry.second.get()); + + sScriptRegistryCompositum->QueueForDelayedDelete(std::move(script_ptr)); + return; + } + + // We're dealing with a code-only script, just add it. + _scripts.insert(std::make_pair(sScriptMgr->GetCurrentScriptContext(), std::move(script_ptr))); + } + + ScriptStoreType& GetScripts() + { + return _scripts; + } + +private: + ScriptStoreType _scripts; }; // Utility macros to refer to the script registry. -#define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptMap -#define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptMapIterator -#define SCR_REG_LST(T) ScriptRegistry<T>::ScriptPointerList -#define SCR_REG_VEC(T) ScriptRegistry<T>::Scripts +#define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptStoreType +#define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptStoreIteratorType +#define SCR_REG_LST(T) ScriptRegistry<T>::Instance()->GetScripts() // Utility macros for looping over scripts. #define FOR_SCRIPTS(T, C, E) \ if (SCR_REG_LST(T).empty()) \ return; \ + \ for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ C != SCR_REG_LST(T).end(); ++C) + #define FOR_SCRIPTS_RET(T, C, E, R) \ if (SCR_REG_LST(T).empty()) \ return R; \ + \ for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ C != SCR_REG_LST(T).end(); ++C) + #define FOREACH_SCRIPT(T) \ FOR_SCRIPTS(T, itr, end) \ - itr->second + itr->second // Utility macros for finding specific scripts. #define GET_SCRIPT(T, I, V) \ - T* V = ScriptRegistry<T>::GetScriptById(I); \ + T* V = ScriptRegistry<T>::Instance()->GetScriptById(I); \ if (!V) \ return; + #define GET_SCRIPT_RET(T, I, V, R) \ - T* V = ScriptRegistry<T>::GetScriptById(I); \ + T* V = ScriptRegistry<T>::Instance()->GetScriptById(I); \ if (!V) \ return R; @@ -240,8 +946,18 @@ struct TSpellSummary uint8 Effects; // set of enum SelectEffect } *SpellSummary; +ScriptObject::ScriptObject(const char* name) : _name(name) +{ + sScriptMgr->IncreaseScriptCount(); +} + +ScriptObject::~ScriptObject() +{ + sScriptMgr->DecreaseScriptCount(); +} + ScriptMgr::ScriptMgr() - : _scriptCount(0), _scheduledScripts(0), _script_loader_callback(nullptr) + : _scriptCount(0), _script_loader_callback(nullptr) { } @@ -255,6 +971,9 @@ ScriptMgr* ScriptMgr::instance() void ScriptMgr::Initialize() { + ASSERT(sSpellMgr->GetSpellInfo(SPELL_HOTSWAP_VISUAL_SPELL_EFFECT) + && "Reload hotswap spell effect for creatures isn't valid!"); + uint32 oldMSTime = getMSTime(); LoadDatabase(); @@ -263,59 +982,85 @@ void ScriptMgr::Initialize() FillSpellSummary(); + // Load core scripts + SetScriptContext("___static___"); + + // SmartAI AddSC_SmartScripts(); + // LFGScripts + lfg::AddSC_LFGScripts(); + + // Load all static linked scripts through the script loader function. ASSERT(_script_loader_callback, "Script loader callback wasn't registered!"); - _script_loader_callback(); -#ifdef SCRIPTS - for (std::string const& scriptName : UnusedScriptNames) + // Initialize all dynamic scripts + // and finishes the context switch to do + // bulk loading + sScriptReloadMgr->Initialize(); + + // Loads all scripts from the current context + sScriptMgr->SwapScriptContext(true); + + // Print unused script names. + std::unordered_set<std::string> unusedScriptNames( + sObjectMgr->GetAllScriptNames().begin(), + sObjectMgr->GetAllScriptNames().end()); + + // Remove the used scripts from the given container. + sScriptRegistryCompositum->RemoveUsedScriptsFromContainer(unusedScriptNames); + + for (std::string const& scriptName : unusedScriptNames) { - TC_LOG_ERROR("sql.sql", "ScriptName '%s' exists in database, but no core script found!", scriptName.c_str()); + // Avoid complaining about empty script names since the + // script name container contains a placeholder as the 0 element. + if (scriptName.empty()) + continue; + + TC_LOG_ERROR("sql.sql", "ScriptName '%s' exists in database, " + "but no core script found!", scriptName.c_str()); } -#endif - TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", GetScriptCount(), GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", + GetScriptCount(), GetMSTimeDiffToNow(oldMSTime)); +} + +void ScriptMgr::SetScriptContext(std::string const& context) +{ + _currentContext = context; +} + +void ScriptMgr::SwapScriptContext(bool initialize) +{ + sScriptRegistryCompositum->SwapContext(initialize); + _currentContext.clear(); +} + +void ScriptMgr::ReleaseScriptContext(std::string const& context) +{ + sScriptRegistryCompositum->ReleaseContext(context); +} + +std::shared_ptr<ModuleReference> + ScriptMgr::AcquireModuleReferenceOfScriptName(std::string const& scriptname) const +{ +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Returns the reference to the module of the given scriptname + return ScriptReloadMgr::AcquireModuleReferenceOfContext( + sScriptRegistryCompositum->GetScriptContextOfScriptName(scriptname)); +#else + (void)scriptname; + // Something went wrong when this function is used in + // a static linked context. + WPAbort(); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING } void ScriptMgr::Unload() { - #define SCR_CLEAR(T) \ - for (T* scr : SCR_REG_VEC(T)) \ - delete scr; \ - SCR_REG_VEC(T).clear(); - - // Clear scripts for every script type. - SCR_CLEAR(SpellScriptLoader); - SCR_CLEAR(ServerScript); - SCR_CLEAR(WorldScript); - SCR_CLEAR(FormulaScript); - SCR_CLEAR(WorldMapScript); - SCR_CLEAR(InstanceMapScript); - SCR_CLEAR(BattlegroundMapScript); - SCR_CLEAR(ItemScript); - SCR_CLEAR(CreatureScript); - SCR_CLEAR(GameObjectScript); - SCR_CLEAR(AreaTriggerScript); - SCR_CLEAR(BattlegroundScript); - SCR_CLEAR(OutdoorPvPScript); - SCR_CLEAR(CommandScript); - SCR_CLEAR(WeatherScript); - SCR_CLEAR(AuctionHouseScript); - SCR_CLEAR(ConditionScript); - SCR_CLEAR(VehicleScript); - SCR_CLEAR(DynamicObjectScript); - SCR_CLEAR(TransportScript); - SCR_CLEAR(AchievementCriteriaScript); - SCR_CLEAR(PlayerScript); - SCR_CLEAR(AccountScript); - SCR_CLEAR(GuildScript); - SCR_CLEAR(GroupScript); - SCR_CLEAR(UnitScript); - - #undef SCR_CLEAR + sScriptRegistryCompositum->Unload(); delete[] SpellSummary; delete[] UnitAI::AISpellInfo; @@ -413,38 +1158,22 @@ void ScriptMgr::FillSpellSummary() } } -void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& scriptVector) +template<typename T, typename F> +void CreateSpellOrAuraScripts(uint32 spellId, std::list<T*>& scriptVector, F&& extractor) { SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) { - SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::GetScriptById(itr->second); - if (!tmpscript) + // When the script is disabled continue with the next one + if (!itr->second.second) continue; - SpellScript* script = tmpscript->GetSpellScript(); - - if (!script) - continue; - - script->_Init(&tmpscript->GetName(), spellId); - - scriptVector.push_back(script); - } -} - -void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& scriptVector) -{ - SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); - - for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) - { - SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::GetScriptById(itr->second); + SpellScriptLoader* tmpscript = sScriptMgr->GetSpellScriptLoader(itr->second.first); if (!tmpscript) continue; - AuraScript* script = tmpscript->GetAuraScript(); + T* script = (*tmpscript.*extractor)(); if (!script) continue; @@ -455,19 +1184,19 @@ void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& script } } -void ScriptMgr::CreateSpellScriptLoaders(uint32 spellId, std::vector<std::pair<SpellScriptLoader*, SpellScriptsContainer::iterator> >& scriptVector) +void ScriptMgr::CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& scriptVector) { - SpellScriptsBounds bounds = sObjectMgr->GetSpellScriptsBounds(spellId); - scriptVector.reserve(std::distance(bounds.first, bounds.second)); + CreateSpellOrAuraScripts(spellId, scriptVector, &SpellScriptLoader::GetSpellScript); +} - for (SpellScriptsContainer::iterator itr = bounds.first; itr != bounds.second; ++itr) - { - SpellScriptLoader* tmpscript = ScriptRegistry<SpellScriptLoader>::GetScriptById(itr->second); - if (!tmpscript) - continue; +void ScriptMgr::CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& scriptVector) +{ + CreateSpellOrAuraScripts(spellId, scriptVector, &SpellScriptLoader::GetAuraScript); +} - scriptVector.push_back(std::make_pair(tmpscript, itr)); - } +SpellScriptLoader* ScriptMgr::GetSpellScriptLoader(uint32 scriptId) +{ + return ScriptRegistry<SpellScriptLoader>::Instance()->GetScriptById(scriptId); } void ScriptMgr::OnNetworkStart() @@ -1495,56 +2224,62 @@ void ScriptMgr::OnGroupDisband(Group* group) void ScriptMgr::OnHeal(Unit* healer, Unit* reciever, uint32& gain) { FOREACH_SCRIPT(UnitScript)->OnHeal(healer, reciever, gain); + FOREACH_SCRIPT(PlayerScript)->OnHeal(healer, reciever, gain); } void ScriptMgr::OnDamage(Unit* attacker, Unit* victim, uint32& damage) { FOREACH_SCRIPT(UnitScript)->OnDamage(attacker, victim, damage); + FOREACH_SCRIPT(PlayerScript)->OnDamage(attacker, victim, damage); } void ScriptMgr::ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage) { FOREACH_SCRIPT(UnitScript)->ModifyPeriodicDamageAurasTick(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifyPeriodicDamageAurasTick(target, attacker, damage); } void ScriptMgr::ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage) { FOREACH_SCRIPT(UnitScript)->ModifyMeleeDamage(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifyMeleeDamage(target, attacker, damage); } void ScriptMgr::ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage) { FOREACH_SCRIPT(UnitScript)->ModifySpellDamageTaken(target, attacker, damage); + FOREACH_SCRIPT(PlayerScript)->ModifySpellDamageTaken(target, attacker, damage); } SpellScriptLoader::SpellScriptLoader(const char* name) : ScriptObject(name) { - ScriptRegistry<SpellScriptLoader>::AddScript(this); + ScriptRegistry<SpellScriptLoader>::Instance()->AddScript(this); } ServerScript::ServerScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ServerScript>::AddScript(this); + ScriptRegistry<ServerScript>::Instance()->AddScript(this); } WorldScript::WorldScript(const char* name) : ScriptObject(name) { - ScriptRegistry<WorldScript>::AddScript(this); + ScriptRegistry<WorldScript>::Instance()->AddScript(this); } FormulaScript::FormulaScript(const char* name) : ScriptObject(name) { - ScriptRegistry<FormulaScript>::AddScript(this); + ScriptRegistry<FormulaScript>::Instance()->AddScript(this); } UnitScript::UnitScript(const char* name, bool addToScripts) : ScriptObject(name) { - ScriptRegistry<UnitScript>::AddScript(this, addToScripts); + if (addToScripts) + ScriptRegistry<UnitScript>::Instance()->AddScript(this); } WorldMapScript::WorldMapScript(const char* name, uint32 mapId) @@ -1553,7 +2288,7 @@ WorldMapScript::WorldMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsWorldMap()) TC_LOG_ERROR("scripts", "WorldMapScript for map %u is invalid.", mapId); - ScriptRegistry<WorldMapScript>::AddScript(this); + ScriptRegistry<WorldMapScript>::Instance()->AddScript(this); } InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) @@ -1562,7 +2297,7 @@ InstanceMapScript::InstanceMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsDungeon()) TC_LOG_ERROR("scripts", "InstanceMapScript for map %u is invalid.", mapId); - ScriptRegistry<InstanceMapScript>::AddScript(this); + ScriptRegistry<InstanceMapScript>::Instance()->AddScript(this); } BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) @@ -1571,122 +2306,117 @@ BattlegroundMapScript::BattlegroundMapScript(const char* name, uint32 mapId) if (GetEntry() && !GetEntry()->IsBattleground()) TC_LOG_ERROR("scripts", "BattlegroundMapScript for map %u is invalid.", mapId); - ScriptRegistry<BattlegroundMapScript>::AddScript(this); + ScriptRegistry<BattlegroundMapScript>::Instance()->AddScript(this); } ItemScript::ItemScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ItemScript>::AddScript(this); + ScriptRegistry<ItemScript>::Instance()->AddScript(this); } CreatureScript::CreatureScript(const char* name) : UnitScript(name, false) { - ScriptRegistry<CreatureScript>::AddScript(this); + ScriptRegistry<CreatureScript>::Instance()->AddScript(this); } GameObjectScript::GameObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GameObjectScript>::AddScript(this); + ScriptRegistry<GameObjectScript>::Instance()->AddScript(this); } AreaTriggerScript::AreaTriggerScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AreaTriggerScript>::AddScript(this); + ScriptRegistry<AreaTriggerScript>::Instance()->AddScript(this); } BattlegroundScript::BattlegroundScript(const char* name) : ScriptObject(name) { - ScriptRegistry<BattlegroundScript>::AddScript(this); + ScriptRegistry<BattlegroundScript>::Instance()->AddScript(this); } OutdoorPvPScript::OutdoorPvPScript(const char* name) : ScriptObject(name) { - ScriptRegistry<OutdoorPvPScript>::AddScript(this); + ScriptRegistry<OutdoorPvPScript>::Instance()->AddScript(this); } CommandScript::CommandScript(const char* name) : ScriptObject(name) { - ScriptRegistry<CommandScript>::AddScript(this); + ScriptRegistry<CommandScript>::Instance()->AddScript(this); } WeatherScript::WeatherScript(const char* name) : ScriptObject(name) { - ScriptRegistry<WeatherScript>::AddScript(this); + ScriptRegistry<WeatherScript>::Instance()->AddScript(this); } AuctionHouseScript::AuctionHouseScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AuctionHouseScript>::AddScript(this); + ScriptRegistry<AuctionHouseScript>::Instance()->AddScript(this); } ConditionScript::ConditionScript(const char* name) : ScriptObject(name) { - ScriptRegistry<ConditionScript>::AddScript(this); + ScriptRegistry<ConditionScript>::Instance()->AddScript(this); } VehicleScript::VehicleScript(const char* name) : ScriptObject(name) { - ScriptRegistry<VehicleScript>::AddScript(this); + ScriptRegistry<VehicleScript>::Instance()->AddScript(this); } DynamicObjectScript::DynamicObjectScript(const char* name) : ScriptObject(name) { - ScriptRegistry<DynamicObjectScript>::AddScript(this); + ScriptRegistry<DynamicObjectScript>::Instance()->AddScript(this); } TransportScript::TransportScript(const char* name) : ScriptObject(name) { - ScriptRegistry<TransportScript>::AddScript(this); + ScriptRegistry<TransportScript>::Instance()->AddScript(this); } AchievementCriteriaScript::AchievementCriteriaScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AchievementCriteriaScript>::AddScript(this); + ScriptRegistry<AchievementCriteriaScript>::Instance()->AddScript(this); } PlayerScript::PlayerScript(const char* name) : UnitScript(name, false) { - ScriptRegistry<PlayerScript>::AddScript(this); + ScriptRegistry<PlayerScript>::Instance()->AddScript(this); } AccountScript::AccountScript(const char* name) : ScriptObject(name) { - ScriptRegistry<AccountScript>::AddScript(this); + ScriptRegistry<AccountScript>::Instance()->AddScript(this); } GuildScript::GuildScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GuildScript>::AddScript(this); + ScriptRegistry<GuildScript>::Instance()->AddScript(this); } GroupScript::GroupScript(const char* name) : ScriptObject(name) { - ScriptRegistry<GroupScript>::AddScript(this); + ScriptRegistry<GroupScript>::Instance()->AddScript(this); } -// Instantiate static members of ScriptRegistry. -template<class TScript> std::map<uint32, TScript*> ScriptRegistry<TScript>::ScriptPointerList; -template<class TScript> std::vector<TScript*> ScriptRegistry<TScript>::Scripts; -template<class TScript> uint32 ScriptRegistry<TScript>::_scriptIdCounter = 0; - // Specialize for each script type class like so: template class TC_GAME_API ScriptRegistry<SpellScriptLoader>; template class TC_GAME_API ScriptRegistry<ServerScript>; @@ -1714,13 +2444,3 @@ template class TC_GAME_API ScriptRegistry<GuildScript>; template class TC_GAME_API ScriptRegistry<GroupScript>; template class TC_GAME_API ScriptRegistry<UnitScript>; template class TC_GAME_API ScriptRegistry<AccountScript>; - -// Undefine utility macros. -#undef GET_SCRIPT_RET -#undef GET_SCRIPT -#undef FOREACH_SCRIPT -#undef FOR_SCRIPTS_RET -#undef FOR_SCRIPTS -#undef SCR_REG_LST -#undef SCR_REG_ITR -#undef SCR_REG_MAP diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 337e7de4861..cc1b65fa593 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -46,6 +46,7 @@ class InstanceMap; class InstanceScript; class Item; class Map; +class ModuleReference; class OutdoorPvP; class Player; class Quest; @@ -157,14 +158,8 @@ class TC_GAME_API ScriptObject protected: - ScriptObject(const char* name) - : _name(name) - { - } - - virtual ~ScriptObject() - { - } + ScriptObject(const char* name); + virtual ~ScriptObject(); private: @@ -338,7 +333,8 @@ class TC_GAME_API WorldMapScript : public ScriptObject, public MapScript<Map> WorldMapScript(const char* name, uint32 mapId); }; -class TC_GAME_API InstanceMapScript : public ScriptObject, public MapScript<InstanceMap> +class TC_GAME_API InstanceMapScript + : public ScriptObject, public MapScript<InstanceMap> { protected: @@ -834,15 +830,6 @@ class TC_GAME_API GroupScript : public ScriptObject virtual void OnDisband(Group* /*group*/) { } }; -// Placed here due to ScriptRegistry::AddScript dependency. -#define sScriptMgr ScriptMgr::instance() - -// namespace -// { - typedef std::list<std::string> UnusedScriptNamesContainer; - TC_GAME_API extern UnusedScriptNamesContainer UnusedScriptNames; -// } - // Manages registration, loading, and execution of scripts. class TC_GAME_API ScriptMgr { @@ -852,16 +839,17 @@ class TC_GAME_API ScriptMgr ScriptMgr(); virtual ~ScriptMgr(); + void FillSpellSummary(); + void LoadDatabase(); + + void IncreaseScriptCount() { ++_scriptCount; } + void DecreaseScriptCount() { --_scriptCount; } + public: /* Initialization */ static ScriptMgr* instance(); void Initialize(); - void LoadDatabase(); - void FillSpellSummary(); - - const char* ScriptsVersion() const { return "Integrated Trinity Scripts"; } - void IncrementScriptCount() { ++_scriptCount; } uint32 GetScriptCount() const { return _scriptCount; } typedef void(*ScriptLoaderCallbackType)(); @@ -873,6 +861,27 @@ class TC_GAME_API ScriptMgr _script_loader_callback = script_loader_callback; } + public: /* Script contexts */ + /// Set the current script context, which allows the ScriptMgr + /// to accept new scripts in this context. + /// Requires a SwapScriptContext() call afterwards to load the new scripts. + void SetScriptContext(std::string const& context); + /// Returns the current script context. + std::string const& GetCurrentScriptContext() const { return _currentContext; } + /// Releases all scripts associated with the given script context immediately. + /// Requires a SwapScriptContext() call afterwards to finish the unloading. + void ReleaseScriptContext(std::string const& context); + /// Executes all changed introduced by SetScriptContext and ReleaseScriptContext. + /// It is possible to combine multiple SetScriptContext and ReleaseScriptContext + /// calls for better performance (bulk changes). + void SwapScriptContext(bool initialize = false); + + /// Acquires a strong module reference to the module containing the given script name, + /// which prevents the shared library which contains the script from unloading. + /// The shared library is lazy unloaded as soon as all references to it are released. + std::shared_ptr<ModuleReference> AcquireModuleReferenceOfScriptName( + std::string const& scriptname) const; + public: /* Unloading */ void Unload(); @@ -881,7 +890,7 @@ class TC_GAME_API ScriptMgr void CreateSpellScripts(uint32 spellId, std::list<SpellScript*>& scriptVector); void CreateAuraScripts(uint32 spellId, std::list<AuraScript*>& scriptVector); - void CreateSpellScriptLoaders(uint32 spellId, std::vector<std::pair<SpellScriptLoader*, std::multimap<uint32, uint32>::iterator> >& scriptVector); + SpellScriptLoader* GetSpellScriptLoader(uint32 scriptId); public: /* ServerScript */ @@ -1094,21 +1103,14 @@ class TC_GAME_API ScriptMgr void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage); void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage); - public: /* Scheduled scripts */ - - uint32 IncreaseScheduledScriptsCount() { return ++_scheduledScripts; } - uint32 DecreaseScheduledScriptCount() { return --_scheduledScripts; } - uint32 DecreaseScheduledScriptCount(size_t count) { return _scheduledScripts -= count; } - bool IsScriptScheduled() const { return _scheduledScripts > 0; } - private: - uint32 _scriptCount; - //atomic op counter for active scripts amount - std::atomic<uint32> _scheduledScripts; - ScriptLoaderCallbackType _script_loader_callback; + + std::string _currentContext; }; +#define sScriptMgr ScriptMgr::instance() + #endif diff --git a/src/server/game/Scripting/ScriptReloadMgr.cpp b/src/server/game/Scripting/ScriptReloadMgr.cpp new file mode 100644 index 00000000000..aac0e26f02b --- /dev/null +++ b/src/server/game/Scripting/ScriptReloadMgr.cpp @@ -0,0 +1,1520 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ScriptReloadMgr.h" +#include "Errors.h" + +#ifndef TRINITY_API_USE_DYNAMIC_LINKING + +// This method should never be called +std::shared_ptr<ModuleReference> + ScriptReloadMgr::AcquireModuleReferenceOfContext(std::string const& /*context*/) +{ + WPAbort(); +} + +// Returns the empty implemented ScriptReloadMgr +ScriptReloadMgr* ScriptReloadMgr::instance() +{ + static ScriptReloadMgr instance; + return &instance; +} + +#else + +#include <algorithm> +#include <regex> +#include <vector> +#include <future> +#include <memory> +#include <fstream> +#include <type_traits> +#include <unordered_set> +#include <unordered_map> + +#include <boost/algorithm/string/replace.hpp> +#include <boost/filesystem.hpp> +#include <boost/system/system_error.hpp> + +#include "efsw/efsw.hpp" + +#include "Log.h" +#include "Config.h" +#include "BuiltInConfig.h" +#include "ScriptMgr.h" +#include "StartProcess.h" +#include "MPSCQueue.h" +#include "GitRevision.h" + +namespace fs = boost::filesystem; + +#ifdef _WIN32 + #include <windows.h> +#else // Posix + #include <dlfcn.h> +#endif + +// Promote the sScriptReloadMgr to a HotSwapScriptReloadMgr +// in this compilation unit. +#undef sScriptReloadMgr +#define sScriptReloadMgr static_cast<HotSwapScriptReloadMgr*>(ScriptReloadMgr::instance()) + +// Returns "" on Windows and "lib" on posix. +static char const* GetSharedLibraryPrefix() +{ +#ifdef _WIN32 + return ""; +#else // Posix + return "lib"; +#endif +} + +// Returns "dll" on Windows and "so" on posix. +static char const* GetSharedLibraryExtension() +{ +#ifdef _WIN32 + return "dll"; +#else // Posix + return "so"; +#endif +} + +#ifdef _WIN32 +typedef HMODULE HandleType; +#else // Posix +typedef void* HandleType; +#endif + +class SharedLibraryUnloader +{ +public: + SharedLibraryUnloader() + : _path() { } + explicit SharedLibraryUnloader(fs::path const& path) + : _path(path) { } + + void operator() (HandleType handle) const + { + // Unload the associated shared library. +#ifdef _WIN32 + bool success = (FreeLibrary(handle) != 0); +#else // Posix + bool success = (dlclose(handle) == 0); +#endif + + if (!success) + { + TC_LOG_ERROR("scripts.hotswap", "Failed to unload (syscall) the shared library \"%s\".", + _path.generic_string().c_str()); + + return; + } + + boost::system::error_code error; + if (fs::remove(_path, error)) + { + TC_LOG_TRACE("scripts.hotswap", "Lazy unloaded and deleted the shared library \"%s\".", + _path.generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to delete the shared library \"%s\" (%s).", + _path.generic_string().c_str(), error.message().c_str()); + } + } + +private: + fs::path _path; +}; + +typedef std::unique_ptr<typename std::remove_pointer<HandleType>::type, SharedLibraryUnloader> HandleHolder; + +typedef char const* (*GetScriptModuleRevisionHashType)(); +typedef void (*AddScriptsType)(); +typedef char const* (*GetScriptModuleType)(); +typedef char const* (*GetBuildDirectiveType)(); + +class ScriptModule + : public ModuleReference +{ +public: + explicit ScriptModule(HandleHolder handle, GetScriptModuleRevisionHashType getScriptModuleRevisionHash, + AddScriptsType addScripts, GetScriptModuleType getScriptModule, + GetBuildDirectiveType getBuildDirective, fs::path const& path) + : _handle(std::forward<HandleHolder>(handle)), _getScriptModuleRevisionHash(getScriptModuleRevisionHash), + _addScripts(addScripts), _getScriptModule(getScriptModule), + _getBuildDirective(getBuildDirective), _path(path) { } + + ScriptModule(ScriptModule const&) = delete; + ScriptModule(ScriptModule&& right) = delete; + + ScriptModule& operator= (ScriptModule const&) = delete; + ScriptModule& operator= (ScriptModule&& right) = delete; + + static Optional<std::shared_ptr<ScriptModule>> CreateFromPath(fs::path const& path); + + char const* GetScriptModuleRevisionHash() const override + { + return _getScriptModuleRevisionHash(); + } + + void AddScripts() const + { + return _addScripts(); + } + + char const* GetScriptModule() const override + { + return _getScriptModule(); + } + + char const* GetBuildDirective() const + { + return _getBuildDirective(); + } + + fs::path const& GetModulePath() const override + { + return _path; + } + +private: + HandleHolder _handle; + + GetScriptModuleRevisionHashType _getScriptModuleRevisionHash; + AddScriptsType _addScripts; + GetScriptModuleType _getScriptModule; + GetBuildDirectiveType _getBuildDirective; + + fs::path _path; +}; + +template<typename Fn> +static bool GetFunctionFromSharedLibrary(HandleType handle, std::string const& name, Fn& fn) +{ +#ifdef _WIN32 + fn = reinterpret_cast<Fn>(GetProcAddress(handle, name.c_str())); +#else // Posix + fn = reinterpret_cast<Fn>(dlsym(handle, name.c_str())); +#endif + return fn != nullptr; +} + +// Load a shared library from the given path. +Optional<std::shared_ptr<ScriptModule>> ScriptModule::CreateFromPath(fs::path const& path) +{ +#ifdef _WIN32 + HandleType handle = LoadLibrary(path.generic_string().c_str()); +#else // Posix + HandleType handle = dlopen(path.c_str(), RTLD_LAZY); +#endif + + if (!handle) + { + TC_LOG_ERROR("scripts.hotswap", "Could not load the shared library \"%s\" for reading.", + path.generic_string().c_str()); + return boost::none; + } + + // Use RAII to release the library on failure. + HandleHolder holder(handle, SharedLibraryUnloader(path)); + + GetScriptModuleRevisionHashType getScriptModuleRevisionHash; + AddScriptsType addScripts; + GetScriptModuleType getScriptModule; + GetBuildDirectiveType getBuildDirective; + + if (GetFunctionFromSharedLibrary(handle, "GetScriptModuleRevisionHash", getScriptModuleRevisionHash) && + GetFunctionFromSharedLibrary(handle, "AddScripts", addScripts) && + GetFunctionFromSharedLibrary(handle, "GetScriptModule", getScriptModule) && + GetFunctionFromSharedLibrary(handle, "GetBuildDirective", getBuildDirective)) + return std::make_shared<ScriptModule>(std::move(holder), getScriptModuleRevisionHash, + addScripts, getScriptModule, getBuildDirective, path); + else + { + TC_LOG_ERROR("scripts.hotswap", "Could not extract all required functions from the shared library \"%s\"!", + path.generic_string().c_str()); + + return boost::none; + } +} + +static bool HasValidScriptModuleName(std::string const& name) +{ + // Detects scripts_NAME.dll's / .so's + static std::regex const regex( + Trinity::StringFormat("^%s[sS]cripts_[a-zA-Z0-9_]+\\.%s$", + GetSharedLibraryPrefix(), + GetSharedLibraryExtension())); + + return std::regex_match(name, regex); +} + +/// File watcher responsible for watching shared libraries +class LibraryUpdateListener : public efsw::FileWatchListener +{ +public: + LibraryUpdateListener() { } + virtual ~LibraryUpdateListener() { } + + void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename = "") final override; +}; + +static LibraryUpdateListener libraryUpdateListener; + +/// File watcher responsible for watching source files +class SourceUpdateListener : public efsw::FileWatchListener +{ + fs::path const path_; + + std::string const script_module_name_; + + efsw::WatchID const watcher_id_; + +public: + explicit SourceUpdateListener(fs::path path, std::string script_module_name); + + virtual ~SourceUpdateListener(); + + void handleFileAction(efsw::WatchID /*watchid*/, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename = "") final override; +}; + +namespace std +{ + template <> + struct hash<fs::path> + { + hash<string> hasher; + + std::size_t operator()(fs::path const& key) const + { + return hasher(key.generic_string()); + } + }; +} + +/// Invokes a synchronous CMake process with the given arguments +template<typename... T> +static int InvokeCMakeCommand(T&&... args) +{ + auto const executable = BuiltInConfig::GetCMakeCommand(); + return Trinity::StartProcess(executable, { + executable, + std::forward<T>(args)... + }, "scripts.hotswap"); +} + +/// Invokes an asynchronous CMake process with the given arguments +template<typename... T> +static std::shared_ptr<Trinity::AsyncProcessResult> InvokeAsyncCMakeCommand(T&&... args) +{ + auto const executable = BuiltInConfig::GetCMakeCommand(); + return Trinity::StartAsyncProcess(executable, { + executable, + std::forward<T>(args)... + }, "scripts.hotswap"); +} + +/// Calculates the C++ project name of the given module which is +/// the lowercase string of scripts_${module}. +static std::string CalculateScriptModuleProjectName(std::string const& module) +{ + std::string module_project = "scripts_" + module; + std::transform(module_project.begin(), module_project.end(), + module_project.begin(), ::tolower); + + return module_project; +} + +/// Returns false when there isn't any attached debugger to the process which +/// could block the rebuild of new shared libraries. +static bool IsDebuggerBlockingRebuild() +{ +#ifdef _WIN32 + if (IsDebuggerPresent()) + return true; +#endif + return false; +} + +/// ScriptReloadMgr which is used when dynamic linking is enabled +/// +/// This class manages shared library loading/unloading through watching +/// the script module directory. Loaded shared libraries are mirrored +/// into a .cache subdirectory to allow lazy unloading as long as +/// the shared library is still used which is useful for scripts +/// which can't be instantly replaced like spells or instances. +/// Several modules which reference different versions can be kept loaded +/// to serve scripts of different versions to entities and spells. +/// +/// Also this class invokes rebuilds as soon as the source of loaded +/// scripts change and installs the modules correctly through CMake. +class HotSwapScriptReloadMgr final + : public ScriptReloadMgr +{ + friend class ScriptReloadMgr; + friend class SourceUpdateListener; + + /// Reflects a queued change on a shared library or shared library + /// which is waiting for processing + enum class ChangeStateRequest : uint8 + { + CHANGE_REQUEST_ADDED, + CHANGE_REQUEST_MODIFIED, + CHANGE_REQUEST_REMOVED + }; + + /// Reflects a running job of an invoked asynchronous external process + enum class BuildJobType : uint8 + { + BUILD_JOB_NONE, + BUILD_JOB_RERUN_CMAKE, + BUILD_JOB_COMPILE, + BUILD_JOB_INSTALL, + }; + + // Represents a job which was invoked through a source or shared library change + class BuildJob + { + // Script module which is processed in the current running job + std::string script_module_name_; + // The C++ project name of the module which is processed + std::string script_module_project_name_; + // The build directive of the current module which is processed + // like "Release" or "Debug". The build directive from the + // previous same module is used if there was any. + std::string script_module_build_directive_; + + // Type of the current running job + BuildJobType type_; + // The async process result of the current job + std::shared_ptr<Trinity::AsyncProcessResult> async_result_; + + public: + explicit BuildJob(std::string script_module_name, std::string script_module_project_name, + std::string script_module_build_directive) + : script_module_name_(std::move(script_module_name)), + script_module_project_name_(std::move(script_module_project_name)), + script_module_build_directive_(std::move(script_module_build_directive)), + type_(BuildJobType::BUILD_JOB_NONE) { } + + bool IsValid() const + { + return type_ != BuildJobType::BUILD_JOB_NONE; + } + + std::string const& GetModuleName() const { return script_module_name_; } + + std::string const& GetProjectName() const { return script_module_project_name_; } + + std::string const& GetBuildDirective() const { return script_module_build_directive_; } + + BuildJobType GetType() const { return type_; } + + std::shared_ptr<Trinity::AsyncProcessResult> const& GetProcess() const + { + ASSERT(async_result_, "Tried to access an empty process handle!"); + return async_result_; + } + + /// Updates the current running job with the given type and async result + void UpdateCurrentJob(BuildJobType type, + std::shared_ptr<Trinity::AsyncProcessResult> async_result) + { + ASSERT(type != BuildJobType::BUILD_JOB_NONE, "None isn't allowed here!"); + ASSERT(async_result, "The async result must not be empty!"); + + type_ = type; + async_result_ = std::move(async_result); + } + }; + + /// Base class for lockfree asynchronous messages to the script reloader + class ScriptReloaderMessage + { + public: + virtual ~ScriptReloaderMessage() { } + + /// Invoke this function to run a message thread safe on the reloader + virtual void operator() (HotSwapScriptReloadMgr* reloader) = 0; + }; + + /// Implementation class which wraps functional types and dispatches + /// it in the overwritten implementation of the reloader messages. + template<typename T> + class ScriptReloaderMessageImplementation + : public ScriptReloaderMessage + { + T dispatcher_; + + public: + explicit ScriptReloaderMessageImplementation(T dispatcher) + : dispatcher_(std::move(dispatcher)) { } + + void operator() (HotSwapScriptReloadMgr* reloader) final override + { + dispatcher_(reloader); + } + }; + + /// Uses the given functional type and creates a asynchronous reloader + /// message on the heap, which requires deletion. + template<typename T> + auto MakeMessage(T&& dispatcher) + -> ScriptReloaderMessageImplementation<typename std::decay<T>::type>* + { + return new ScriptReloaderMessageImplementation<typename std::decay<T>::type> + (std::forward<T>(dispatcher)); + } + +public: + HotSwapScriptReloadMgr() + : _libraryWatcher(-1), _unique_library_name_counter(0), + _last_time_library_changed(0), _last_time_sources_changed(0), + _last_time_user_informed(0), terminate_early(false) { } + + virtual ~HotSwapScriptReloadMgr() + { + // Delete all messages + ScriptReloaderMessage* message; + while (_messages.Dequeue(message)) + delete message; + } + + /// Returns the absolute path to the script module directory + static fs::path GetLibraryDirectory() + { + return fs::absolute(sConfigMgr->GetStringDefault("HotSwap.ScriptDir", "scripts")); + } + + /// Returns the absolute path to the scripts directory in the source tree. + static fs::path GetSourceDirectory() + { + fs::path dir = BuiltInConfig::GetSourceDirectory(); + dir /= "src"; + dir /= "server"; + dir /= "scripts"; + return dir; + } + + /// Initializes the file watchers and loads all existing shared libraries + /// into the running server. + void Initialize() final override + { + if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_ENABLED)) + return; + + if (BuiltInConfig::GetBuildDirectory().find(" ") != std::string::npos) + { + TC_LOG_ERROR("scripts.hotswap", "Your build directory path \"%s\" " + "contains spaces, which isn't allowed for compatibility reasons! " + "You need to create a build directory which doesn't contain any space character " + "in it's path!", + BuiltInConfig::GetBuildDirectory().c_str()); + + return; + } + + { + auto const library_directory = GetLibraryDirectory(); + if (!fs::exists(library_directory) || !fs::is_directory(library_directory)) + { + TC_LOG_ERROR("scripts.hotswap", "Library directory \"%s\" doesn't exist!.", + library_directory.generic_string().c_str()); + return; + } + } + + // Get the cache directory path + fs::path const cache_path = [] + { + auto path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); + path /= ".cache"; + return path; + }(); + + // We use the boost filesystem function versions which accept + // an error code to prevent it from throwing exceptions. + boost::system::error_code code; + if ((!fs::exists(cache_path, code) || (fs::remove_all(cache_path, code) > 0)) && + !fs::create_directory(cache_path, code)) + { + TC_LOG_ERROR("scripts.hotswap", "Couldn't create the cache directory \"%s\".", + cache_path.generic_string().c_str()); + return; + } + + // Used to silent compiler warnings + (void)code; + + // Correct the CMake prefix when needed + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED)) + DoCMakePrefixCorrectionIfNeeded(); + + InitializeDefaultLibraries(); + InitializeFileWatchers(); + } + + /// Needs to be called periodically from the worldserver loop + /// to invoke queued actions like module loading/unloading and script + /// compilation. + /// This method should be invoked from a thread safe point to + /// prevent misbehavior. + void Update() final override + { + // Consume all messages + ScriptReloaderMessage* message; + while (_messages.Dequeue(message)) + { + (*message)(this); + delete message; + } + + DispatchRunningBuildJobs(); + DispatchModuleChanges(); + } + + /// Unloads the manager and cancels all runnings jobs immediately + void Unload() final override + { + if (_libraryWatcher >= 0) + { + _fileWatcher.removeWatch(_libraryWatcher); + _libraryWatcher = -1; + } + + // If a build is in progress cancel it + if (_build_job) + { + _build_job->GetProcess()->Terminate(); + _build_job.reset(); + } + + // Release all strong references to script modules + // to trigger unload actions as early as possible, + // otherwise the worldserver will crash on exit. + _running_script_modules.clear(); + } + + /// Queue's a thread safe message to the reloader which is executed on + /// the next world server update tick. + template<typename T> + void QueueMessage(T&& message) + { + _messages.Enqueue(MakeMessage(std::forward<T>(message))); + } + + /// Queues an action which marks the given shared library as changed + /// which will add, unload or reload it at the next world update tick. + /// This method is thread safe. + void QueueSharedLibraryChanged(fs::path const& path) + { + _last_time_library_changed = getMSTime(); + _libraries_changed.insert(path); + } + + /// Queues a notification that a source file was added + /// This method is thread unsafe. + void QueueAddSourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_ADDED); + } + + /// Queues a notification that a source file was modified + /// This method is thread unsafe. + void QueueModifySourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_MODIFIED); + } + + /// Queues a notification that a source file was removed + /// This method is thread unsafe. + void QueueRemoveSourceFile(std::string const& module_name, fs::path const& path) + { + UpdateSourceChangeRequest(module_name, path, ChangeStateRequest::CHANGE_REQUEST_REMOVED); + } + +private: + // Loads all shared libraries which are contained in the + // scripts directory on startup. + void InitializeDefaultLibraries() + { + fs::path const libraryDirectory(GetLibraryDirectory()); + fs::directory_iterator const dir_end; + + uint32 count = 0; + + // Iterate through all shared libraries in the script directory and load it + for (fs::directory_iterator dir_itr(libraryDirectory); dir_itr != dir_end ; ++dir_itr) + if (fs::is_regular_file(dir_itr->path()) && HasValidScriptModuleName(dir_itr->path().filename().generic_string())) + { + TC_LOG_INFO("scripts.hotswap", "Loading script module \"%s\"...", + dir_itr->path().filename().generic_string().c_str()); + + // Don't swap the script context to do bulk loading + ProcessLoadScriptModule(dir_itr->path(), false); + ++count; + } + + TC_LOG_INFO("scripts.hotswap", ">> Loaded %u script modules.", count); + } + + // Initialize all enabled file watchers. + // Needs to be called after InitializeDefaultLibraries()! + void InitializeFileWatchers() + { + _libraryWatcher = _fileWatcher.addWatch(GetLibraryDirectory().generic_string(), &libraryUpdateListener, false); + if (_libraryWatcher >= 0) + { + TC_LOG_INFO("scripts.hotswap", ">> Library reloader is listening on \"%s\".", + GetLibraryDirectory().generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to initialize the library reloader on \"%s\".", + GetLibraryDirectory().generic_string().c_str()); + } + + _fileWatcher.watch(); + } + + /// Updates the current state of the given source path + void UpdateSourceChangeRequest(std::string const& module_name, + fs::path const& path, + ChangeStateRequest state) + { + _last_time_sources_changed = getMSTime(); + + // Write when there is no module with the given name known + auto module_itr = _sources_changed.find(module_name); + + // When the file was modified it's enough to mark the module as + // dirty by initializing the associated map. + if (module_itr == _sources_changed.end()) + module_itr = _sources_changed.insert(std::make_pair( + module_name, decltype(_sources_changed)::mapped_type{})).first; + + // Leave when the file was just modified as explained above + if (state == ChangeStateRequest::CHANGE_REQUEST_MODIFIED) + return; + + // Insert when the given path isn't existent + auto const itr = module_itr->second.find(path); + if (itr == module_itr->second.end()) + { + module_itr->second.insert(std::make_pair(path, state)); + return; + } + + ASSERT((itr->second == ChangeStateRequest::CHANGE_REQUEST_ADDED) + || (itr->second == ChangeStateRequest::CHANGE_REQUEST_REMOVED), + "Stored value is invalid!"); + + ASSERT((state == ChangeStateRequest::CHANGE_REQUEST_ADDED) + || (state == ChangeStateRequest::CHANGE_REQUEST_REMOVED), + "The given state is invalid!"); + + ASSERT(state != itr->second, + "Tried to apply a state which is stored already!"); + + module_itr->second.erase(itr); + } + + /// Called periodically on the worldserver tick to process all + /// load/unload/reload requests of shared libraries. + void DispatchModuleChanges() + { + // When there are no libraries to change return + if (_libraries_changed.empty()) + return; + + // Wait some time after changes to catch bulk changes + if (GetMSTimeDiffToNow(_last_time_library_changed) < 500) + return; + + for (auto const& path : _libraries_changed) + { + bool const is_running = + _running_script_module_names.find(path) != _running_script_module_names.end(); + + bool const exists = fs::exists(path); + + if (is_running) + { + if (exists) + ProcessReloadScriptModule(path); + else + ProcessUnloadScriptModule(path); + } + else if (exists) + ProcessLoadScriptModule(path); + } + + _libraries_changed.clear(); + } + + void ProcessLoadScriptModule(fs::path const& path, bool swap_context = true) + { + ASSERT(_running_script_module_names.find(path) == _running_script_module_names.end(), + "Can't load a module which is running already!"); + + // Create the cache path and increment the library counter to use an unique name for each library + fs::path cache_path = fs::absolute(sScriptReloadMgr->GetLibraryDirectory()); + cache_path /= ".cache"; + cache_path /= Trinity::StringFormat("%s.%u%s", + path.stem().generic_string().c_str(), + _unique_library_name_counter++, + path.extension().generic_string().c_str()); + + if ([&] + { + boost::system::error_code code; + fs::copy_file(path, cache_path, fs::copy_option::fail_if_exists, code); + return code; + }()) + { + TC_LOG_FATAL("scripts.hotswap", ">> Failed to create cache entry for module \"%s\"!", + path.filename().generic_string().c_str()); + + // Find a better solution for this but it's much better + // to start the core without scripts + std::this_thread::sleep_for(std::chrono::seconds(5)); + ABORT(); + return; + } + + auto module = ScriptModule::CreateFromPath(cache_path); + if (!module) + { + TC_LOG_FATAL("scripts.hotswap", ">> Failed to load script module \"%s\"!", + path.filename().generic_string().c_str()); + + // Find a better solution for this but it's much better + // to start the core without scripts + std::this_thread::sleep_for(std::chrono::seconds(5)); + ABORT(); + return; + } + + // Limit the git revision hash to 7 characters. + std::string module_revision((*module)->GetScriptModuleRevisionHash()); + if (module_revision.size() >= 7) + module_revision = module_revision.substr(0, 7); + + std::string const module_name = (*module)->GetScriptModule(); + TC_LOG_INFO("scripts.hotswap", ">> Loaded script module \"%s\" (\"%s\" - %s).", + path.filename().generic_string().c_str(), module_name.c_str(), module_revision.c_str()); + + if (module_revision.empty()) + { + TC_LOG_WARN("scripts.hotswap", ">> Script module \"%s\" has an empty revision hash!", + path.filename().generic_string().c_str()); + } + else + { + // Trim the revision hash + std::string my_revision_hash = GitRevision::GetHash(); + std::size_t const trim = std::min(module_revision.size(), my_revision_hash.size()); + my_revision_hash = my_revision_hash.substr(0, trim); + module_revision = module_revision.substr(0, trim); + + if (my_revision_hash != module_revision) + { + TC_LOG_WARN("scripts.hotswap", ">> Script module \"%s\" has a different revision hash! " + "Binary incompatibility could lead to unknown behaviour!", path.filename().generic_string().c_str()); + } + } + + { + auto const itr = _running_script_modules.find(module_name); + if (itr != _running_script_modules.end()) + { + TC_LOG_ERROR("scripts.hotswap", ">> Attempt to load a module twice \"%s\" (loaded module is at %s)!", + path.generic_string().c_str(), itr->second.first->GetModulePath().generic_string().c_str()); + + return; + } + } + + sScriptMgr->SetScriptContext(module_name); + (*module)->AddScripts(); + TC_LOG_TRACE("scripts.hotswap", ">> Registered all scripts of module %s.", module_name.c_str()); + + if (swap_context) + sScriptMgr->SwapScriptContext(); + + // Create the source listener + auto listener = Trinity::make_unique<SourceUpdateListener>( + sScriptReloadMgr->GetSourceDirectory() / module_name, + module_name); + + // Store the module + _known_modules_build_directives.insert(std::make_pair(module_name, (*module)->GetBuildDirective())); + _running_script_modules.insert(std::make_pair(module_name, + std::make_pair(std::move(*module), std::move(listener)))); + _running_script_module_names.insert(std::make_pair(path, module_name)); + } + + void ProcessReloadScriptModule(fs::path const& path) + { + ProcessUnloadScriptModule(path, false); + ProcessLoadScriptModule(path); + } + + void ProcessUnloadScriptModule(fs::path const& path, bool finish = true) + { + auto const itr = _running_script_module_names.find(path); + + ASSERT(itr != _running_script_module_names.end(), + "Can't unload a module which isn't running!"); + + // Unload the script context + sScriptMgr->ReleaseScriptContext(itr->second); + + if (finish) + sScriptMgr->SwapScriptContext(); + + TC_LOG_INFO("scripts.hotswap", "Released script module \"%s\" (\"%s\")...", + path.filename().generic_string().c_str(), itr->second.c_str()); + + // Unload the script module + auto ref = _running_script_modules.find(itr->second); + ASSERT(ref != _running_script_modules.end() && + "Expected the script reference to be present!"); + + // Yield a message when there are other owning references to + // the module which prevents it from unloading. + // The module will be unloaded once all scripts provided from the module + // are destroyed. + if (!ref->second.first.unique()) + { + TC_LOG_INFO("scripts.hotswap", + "Script module %s is still used by %lu spell, aura or instance scripts. " + "Will lazy unload the module once all scripts stopped using it, " + "to use the latest version of an edited script unbind yourself from " + "the instance or re-cast the spell.", + ref->second.first->GetScriptModule(), ref->second.first.use_count() - 1); + } + + // Remove the owning reference from the reloader + _running_script_modules.erase(ref); + _running_script_module_names.erase(itr); + } + + /// Called periodically on the worldserver tick to process all recompile + /// requests. This method invokes one build or install job at the time + void DispatchRunningBuildJobs() + { + if (_build_job) + { + // Terminate the current build job when an associated source was changed + // while compiling and the terminate early option is enabled. + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED)) + { + if (!terminate_early && _sources_changed.find(_build_job->GetModuleName()) != _sources_changed.end()) + { + /* + FIXME: Currently crashes the server + TC_LOG_INFO("scripts.hotswap", "Terminating the running build of module \"%s\"...", + _build_job->GetModuleName().c_str()); + + _build_job->GetProcess()->Terminate(); + _build_job.reset(); + + // Continue with the default execution path + DispatchRunningBuildJobs(); + return; + */ + + terminate_early = true; + return; + } + } + + // Wait for the current build job to finish, if the job finishes in time + // evaluate it and continue with the next one. + if (_build_job->GetProcess()->GetFutureResult(). + wait_for(std::chrono::seconds(0)) == std::future_status::ready) + ProcessReadyBuildJob(); + else + return; // Return when the job didn't finish in time + + // Skip this cycle when the previous job scheduled a new one + if (_build_job) + return; + } + + // Avoid burst updates through waiting for a short time after changes + if ((_last_time_sources_changed != 0) && + (GetMSTimeDiffToNow(_last_time_sources_changed) < 500)) + return; + + // If the changed sources are empty do nothing + if (_sources_changed.empty()) + return; + + // Wait until are attached debugger were detached. + if (IsDebuggerBlockingRebuild()) + { + if ((_last_time_user_informed == 0) || + (GetMSTimeDiffToNow(_last_time_user_informed) > 7500)) + { + _last_time_user_informed = getMSTime(); + + // Informs the user that the attached debugger is blocking the automatic script rebuild. + TC_LOG_INFO("scripts.hotswap", "Your attached debugger is blocking the TrinityCore " + "automatic script rebuild, please detach it!"); + } + + return; + } + + // Find all source files of a changed script module and removes + // it from the changed source list, invoke the build afterwards. + bool rebuild_buildfiles; + auto module_name = [&] + { + auto itr = _sources_changed.begin(); + auto name = itr->first; + rebuild_buildfiles = !itr->second.empty(); + + if (sLog->ShouldLog("scripts.hotswap", LogLevel::LOG_LEVEL_TRACE)) + for (auto const& entry : itr->second) + { + TC_LOG_TRACE("scripts.hotswap", "Source file %s was %s.", + entry.first.generic_string().c_str(), + ((entry.second == ChangeStateRequest::CHANGE_REQUEST_ADDED) ? + "added" : "removed")); + } + + _sources_changed.erase(itr); + return name; + }(); + + // Erase the added delete history all modules when we + // invoke a cmake rebuild since we add all + // added files of other modules to the build as well + if (rebuild_buildfiles) + { + for (auto& entry : _sources_changed) + entry.second.clear(); + } + + ASSERT(!module_name.empty(), + "The current module name is invalid!"); + + TC_LOG_INFO("scripts.hotswap", "Recompiling Module \"%s\"...", + module_name.c_str()); + + // Calculate the project name of the script module + auto project_name = CalculateScriptModuleProjectName(module_name); + + // Find the best build directive for the module + auto build_directive = [&] () -> std::string + { + auto directive = sConfigMgr->GetStringDefault("HotSwap.ReCompilerBuildType", ""); + if (!directive.empty()) + return directive; + + auto const itr = _known_modules_build_directives.find(module_name); + if (itr != _known_modules_build_directives.end()) + return itr->second; + else // If no build directive of the module was found use the one from the game library + return _BUILD_DIRECTIVE; + }(); + + // Initiate the new build job + _build_job = BuildJob(std::move(module_name), + std::move(project_name), std::move(build_directive)); + + // Rerun CMake when we need to recreate the build files + if (rebuild_buildfiles + && sWorld->getBoolConfig(CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED)) + DoRerunCMake(); + else + DoCompileCurrentProcessedModule(); + } + + void ProcessReadyBuildJob() + { + ASSERT(_build_job->IsValid(), "Invalid build job!"); + + // Retrieve the result + auto const error = _build_job->GetProcess()->GetFutureResult().get(); + + if (terminate_early) + { + _build_job.reset(); + terminate_early = false; + return; + } + + switch (_build_job->GetType()) + { + case BuildJobType::BUILD_JOB_RERUN_CMAKE: + { + if (!error) + { + TC_LOG_INFO("scripts.hotswap", ">> Successfully updated the build files!"); + } + else + { + TC_LOG_INFO("scripts.hotswap", ">> Failed to update the build files at \"%s\", " + "it's possible that recently added sources are not included " + "in your next builds, rerun CMake manually.", + BuiltInConfig::GetBuildDirectory().c_str()); + } + // Continue with building the changes sources + DoCompileCurrentProcessedModule(); + return; + } + case BuildJobType::BUILD_JOB_COMPILE: + { + if (!error) // Build was successful + { + if (sWorld->getBoolConfig(CONFIG_HOTSWAP_INSTALL_ENABLED)) + { + // Continue with the installation when it's enabled + TC_LOG_INFO("scripts.hotswap", + ">> Successfully build module %s, continue with installing...", + _build_job->GetModuleName().c_str()); + + DoInstallCurrentProcessedModule(); + return; + } + + // Skip the installation because it's disabled in config + TC_LOG_INFO("scripts.hotswap", + ">> Successfully build module %s, skipped the installation.", + _build_job->GetModuleName().c_str()); + } + else // Build wasn't successful + { + TC_LOG_ERROR("scripts.hotswap", + ">> The build of module %s failed! See the log for details.", + _build_job->GetModuleName().c_str()); + } + break; + } + case BuildJobType::BUILD_JOB_INSTALL: + { + if (!error) + { + // Installation was successful + TC_LOG_INFO("scripts.hotswap", ">> Successfully installed module %s.", + _build_job->GetModuleName().c_str()); + } + else + { + // Installation wasn't successful + TC_LOG_INFO("scripts.hotswap", + ">> The installation of module %s failed! See the log for details.", + _build_job->GetModuleName().c_str()); + } + break; + } + default: + break; + } + + // Clear the current job + _build_job.reset(); + } + + /// Reruns CMake asynchronously over the build directory + void DoRerunCMake() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Rerunning CMake because there were sources added or removed..."); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_RERUN_CMAKE, + InvokeAsyncCMakeCommand(BuiltInConfig::GetBuildDirectory())); + } + + /// Invokes a new build of the current active module job + void DoCompileCurrentProcessedModule() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Starting asynchronous build job for module %s...", + _build_job->GetModuleName().c_str()); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_COMPILE, + InvokeAsyncCMakeCommand( + "--build", BuiltInConfig::GetBuildDirectory(), + "--target", _build_job->GetProjectName(), + "--config", _build_job->GetBuildDirective())); + } + + /// Invokes a new asynchronous install of the current active module job + void DoInstallCurrentProcessedModule() + { + ASSERT(_build_job, "There isn't any active build job!"); + + TC_LOG_INFO("scripts.hotswap", "Starting asynchronous install job for module %s...", + _build_job->GetModuleName().c_str()); + + _build_job->UpdateCurrentJob(BuildJobType::BUILD_JOB_INSTALL, + InvokeAsyncCMakeCommand( + "-DCOMPONENT=" + _build_job->GetProjectName(), + "-DBUILD_TYPE=" + _build_job->GetBuildDirective(), + "-P", fs::absolute("cmake_install.cmake", + BuiltInConfig::GetBuildDirectory()).generic_string())); + } + + /// Sets the CMAKE_INSTALL_PREFIX variable in the CMake cache + /// to point to the current worldserver position, + /// since most users will forget this. + void DoCMakePrefixCorrectionIfNeeded() + { + TC_LOG_INFO("scripts.hotswap", "Correcting your CMAKE_INSTALL_PREFIX in \"%s\"...", + BuiltInConfig::GetBuildDirectory().c_str()); + + auto const cmake_cache_path = fs::absolute("CMakeCache.txt", + BuiltInConfig::GetBuildDirectory()); + + // Stop when the CMakeCache wasn't found + if (![&] + { + boost::system::error_code error; + if (!fs::exists(cmake_cache_path, error)) + { + TC_LOG_ERROR("scripts.hotswap", ">> CMake cache \"%s\" doesn't exist, " + "set the \"BuildDirectory\" option in your worldserver.conf to point" + "to your build directory!", + cmake_cache_path.generic_string().c_str()); + + return false; + } + else + return true; + }()) + return; + + TC_LOG_TRACE("scripts.hotswap", "Checking CMake cache (\"%s\") " + "for the correct CMAKE_INSTALL_PREFIX location...", + cmake_cache_path.generic_string().c_str()); + + std::string cmake_cache_content; + { + std::ifstream in(cmake_cache_path.generic_string()); + if (!in.is_open()) + { + TC_LOG_ERROR("scripts.hotswap", ">> Failed to read the CMake cache at \"%s\"!", + cmake_cache_path.generic_string().c_str()); + + return; + } + + std::ostringstream ss; + ss << in.rdbuf(); + cmake_cache_content = ss.str(); + + in.close(); + } + + static std::string const prefix_key = "CMAKE_INSTALL_PREFIX:PATH="; + + // Extract the value of CMAKE_INSTALL_PREFIX + auto begin = cmake_cache_content.find(prefix_key); + if (begin != std::string::npos) + { + begin += prefix_key.length(); + auto const end = cmake_cache_content.find("\n", begin); + if (end != std::string::npos) + { + fs::path value = cmake_cache_content.substr(begin, end - begin); + + auto current_path = fs::current_path(); + + #ifndef _WIN32 + // The worldserver location is ${CMAKE_INSTALL_PREFIX}/bin + // on all other platforms then windows + current_path = current_path.remove_leaf(); + #endif + + if (value != current_path) + { + // Prevent correction of the install prefix + // when we are starting the core from inside the build tree + bool const is_in_path = [&] + { + fs::path base = BuiltInConfig::GetBuildDirectory(); + fs::path branch = value; + while (!branch.empty()) + { + if (base == branch) + return true; + + branch = branch.remove_leaf(); + } + + return false; + }(); + + if (is_in_path) + return; + + TC_LOG_INFO("scripts.hotswap", ">> Found outdated CMAKE_INSTALL_PREFIX (\"%s\"), " + "worldserver is currently installed at %s...", + value.generic_string().c_str(), current_path.generic_string().c_str()); + } + else + { + TC_LOG_INFO("scripts.hotswap", ">> CMAKE_INSTALL_PREFIX is equal to the current path of execution."); + return; + } + } + } + + TC_LOG_INFO("scripts.hotswap", "Invoking CMake cache correction..."); + + auto const error = InvokeCMakeCommand( + "-DCMAKE_INSTALL_PREFIX:PATH=" + fs::current_path().generic_string(), + BuiltInConfig::GetBuildDirectory()); + + if (error) + { + TC_LOG_ERROR("scripts.hotswap", ">> Failed to update the CMAKE_INSTALL_PREFIX! " + "This could lead to unexpected behaviour!"); + } + else + { + TC_LOG_ERROR("scripts.hotswap", ">> Successfully corrected your CMAKE_INSTALL_PREFIX variable" + "to point at your current path of execution."); + } + } + + // File watcher instance and watcher ID's + efsw::FileWatcher _fileWatcher; + efsw::WatchID _libraryWatcher; + + // Unique library name counter which is used to + // generate unique names for every shared library version. + uint32 _unique_library_name_counter; + + // Queue which is used for thread safe message processing + MPSCQueue<ScriptReloaderMessage> _messages; + + // Change requests to load or unload shared libraries + std::unordered_set<fs::path /*path*/> _libraries_changed; + // The timestamp which indicates the last time a library was changed + uint32 _last_time_library_changed; + + // Contains all running script modules + // The associated shared libraries are unloaded immediately + // on loosing ownership through RAII. + std::unordered_map<std::string /*module name*/, + std::pair<std::shared_ptr<ScriptModule>, std::unique_ptr<SourceUpdateListener>> + > _running_script_modules; + // Container which maps the path of a shared library to it's module name + std::unordered_map<fs::path, std::string /*module name*/> _running_script_module_names; + // Container which maps the module name to it's last known build directive + std::unordered_map<std::string /*module name*/, std::string /*build directive*/> _known_modules_build_directives; + + // Modules which were changed and are queued for recompilation + std::unordered_map<std::string /*module*/, + std::unordered_map<fs::path /*path*/, ChangeStateRequest /*state*/>> _sources_changed; + // Tracks the time since the last module has changed to avoid burst updates + uint32 _last_time_sources_changed; + + // Tracks the last timestamp the user was informed about a certain repeating event. + uint32 _last_time_user_informed; + + // Represents the current build job which is in progress + Optional<BuildJob> _build_job; + + // Is true when the build job dispatcher should stop after + // the current job has finished + bool terminate_early; +}; + +/// Maps efsw actions to strings +static char const* ActionToString(efsw::Action action) +{ + switch (action) + { + case efsw::Action::Add: + return "added"; + case efsw::Action::Delete: + return "deleted"; + case efsw::Action::Moved: + return "moved"; + default: + return "modified"; + } +} + +void LibraryUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename) +{ + // TC_LOG_TRACE("scripts.hotswap", "Library listener detected change on possible module \"%s\ (%s)".", filename.c_str(), ActionToString(action)); + + // Split moved actions into a delete and an add action + if (action == efsw::Action::Moved) + { + ASSERT(!oldFilename.empty(), "Old filename doesn't exist!"); + handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete); + handleFileAction(watchid, dir, filename, efsw::Action::Add); + return; + } + + sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader) mutable + { + auto const path = fs::absolute( + filename, + sScriptReloadMgr->GetLibraryDirectory()); + + if (!HasValidScriptModuleName(filename)) + return; + + switch (action) + { + case efsw::Actions::Add: + TC_LOG_TRACE("scripts.hotswap", ">> Loading \"%s\" (%s)...", + path.generic_string().c_str(), ActionToString(action)); + reloader->QueueSharedLibraryChanged(path); + break; + case efsw::Actions::Delete: + TC_LOG_TRACE("scripts.hotswap", ">> Unloading \"%s\" (%s)...", + path.generic_string().c_str(), ActionToString(action)); + reloader->QueueSharedLibraryChanged(path); + break; + case efsw::Actions::Modified: + TC_LOG_TRACE("scripts.hotswap", ">> Reloading \"%s\" (%s)...", + path.generic_string().c_str(), ActionToString(action)); + reloader->QueueSharedLibraryChanged(path); + break; + default: + WPAbort(); + break; + } + }); +} + +/// Returns true when the given path has a known C++ file extension +static bool HasCXXSourceFileExtension(fs::path const& path) +{ + static std::regex const regex("^\\.(h|hpp|c|cc|cpp)$"); + return std::regex_match(path.extension().generic_string(), regex); +} + +SourceUpdateListener::SourceUpdateListener(fs::path path, std::string script_module_name) + : path_(std::move(path)), script_module_name_(std::move(script_module_name)), + watcher_id_(sScriptReloadMgr->_fileWatcher.addWatch(path_.generic_string(), this, true)) +{ + if (watcher_id_ >= 0) + { + TC_LOG_TRACE("scripts.hotswap", ">> Attached the source recompiler to \"%s\".", + path_.generic_string().c_str()); + } + else + { + TC_LOG_ERROR("scripts.hotswap", "Failed to initialize thesource recompiler on \"%s\".", + path_.generic_string().c_str()); + } +} + +SourceUpdateListener::~SourceUpdateListener() +{ + if (watcher_id_ >= 0) + { + sScriptReloadMgr->_fileWatcher.removeWatch(watcher_id_); + + TC_LOG_TRACE("scripts.hotswap", ">> Detached the source recompiler from \"%s\".", + path_.generic_string().c_str()); + } +} + +void SourceUpdateListener::handleFileAction(efsw::WatchID watchid, std::string const& dir, + std::string const& filename, efsw::Action action, std::string oldFilename) +{ + // TC_LOG_TRACE("scripts.hotswap", "Source listener detected change on possible file \"%s/%s\" (%s).", dir.c_str(), filename.c_str(), ActionToString(action)); + + // Skip the file change notification if the recompiler is disabled + if (!sWorld->getBoolConfig(CONFIG_HOTSWAP_RECOMPILER_ENABLED)) + return; + + // Split moved actions into a delete and an add action + if (action == efsw::Action::Moved) + { + ASSERT(!oldFilename.empty(), "Old filename doesn't exist!"); + handleFileAction(watchid, dir, oldFilename, efsw::Action::Delete); + handleFileAction(watchid, dir, filename, efsw::Action::Add); + return; + } + + auto const path = fs::absolute( + filename, + dir); + + // Check if the file is a C/C++ source file. + if (!path.has_extension() || !HasCXXSourceFileExtension(path)) + return; + + /// Thread safe part + sScriptReloadMgr->QueueMessage([=](HotSwapScriptReloadMgr* reloader) + { + TC_LOG_TRACE("scripts.hotswap", "Detected source change on module \"%s\", " + "queued for recompilation...", script_module_name_.c_str()); + + switch (action) + { + case efsw::Actions::Add: + TC_LOG_TRACE("scripts.hotswap", "Source file %s of module %s was added.", + path.generic_string().c_str(), script_module_name_.c_str()); + reloader->QueueAddSourceFile(script_module_name_, path); + break; + case efsw::Actions::Delete: + TC_LOG_TRACE("scripts.hotswap", "Source file %s of module %s was deleted.", + path.generic_string().c_str(), script_module_name_.c_str()); + reloader->QueueRemoveSourceFile(script_module_name_, path); + break; + case efsw::Actions::Modified: + TC_LOG_TRACE("scripts.hotswap", "Source file %s of module %s was modified.", + path.generic_string().c_str(), script_module_name_.c_str()); + reloader->QueueModifySourceFile(script_module_name_, path); + break; + default: + WPAbort(); + break; + } + }); +} + +// Returns the module reference of the given context +std::shared_ptr<ModuleReference> + ScriptReloadMgr::AcquireModuleReferenceOfContext(std::string const& context) +{ + auto const itr = sScriptReloadMgr->_running_script_modules.find(context); + if (itr != sScriptReloadMgr->_running_script_modules.end()) + return itr->second.first; + else + return { }; +} + +// Returns the full hot swap implemented ScriptReloadMgr +ScriptReloadMgr* ScriptReloadMgr::instance() +{ + static HotSwapScriptReloadMgr instance; + return &instance; +} + +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING diff --git a/src/server/game/Scripting/ScriptReloadMgr.h b/src/server/game/Scripting/ScriptReloadMgr.h new file mode 100644 index 00000000000..f9b388f8eb0 --- /dev/null +++ b/src/server/game/Scripting/ScriptReloadMgr.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SCRIPT_RELOADER_H +#define SCRIPT_RELOADER_H + +#include <memory> +#include <string> +#include "Define.h" +#include <boost/filesystem/path.hpp> + +/// Represents a strong reference to a dynamic library which +/// provides C++ scripts. As long as one reference to the library exists +/// the library is kept loaded in the server, which makes it possible to lazy +/// unload several script types on demand (like SpellScripts), and to +/// provide multiple versions of the same script to the script factories. +/// +/// Acquire a new reference through using: +/// `ScriptReloadMgr::AcquireModuleReferenceOfContext` +class ModuleReference +{ +public: + virtual ~ModuleReference() { } + + /// Returns the git revision hash of the referenced script module + virtual char const* GetScriptModuleRevisionHash() const = 0; + /// Returns the name of the referenced script module + virtual char const* GetScriptModule() const = 0; + /// Returns the path to the script module + virtual boost::filesystem::path const& GetModulePath() const = 0; +}; + +/// Provides the whole physical dynamic library unloading capability. +/// Loads, Reloads and Unloads dynamic libraries on changes and +/// informs the ScriptMgr about changes which were made. +/// The ScriptReloadMgr is also responsible for watching the source directory +/// and to invoke a build on changes. +class TC_GAME_API ScriptReloadMgr +{ +protected: + ScriptReloadMgr() { } + +public: + virtual ~ScriptReloadMgr() { } + + /// Initializes the ScriptReloadMgr + virtual void Initialize() { } + + /// Needs to be called periodically to check for updates on script modules. + /// Expects to be invoked in a thread safe way which means it's required that + /// the current thread is the only one which accesses the world data. + virtual void Update() { } + + /// Unloads the ScriptReloadMgr + virtual void Unload() { } + + /// Returns an owning reference to the current module of the given context + static std::shared_ptr<ModuleReference> AcquireModuleReferenceOfContext( + std::string const& context); + + /// Returns the unique ScriptReloadMgr singleton instance + static ScriptReloadMgr* instance(); +}; + +#define sScriptReloadMgr ScriptReloadMgr::instance() + +#endif // SCRIPT_RELOADER_H diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index d6cdb99e75c..a8adda3ad58 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1325,7 +1325,7 @@ void AuraEffect::HandleModInvisibility(AuraApplication const* aurApp, uint8 mode { // apply glow vision if (target->GetTypeId() == TYPEID_PLAYER) - target->SetByteFlag(PLAYER_FIELD_BYTES2, 3, PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); + target->SetByteFlag(PLAYER_FIELD_BYTES2, PLAYER_FIELD_BYTES_2_OFFSET_AURA_VISION, PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); target->m_invisibility.AddFlag(type); target->m_invisibility.AddValue(type, GetAmount()); @@ -1337,7 +1337,7 @@ void AuraEffect::HandleModInvisibility(AuraApplication const* aurApp, uint8 mode // if not have different invisibility auras. // remove glow vision if (target->GetTypeId() == TYPEID_PLAYER) - target->RemoveByteFlag(PLAYER_FIELD_BYTES2, 3, PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); + target->RemoveByteFlag(PLAYER_FIELD_BYTES2, PLAYER_FIELD_BYTES_2_OFFSET_AURA_VISION, PLAYER_FIELD_BYTE2_INVISIBILITY_GLOW); target->m_invisibility.DelFlag(type); } @@ -1409,7 +1409,7 @@ void AuraEffect::HandleModStealth(AuraApplication const* aurApp, uint8 mode, boo target->SetStandFlags(UNIT_STAND_FLAGS_CREEP); if (target->GetTypeId() == TYPEID_PLAYER) - target->SetByteFlag(PLAYER_FIELD_BYTES2, 3, PLAYER_FIELD_BYTE2_STEALTH); + target->SetByteFlag(PLAYER_FIELD_BYTES2, PLAYER_FIELD_BYTES_2_OFFSET_AURA_VISION, PLAYER_FIELD_BYTE2_STEALTH); } else { @@ -1421,7 +1421,7 @@ void AuraEffect::HandleModStealth(AuraApplication const* aurApp, uint8 mode, boo target->RemoveStandFlags(UNIT_STAND_FLAGS_CREEP); if (target->GetTypeId() == TYPEID_PLAYER) - target->RemoveByteFlag(PLAYER_FIELD_BYTES2, 3, PLAYER_FIELD_BYTE2_STEALTH); + target->RemoveByteFlag(PLAYER_FIELD_BYTES2, PLAYER_FIELD_BYTES_2_OFFSET_AURA_VISION, PLAYER_FIELD_BYTE2_STEALTH); } } @@ -2400,7 +2400,7 @@ void AuraEffect::HandleAuraTrackStealthed(AuraApplication const* aurApp, uint8 m if (target->HasAuraType(GetAuraType())) return; } - target->ApplyModFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTE_TRACK_STEALTHED, apply); + target->ApplyModByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_TRACK_STEALTHED, apply); } void AuraEffect::HandleAuraModStalked(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -2915,7 +2915,7 @@ void AuraEffect::HandleAuraModIncreaseSpeed(AuraApplication const* aurApp, uint8 Unit* target = aurApp->GetTarget(); - target->UpdateSpeed(MOVE_RUN, true); + target->UpdateSpeed(MOVE_RUN); } void AuraEffect::HandleAuraModIncreaseMountedSpeed(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -2930,7 +2930,7 @@ void AuraEffect::HandleAuraModIncreaseFlightSpeed(AuraApplication const* aurApp, Unit* target = aurApp->GetTarget(); if (mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK) - target->UpdateSpeed(MOVE_FLIGHT, true); + target->UpdateSpeed(MOVE_FLIGHT); //! Update ability to fly if (GetAuraType() == SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) @@ -2965,7 +2965,7 @@ void AuraEffect::HandleAuraModIncreaseSwimSpeed(AuraApplication const* aurApp, u Unit* target = aurApp->GetTarget(); - target->UpdateSpeed(MOVE_SWIM, true); + target->UpdateSpeed(MOVE_SWIM); } void AuraEffect::HandleAuraModDecreaseSpeed(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const @@ -2975,12 +2975,12 @@ void AuraEffect::HandleAuraModDecreaseSpeed(AuraApplication const* aurApp, uint8 Unit* target = aurApp->GetTarget(); - target->UpdateSpeed(MOVE_RUN, true); - target->UpdateSpeed(MOVE_SWIM, true); - target->UpdateSpeed(MOVE_FLIGHT, true); - target->UpdateSpeed(MOVE_RUN_BACK, true); - target->UpdateSpeed(MOVE_SWIM_BACK, true); - target->UpdateSpeed(MOVE_FLIGHT_BACK, true); + target->UpdateSpeed(MOVE_RUN); + target->UpdateSpeed(MOVE_SWIM); + target->UpdateSpeed(MOVE_FLIGHT); + target->UpdateSpeed(MOVE_RUN_BACK); + target->UpdateSpeed(MOVE_SWIM_BACK); + target->UpdateSpeed(MOVE_FLIGHT_BACK); } void AuraEffect::HandleAuraModUseNormalSpeed(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const @@ -2990,9 +2990,9 @@ void AuraEffect::HandleAuraModUseNormalSpeed(AuraApplication const* aurApp, uint Unit* target = aurApp->GetTarget(); - target->UpdateSpeed(MOVE_RUN, true); - target->UpdateSpeed(MOVE_SWIM, true); - target->UpdateSpeed(MOVE_FLIGHT, true); + target->UpdateSpeed(MOVE_RUN); + target->UpdateSpeed(MOVE_SWIM); + target->UpdateSpeed(MOVE_FLIGHT); } /*********************************************************/ @@ -5248,7 +5248,7 @@ void AuraEffect::HandleAuraOverrideSpells(AuraApplication const* aurApp, uint8 m if (apply) { - target->SetUInt16Value(PLAYER_FIELD_BYTES2, 0, overrideId); + target->SetUInt16Value(PLAYER_FIELD_BYTES2, PLAYER_BYTES_2_OVERRIDE_SPELLS_UINT16_OFFSET, overrideId); if (OverrideSpellDataEntry const* overrideSpells = sOverrideSpellDataStore.LookupEntry(overrideId)) for (uint8 i = 0; i < MAX_OVERRIDE_SPELL; ++i) if (uint32 spellId = overrideSpells->spellId[i]) @@ -5256,7 +5256,7 @@ void AuraEffect::HandleAuraOverrideSpells(AuraApplication const* aurApp, uint8 m } else { - target->SetUInt16Value(PLAYER_FIELD_BYTES2, 0, 0); + target->SetUInt16Value(PLAYER_FIELD_BYTES2, PLAYER_BYTES_2_OVERRIDE_SPELLS_UINT16_OFFSET, 0); if (OverrideSpellDataEntry const* overrideSpells = sOverrideSpellDataStore.LookupEntry(overrideId)) for (uint8 i = 0; i < MAX_OVERRIDE_SPELL; ++i) if (uint32 spellId = overrideSpells->spellId[i]) @@ -5302,9 +5302,9 @@ void AuraEffect::HandlePreventResurrection(AuraApplication const* aurApp, uint8 return; if (apply) - aurApp->GetTarget()->RemoveByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER); + aurApp->GetTarget()->RemoveByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_RELEASE_TIMER); else if (!aurApp->GetTarget()->GetBaseMap()->Instanceable()) - aurApp->GetTarget()->SetByteFlag(PLAYER_FIELD_BYTES, 0, PLAYER_FIELD_BYTE_RELEASE_TIMER); + aurApp->GetTarget()->SetByteFlag(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_FLAGS, PLAYER_FIELD_BYTE_RELEASE_TIMER); } void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const @@ -5974,14 +5974,14 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c caster->CalcAbsorbResist(target, GetSpellInfo()->GetSchoolMask(), DOT, damage, &absorb, &resist, m_spellInfo); - if (target->GetHealth() < damage) - damage = uint32(target->GetHealth()); - TC_LOG_DEBUG("spells.periodic", "PeriodicTick: %s health leech of %s for %u dmg inflicted by %u abs is %u", GetCasterGUID().ToString().c_str(), target->GetGUID().ToString().c_str(), damage, GetId(), absorb); caster->SendSpellNonMeleeDamageLog(target, GetId(), damage, GetSpellInfo()->GetSchoolMask(), absorb, resist, false, 0, crit); + if (target->GetHealth() < damage) + damage = uint32(target->GetHealth()); + // Set trigger flag uint32 procAttacker = PROC_FLAG_DONE_PERIODIC; uint32 procVictim = PROC_FLAG_TAKEN_PERIODIC; diff --git a/src/server/game/Spells/SpellScript.cpp b/src/server/game/Spells/SpellScript.cpp index 6876f8fa7ef..e2598386466 100644 --- a/src/server/game/Spells/SpellScript.cpp +++ b/src/server/game/Spells/SpellScript.cpp @@ -16,6 +16,7 @@ */ #include "Spell.h" +#include "ScriptMgr.h" #include "SpellAuras.h" #include "SpellScript.h" #include "SpellMgr.h" @@ -50,6 +51,12 @@ void _SpellScript::_Init(std::string const* scriptname, uint32 spellId) m_currentScriptState = SPELL_SCRIPT_STATE_NONE; m_scriptName = scriptname; m_scriptSpellId = spellId; + +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + // Acquire a strong reference to the binary code + // to keep it loaded until all spells are destroyed. + m_moduleReference = sScriptMgr->AcquireModuleReferenceOfScriptName(*scriptname); +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING } std::string const* _SpellScript::_GetScriptName() const diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h index 1d4efd8073d..539bc54cc94 100644 --- a/src/server/game/Spells/SpellScript.h +++ b/src/server/game/Spells/SpellScript.h @@ -22,6 +22,7 @@ #include "SharedDefines.h" #include "SpellAuraDefines.h" #include "Spell.h" +#include "ScriptReloadMgr.h" #include <stack> class Unit; @@ -105,6 +106,16 @@ class TC_GAME_API _SpellScript uint8 m_currentScriptState; std::string const* m_scriptName; uint32 m_scriptSpellId; + + private: + +#ifdef TRINITY_API_USE_DYNAMIC_LINKING + + // Strong reference to keep the binary code loaded + std::shared_ptr<ModuleReference> m_moduleReference; + +#endif // #ifndef TRINITY_API_USE_DYNAMIC_LINKING + public: // // SpellScript/AuraScript interface base diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 6380aece941..4b64ef0bbd8 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -54,6 +54,7 @@ #include "PoolMgr.h" #include "GitRevision.h" #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "SkillDiscovery.h" #include "SkillExtraItems.h" #include "SmartAI.h" @@ -1319,6 +1320,14 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Creature.Zone.Area.Data", false); m_bool_configs[CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA] = sConfigMgr->GetBoolDefault("Calculate.Gameoject.Zone.Area.Data", false); + // HotSwap + m_bool_configs[CONFIG_HOTSWAP_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.Enabled", true); + m_bool_configs[CONFIG_HOTSWAP_RECOMPILER_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableReCompiler", true); + m_bool_configs[CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableEarlyTermination", true); + m_bool_configs[CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableBuildFileRecreation", true); + m_bool_configs[CONFIG_HOTSWAP_INSTALL_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnableInstall", true); + m_bool_configs[CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED] = sConfigMgr->GetBoolDefault("HotSwap.EnablePrefixCorrection", true); + // call ScriptMgr if we're reloading the configuration if (reload) sScriptMgr->OnConfigLoad(reload); @@ -1826,6 +1835,8 @@ void World::SetInitialWorldSettings() m_timers[WUPDATE_PINGDB].SetInterval(getIntConfig(CONFIG_DB_PING_INTERVAL)*MINUTE*IN_MILLISECONDS); // Mysql ping time in minutes + m_timers[WUPDATE_CHECK_FILECHANGES].SetInterval(500); + //to set mailtimer to return mails every day between 4 and 5 am //mailtimer is increased when updating auctions //one second is 1000 -(tested on win system) @@ -2109,6 +2120,13 @@ void World::Update(uint32 diff) m_timers[WUPDATE_AHBOT].Reset(); } + /// <li> Handle file changes + if (m_timers[WUPDATE_CHECK_FILECHANGES].Passed()) + { + sScriptReloadMgr->Update(); + m_timers[WUPDATE_CHECK_FILECHANGES].Reset(); + } + /// <li> Handle session updates when the timer has passed ResetTimeDiffRecord(); UpdateSessions(diff); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index a59d9ef882a..abc0ea452ac 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -81,6 +81,7 @@ enum WorldTimers WUPDATE_DELETECHARS, WUPDATE_AHBOT, WUPDATE_PINGDB, + WUPDATE_CHECK_FILECHANGES, WUPDATE_COUNT }; @@ -167,6 +168,12 @@ enum WorldBoolConfigs CONFIG_RESET_DUEL_HEALTH_MANA, CONFIG_BASEMAP_LOAD_GRIDS, CONFIG_INSTANCEMAP_LOAD_GRIDS, + CONFIG_HOTSWAP_ENABLED, + CONFIG_HOTSWAP_RECOMPILER_ENABLED, + CONFIG_HOTSWAP_EARLY_TERMINATION_ENABLED, + CONFIG_HOTSWAP_BUILD_FILE_RECREATION_ENABLED, + CONFIG_HOTSWAP_INSTALL_ENABLED, + CONFIG_HOTSWAP_PREFIX_CORRECTION_ENABLED, BOOL_CONFIG_VALUE_COUNT }; diff --git a/src/server/scripts/CMakeLists.txt b/src/server/scripts/CMakeLists.txt index 8c0adc4c1e1..31ba073e77d 100644 --- a/src/server/scripts/CMakeLists.txt +++ b/src/server/scripts/CMakeLists.txt @@ -8,69 +8,232 @@ # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# Enable precompiled headers when using the GCC compiler. - -message(STATUS "SCRIPT PREPARATIONS") - -macro(PrepareScripts name out) - file(GLOB_RECURSE found - ${name}/*.h - ${name}/*.cpp - ) - list(APPEND ${out} ${found}) - message(STATUS " -> Prepared: ${name}") -endmacro(PrepareScripts) - -PrepareScripts(Spells PRIVATE_SOURCES) -PrepareScripts(Commands PRIVATE_SOURCES) - -if(SCRIPTS) - PrepareScripts(Custom PRIVATE_SOURCES) - PrepareScripts(World PRIVATE_SOURCES) - PrepareScripts(OutdoorPvP PRIVATE_SOURCES) - PrepareScripts(EasternKingdoms PRIVATE_SOURCES) - PrepareScripts(Kalimdor PRIVATE_SOURCES) - PrepareScripts(Outland PRIVATE_SOURCES) - PrepareScripts(Northrend PRIVATE_SOURCES) - PrepareScripts(Events PRIVATE_SOURCES) - PrepareScripts(Pet PRIVATE_SOURCES) +message("") + +# Make the script module list available in the current scope +GetScriptModuleList(SCRIPT_MODULE_LIST) + +# Make the native install offset available in this scope +GetInstallOffset(INSTALL_OFFSET) + +# Sets the SCRIPTS_${SCRIPT_MODULE} variables +# when using predefined templates for script building +# like dynamic, static, minimal-static... +# Sets SCRIPTS_DEFAULT_LINKAGE +if (SCRIPTS MATCHES "dynamic") + set(SCRIPTS_DEFAULT_LINKAGE "dynamic") +elseif(SCRIPTS MATCHES "static") + set(SCRIPTS_DEFAULT_LINKAGE "static") +else() + set(SCRIPTS_DEFAULT_LINKAGE "disabled") +endif() +# Sets SCRIPTS_USE_WHITELIST +# Sets SCRIPTS_WHITELIST +if (SCRIPTS MATCHES "minimal") + set(SCRIPTS_USE_WHITELIST ON) + # Whitelist which is used when minimal is selected + list(APPEND SCRIPTS_WHITELIST Commands Spells) endif() -message(STATUS "SCRIPT PREPARATION COMPLETE") -message("") +# Set the SCRIPTS_${SCRIPT_MODULE} variables from the +# variables set above +foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + if (${SCRIPT_MODULE_VARIABLE} STREQUAL "default") + if(SCRIPTS_USE_WHITELIST) + list(FIND SCRIPTS_WHITELIST "${SCRIPT_MODULE}" INDEX) + if (${INDEX} GREATER -1) + set(${SCRIPT_MODULE_VARIABLE} ${SCRIPTS_DEFAULT_LINKAGE}) + else() + set(${SCRIPT_MODULE_VARIABLE} "disabled") + endif() + else() + set(${SCRIPT_MODULE_VARIABLE} ${SCRIPTS_DEFAULT_LINKAGE}) + endif() + endif() + # Build the Graph values + if (${SCRIPT_MODULE_VARIABLE} MATCHES "dynamic") + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + GetNativeSharedLibraryName(${SCRIPT_MODULE_PROJECT_NAME} SCRIPT_PROJECT_LIBRARY) + list(APPEND GRAPH_KEYS ${SCRIPT_MODULE_PROJECT_NAME}) + set(GRAPH_VALUE_DISPLAY_${SCRIPT_MODULE_PROJECT_NAME} ${SCRIPT_PROJECT_LIBRARY}) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_${SCRIPT_MODULE_PROJECT_NAME} ${SCRIPT_MODULE}) + elseif(${SCRIPT_MODULE_VARIABLE} MATCHES "static") + list(APPEND GRAPH_KEYS worldserver) + set(GRAPH_VALUE_DISPLAY_worldserver worldserver) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_worldserver ${SCRIPT_MODULE}) + else() + list(APPEND GRAPH_KEYS disabled) + set(GRAPH_VALUE_DISPLAY_disabled disabled) + list(APPEND GRAPH_VALUE_CONTAINS_MODULES_disabled ${SCRIPT_MODULE}) + endif() +endforeach() -list(APPEND PRIVATE_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.h - ${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.cpp) +list(SORT GRAPH_KEYS) +list(REMOVE_DUPLICATES GRAPH_KEYS) +# Display the script graph +message("* Script configuration (${SCRIPTS}): + |") + +foreach(GRAPH_KEY ${GRAPH_KEYS}) + if (NOT GRAPH_KEY STREQUAL "disabled") + message(" +- ${GRAPH_VALUE_DISPLAY_${GRAPH_KEY}}") + else() + message(" | ${GRAPH_VALUE_DISPLAY_${GRAPH_KEY}}") + endif() + foreach(GRAPH_PROJECT_ENTRY ${GRAPH_VALUE_CONTAINS_MODULES_${GRAPH_KEY}}) + message(" | +- ${GRAPH_PROJECT_ENTRY}") + endforeach() + message(" |") +endforeach() + +# Base sources which are used by every script project if (USE_SCRIPTPCH) - set(PRIVATE_PCH_HEADER PrecompiledHeaders/ScriptPCH.h) - set(PRIVATE_PCH_SOURCE PrecompiledHeaders/ScriptPCH.cpp) + set(PRIVATE_PCH_HEADER ScriptPCH.h) + set(PRIVATE_PCH_SOURCE ScriptPCH.cpp) endif () GroupSources(${CMAKE_CURRENT_SOURCE_DIR}) +# Configures the scriptloader with the given name and stores the output in the LOADER_OUT variable. +# It is possible to expose multiple subdirectories from the same scriptloader through passing +# it to the variable arguments +function(ConfigureScriptLoader SCRIPTLOADER_NAME LOADER_OUT IS_DYNAMIC_SCRIPTLOADER) + # Deduces following variables which are referenced by thge template: + # TRINITY_IS_DYNAMIC_SCRIPTLOADER + # TRINITY_SCRIPTS_FORWARD_DECL + # TRINITY_SCRIPTS_INVOKE + # TRINITY_CURRENT_SCRIPT_PROJECT + + # To generate export macros + set(TRINITY_IS_DYNAMIC_SCRIPTLOADER ${IS_DYNAMIC_SCRIPTLOADER}) + # To generate forward declarations of the loading functions + unset(TRINITY_SCRIPTS_FORWARD_DECL) + unset(TRINITY_SCRIPTS_INVOKE) + # The current script project which is built in + set(TRINITY_CURRENT_SCRIPT_PROJECT ${SCRIPTLOADER_NAME}) + foreach(LOCALE_SCRIPT_MODULE ${ARGN}) + # Determine the loader function ("Add##${NameOfDirectory}##Scripts()") + set(LOADER_FUNCTION + "Add${LOCALE_SCRIPT_MODULE}Scripts()") + # Generate the funciton call and the forward declarations + set(TRINITY_SCRIPTS_FORWARD_DECL + "${TRINITY_SCRIPTS_FORWARD_DECL}void ${LOADER_FUNCTION};\n") + set(TRINITY_SCRIPTS_INVOKE + "${TRINITY_SCRIPTS_INVOKE} ${LOADER_FUNCTION};\n") + endforeach() + set(GENERATED_LOADER ${CMAKE_CURRENT_BINARY_DIR}/gen_scriptloader/${SCRIPTLOADER_NAME}/ScriptLoader.cpp) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ScriptLoader.cpp.in.cmake ${GENERATED_LOADER}) + set(${LOADER_OUT} ${GENERATED_LOADER} PARENT_SCOPE) +endfunction() + +# Generates the actual script projects +# Fills the STATIC_SCRIPT_MODULES and DYNAMIC_SCRIPT_MODULE_PROJECTS variables +# which contain the names which scripts are linked statically/dynamically and +# adds the sources of the static modules to the PRIVATE_SOURCES variable. +foreach(SCRIPT_MODULE ${SCRIPT_MODULE_LIST}) + GetPathToScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PATH) + ScriptModuleNameToVariable(${SCRIPT_MODULE} SCRIPT_MODULE_VARIABLE) + + if ((${SCRIPT_MODULE_VARIABLE} STREQUAL "disabled") OR + (${SCRIPT_MODULE_VARIABLE} STREQUAL "static")) + # Uninstall disabled modules + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + GetNativeSharedLibraryName(${SCRIPT_MODULE_PROJECT_NAME} SCRIPT_MODULE_OUTPUT_NAME) + list(APPEND DISABLED_SCRIPT_MODULE_PROJECTS ${INSTALL_OFFSET}/${SCRIPT_MODULE_OUTPUT_NAME}) + if (${SCRIPT_MODULE_VARIABLE} STREQUAL "static") + # Add the module name to STATIC_SCRIPT_MODULES + list(APPEND STATIC_SCRIPT_MODULES ${SCRIPT_MODULE}) + # Add the module content to the whole static module + CollectSourceFiles(${SCRIPT_MODULE_PATH} PRIVATE_SOURCES) + endif() + elseif(${SCRIPT_MODULE_VARIABLE} STREQUAL "dynamic") + # Generate an own dynamic module which is loadable on runtime + # Add the module content to the whole static module + unset(SCRIPT_MODULE_PRIVATE_SOURCES) + CollectSourceFiles(${SCRIPT_MODULE_PATH} SCRIPT_MODULE_PRIVATE_SOURCES) + # Configure the scriptloader + ConfigureScriptLoader(${SCRIPT_MODULE} SCRIPT_MODULE_PRIVATE_SCRIPTLOADER ON ${SCRIPT_MODULE}) + GetProjectNameOfScriptModule(${SCRIPT_MODULE} SCRIPT_MODULE_PROJECT_NAME) + # Add the module name to DYNAMIC_SCRIPT_MODULES + list(APPEND DYNAMIC_SCRIPT_MODULE_PROJECTS ${SCRIPT_MODULE_PROJECT_NAME}) + # Create the script module project + add_library(${SCRIPT_MODULE_PROJECT_NAME} SHARED + ${PRIVATE_PCH_SOURCE} + ${SCRIPT_MODULE_PRIVATE_SOURCES} + ${SCRIPT_MODULE_PRIVATE_SCRIPTLOADER}) + target_link_libraries(${SCRIPT_MODULE_PROJECT_NAME} + PUBLIC + game) + set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} + PROPERTIES + FOLDER + "scripts") + + if(UNIX) + install(TARGETS ${SCRIPT_MODULE_PROJECT_NAME} + DESTINATION ${INSTALL_OFFSET} + COMPONENT ${SCRIPT_MODULE_PROJECT_NAME}) + elseif(WIN32) + install(TARGETS ${SCRIPT_MODULE_PROJECT_NAME} + RUNTIME DESTINATION ${INSTALL_OFFSET} + COMPONENT ${SCRIPT_MODULE_PROJECT_NAME}) + if(MSVC) + # Place the script modules in the script subdirectory + set_target_properties(${SCRIPT_MODULE_PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/Debug/scripts + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release/scripts + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/RelWithDebInfo/scripts + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/bin/MinSizeRel/scripts) + endif() + endif() + else() + message(FATAL_ERROR "Unknown value \"${${SCRIPT_MODULE_VARIABLE}}\"!") + endif() +endforeach() + +# Add the dynamic script modules to the worldserver as dependency +set(WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES ${DYNAMIC_SCRIPT_MODULE_PROJECTS} PARENT_SCOPE) + +ConfigureScriptLoader("static" SCRIPT_MODULE_PRIVATE_SCRIPTLOADER OFF ${STATIC_SCRIPT_MODULES}) + add_library(scripts STATIC + ScriptLoader.h ${PRIVATE_PCH_SOURCE} - ${PRIVATE_SOURCES} -) + ${SCRIPT_MODULE_PRIVATE_SCRIPTLOADER} + ${PRIVATE_SOURCES}) -target_include_directories(scripts +target_link_libraries(scripts PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}) + game-interface) -target_link_libraries(scripts +target_include_directories(scripts PUBLIC - game) + ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(scripts - PROPERTIES - FOLDER - "server") + PROPERTIES + FOLDER + "scripts") # Generate precompiled header if (USE_SCRIPTPCH) - add_cxx_pch(scripts ${PRIVATE_PCH_HEADER} ${PRIVATE_PCH_SOURCE}) + list(APPEND ALL_SCRIPT_PROJECTS scripts ${DYNAMIC_SCRIPT_MODULE_PROJECTS}) + add_cxx_pch("${ALL_SCRIPT_PROJECTS}" ${PRIVATE_PCH_HEADER} ${PRIVATE_PCH_SOURCE}) +endif() + +# Remove all shared libraries in the installl directory which +# are contained in the static library already. +if (DISABLED_SCRIPT_MODULE_PROJECTS) + install(CODE " + foreach(SCRIPT_TO_UNINSTALL ${DISABLED_SCRIPT_MODULE_PROJECTS}) + if (EXISTS \"\${SCRIPT_TO_UNINSTALL}\") + message(STATUS \"Uninstalling: \${SCRIPT_TO_UNINSTALL}\") + file(REMOVE \"\${SCRIPT_TO_UNINSTALL}\") + endif() + endforeach() + ") endif() + +message("") diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 01a048c8795..1d8094885d4 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -124,6 +124,23 @@ public: return false; } + // Dump camera locations + if (CinematicSequencesEntry const* cineSeq = sCinematicSequencesStore.LookupEntry(id)) + { + std::unordered_map<uint32, FlyByCameraCollection>::const_iterator itr = sFlyByCameraStore.find(cineSeq->cinematicCamera); + if (itr != sFlyByCameraStore.end()) + { + handler->PSendSysMessage("Waypoints for sequence %u, camera %u", id, cineSeq->cinematicCamera); + uint32 count = 1 ; + for (FlyByCamera cam : itr->second) + { + handler->PSendSysMessage("%02u - %7ums [%f, %f, %f] Facing %f (%f degrees)", count, cam.timeStamp, cam.locations.x, cam.locations.y, cam.locations.z, cam.locations.w, cam.locations.w * (180 / M_PI)); + count++; + } + handler->PSendSysMessage("%u waypoints dumped", itr->second.size()); + } + } + handler->GetSession()->GetPlayer()->SendCinematicStart(id); return true; } diff --git a/src/server/scripts/Commands/cs_modify.cpp b/src/server/scripts/Commands/cs_modify.cpp index 3a062b21c3e..f1ddb448b35 100644 --- a/src/server/scripts/Commands/cs_modify.cpp +++ b/src/server/scripts/Commands/cs_modify.cpp @@ -477,11 +477,11 @@ public: if (handler->needReportToTarget(target)) ChatHandler(target->GetSession()).PSendSysMessage(LANG_YOURS_ASPEED_CHANGED, handler->GetNameLink().c_str(), ASpeed); - target->SetSpeed(MOVE_WALK, ASpeed, true); - target->SetSpeed(MOVE_RUN, ASpeed, true); - target->SetSpeed(MOVE_SWIM, ASpeed, true); - //target->SetSpeed(MOVE_TURN, ASpeed, true); - target->SetSpeed(MOVE_FLIGHT, ASpeed, true); + target->SetSpeedRate(MOVE_WALK, ASpeed); + target->SetSpeedRate(MOVE_RUN, ASpeed); + target->SetSpeedRate(MOVE_SWIM, ASpeed); + //target->SetSpeedRate(MOVE_TURN, ASpeed); + target->SetSpeedRate(MOVE_FLIGHT, ASpeed); return true; } @@ -525,7 +525,7 @@ public: if (handler->needReportToTarget(target)) ChatHandler(target->GetSession()).PSendSysMessage(LANG_YOURS_SPEED_CHANGED, handler->GetNameLink().c_str(), Speed); - target->SetSpeed(MOVE_RUN, Speed, true); + target->SetSpeedRate(MOVE_RUN, Speed); return true; } @@ -570,7 +570,7 @@ public: if (handler->needReportToTarget(target)) ChatHandler(target->GetSession()).PSendSysMessage(LANG_YOURS_SWIM_SPEED_CHANGED, handler->GetNameLink().c_str(), Swim); - target->SetSpeed(MOVE_SWIM, Swim, true); + target->SetSpeedRate(MOVE_SWIM, Swim); return true; } @@ -615,7 +615,7 @@ public: if (handler->needReportToTarget(target)) ChatHandler(target->GetSession()).PSendSysMessage(LANG_YOURS_BACK_SPEED_CHANGED, handler->GetNameLink().c_str(), BSpeed); - target->SetSpeed(MOVE_RUN_BACK, BSpeed, true); + target->SetSpeedRate(MOVE_RUN_BACK, BSpeed); return true; } @@ -651,7 +651,7 @@ public: if (handler->needReportToTarget(target)) ChatHandler(target->GetSession()).PSendSysMessage(LANG_YOURS_FLY_SPEED_CHANGED, handler->GetNameLink().c_str(), FSpeed); - target->SetSpeed(MOVE_FLIGHT, FSpeed, true); + target->SetSpeedRate(MOVE_FLIGHT, FSpeed); return true; } @@ -1339,8 +1339,8 @@ public: } // Set gender - target->SetByteValue(UNIT_FIELD_BYTES_0, 2, gender); - target->SetByteValue(PLAYER_BYTES_3, 0, gender); + target->SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_GENDER, gender); + target->SetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER, gender); // Change display ID target->InitDisplayIds(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index 56b0dbf43d0..eb28a8adae4 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -251,7 +251,7 @@ public: static bool HandleReloadAllScriptsCommand(ChatHandler* handler, const char* /*args*/) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->PSendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -393,7 +393,7 @@ public: static bool HandleReloadCommandCommand(ChatHandler* handler, const char* /*args*/) { - handler->SetLoadCommandTable(true); + ChatHandler::invalidateCommandTable(); handler->SendGlobalGMSysMessage("DB table `command` will be reloaded at next chat command use."); return true; } @@ -893,7 +893,7 @@ public: static bool HandleReloadEventScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -913,7 +913,7 @@ public: static bool HandleReloadWpScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); @@ -946,7 +946,7 @@ public: static bool HandleReloadSpellScriptsCommand(ChatHandler* handler, const char* args) { - if (sScriptMgr->IsScriptScheduled()) + if (sMapMgr->IsScriptScheduled()) { handler->SendSysMessage("DB scripts used currently, please attempt reload later."); handler->SetSentErrorMessage(true); diff --git a/src/server/scripts/Commands/cs_reset.cpp b/src/server/scripts/Commands/cs_reset.cpp index 05941120423..ba1dab28350 100644 --- a/src/server/scripts/Commands/cs_reset.cpp +++ b/src/server/scripts/Commands/cs_reset.cpp @@ -102,7 +102,7 @@ public: player->setFactionForRace(player->getRace()); - player->SetUInt32Value(UNIT_FIELD_BYTES_0, ((player->getRace()) | (player->getClass() << 8) | (player->getGender() << 16) | (powerType << 24))); + player->SetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_POWER_TYPE, powerType); // reset only if player not in some form; if (player->GetShapeshiftForm() == FORM_NONE) diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp index 51bb314968d..ffec32c0619 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_baron_geddon.cpp @@ -27,6 +27,8 @@ EndScriptData */ #include "ScriptMgr.h" #include "ScriptedCreature.h" #include "molten_core.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" enum Emotes { @@ -36,9 +38,10 @@ enum Emotes enum Spells { SPELL_INFERNO = 19695, + SPELL_INFERNO_DMG = 19698, SPELL_IGNITE_MANA = 19659, SPELL_LIVING_BOMB = 20475, - SPELL_ARMAGEDDON = 20479, + SPELL_ARMAGEDDON = 20478, }; enum Events @@ -119,7 +122,36 @@ class boss_baron_geddon : public CreatureScript } }; +class spell_baron_geddon_inferno : public SpellScriptLoader +{ + public: + spell_baron_geddon_inferno() : SpellScriptLoader("spell_baron_geddon_inferno") { } + + class spell_baron_geddon_inferno_AuraScript : public AuraScript + { + PrepareAuraScript(spell_baron_geddon_inferno_AuraScript); + + void OnPeriodic(AuraEffect const* aurEff) + { + PreventDefaultAction(); + int32 damageForTick[8] = { 500, 500, 1000, 1000, 2000, 2000, 3000, 5000 }; + GetTarget()->CastCustomSpell(SPELL_INFERNO_DMG, SPELLVALUE_BASE_POINT0, damageForTick[aurEff->GetTickNumber() - 1], (Unit*)nullptr, TRIGGERED_FULL_MASK, nullptr, aurEff); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_baron_geddon_inferno_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_baron_geddon_inferno_AuraScript(); + } +}; + void AddSC_boss_baron_geddon() { new boss_baron_geddon(); + new spell_baron_geddon_inferno(); } diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_nightbane.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_nightbane.cpp index 7d7c60ac419..6ff20e66f7f 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_nightbane.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_nightbane.cpp @@ -138,7 +138,7 @@ public: { Initialize(); - me->SetSpeed(MOVE_RUN, 2.0f); + me->SetSpeedRate(MOVE_RUN, 2.0f); me->SetDisableGravity(true); me->SetWalk(false); me->setActive(true); diff --git a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp index 4aef3c8d4a3..ace15cc2a7e 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp @@ -562,7 +562,7 @@ public: arca->GetMotionMaster()->MovePoint(0, -11010.82f, -1761.18f, 156.47f); arca->setActive(true); arca->InterruptNonMeleeSpells(true); - arca->SetSpeed(MOVE_FLIGHT, 2.0f); + arca->SetSpeedRate(MOVE_FLIGHT, 2.0f); } return 10000; case 13: diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp index b809128e731..ebe406c8909 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp @@ -405,7 +405,7 @@ public: Creature* Orb = DoSpawnCreature(CREATURE_ARCANE_SPHERE, 5, 5, 0, 0, TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 30000); if (Orb && target) { - Orb->SetSpeed(MOVE_RUN, 0.5f); + Orb->SetSpeedRate(MOVE_RUN, 0.5f); Orb->AddThreat(target, 1000000.0f); Orb->AI()->AttackStart(target); } diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp index 81044a0dbb1..3cf78b79f67 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp @@ -396,7 +396,7 @@ class npc_eye_of_acherus : public CreatureScript if (Player* owner = me->GetCharmerOrOwner()->ToPlayer()) { for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) - me->SetSpeed(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i)), true); + me->SetSpeedRate(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i))); Talk(TALK_MOVE_START, owner); } me->GetMotionMaster()->MovePath(me->GetEntry() * 100, false); @@ -419,7 +419,7 @@ class npc_eye_of_acherus : public CreatureScript { owner->RemoveAura(SPELL_EYE_FLIGHT_BOOST); for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i) - me->SetSpeed(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i)), true); + me->SetSpeedRate(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i))); Talk(TALK_CONTROL, owner); } @@ -703,7 +703,7 @@ class npc_dark_rider_of_acherus : public CreatureScript TargetGUID = who->GetGUID(); me->SetWalk(true); - me->SetSpeed(MOVE_RUN, 0.4f); + me->SetSpeedRate(MOVE_RUN, 0.4f); me->GetMotionMaster()->MoveChase(who); me->SetTarget(TargetGUID); Intro = true; @@ -1050,7 +1050,7 @@ class npc_scarlet_miner_cart : public CreatureScript // Not 100% correct, but movement is smooth. Sometimes miner walks faster // than normal, this speed is fast enough to keep up at those times. - me->SetSpeed(MOVE_RUN, 1.25f); + me->SetSpeedRate(MOVE_RUN, 1.25f); me->GetMotionMaster()->MoveFollow(miner, 1.0f, 0); } diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp index e336ff24382..d25a225717a 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp @@ -923,7 +923,7 @@ public: if (ObjectAccessor::GetCreature(*me, uiLichKingGUID)) DoCast(me, SPELL_MOGRAINE_CHARGE); // jumping charge // doesn't make it looks well, so workarounds, Darion charges, looks better - me->SetSpeed(MOVE_RUN, 3.0f); + me->SetSpeedRate(MOVE_RUN, 3.0f); me->SetWalk(false); SetHoldState(false); JumpToNextStep(0); @@ -935,7 +935,7 @@ public: temp->HandleEmoteCommand(EMOTE_ONESHOT_KICK); temp->AI()->Talk(SAY_LIGHT_OF_DAWN46); } - me->SetSpeed(MOVE_RUN, 6.0f); + me->SetSpeedRate(MOVE_RUN, 6.0f); me->SetStandState(UNIT_STAND_STATE_DEAD); SetHoldState(false); // Darion got kicked by lich king JumpToNextStep(0); @@ -997,7 +997,7 @@ public: Unit* temp = me->SummonCreature(NPC_DEFENDER_OF_THE_LIGHT, LightofDawnLoc[0].GetPositionWithOffset({ float(rand32() % 10), float(rand32() % 10), 0.0f, 0.0f }), TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 10000); temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_ATTACK_UNARMED); temp->SetWalk(false); - temp->SetSpeed(MOVE_RUN, 2.0f); + temp->SetSpeedRate(MOVE_RUN, 2.0f); temp->setFaction(me->getFaction()); temp->GetMotionMaster()->MovePoint(0, fLichPositionX, fLichPositionY, fLichPositionZ); uiDefenderGUID[0] = temp->GetGUID(); @@ -1005,7 +1005,7 @@ public: temp = me->SummonCreature(NPC_RIMBLAT_EARTHSHATTER, LightofDawnLoc[0].GetPositionWithOffset({ float(rand32() % 10), float(rand32() % 10), 0.0f, 0.0f }), TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN, 10000); temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_ATTACK_UNARMED); temp->SetWalk(false); - temp->SetSpeed(MOVE_RUN, 2.0f); + temp->SetSpeedRate(MOVE_RUN, 2.0f); temp->setFaction(me->getFaction()); temp->GetMotionMaster()->MovePoint(0, fLichPositionX, fLichPositionY, fLichPositionZ); uiEarthshatterGUID[0] = temp->GetGUID(); @@ -1014,7 +1014,7 @@ public: { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_ATTACK_UNARMED); temp->SetWalk(false); - temp->SetSpeed(MOVE_RUN, 2.0f); + temp->SetSpeedRate(MOVE_RUN, 2.0f); temp->GetMotionMaster()->MovePoint(0, fLichPositionX, fLichPositionY, fLichPositionZ); temp->AI()->Talk(SAY_LIGHT_OF_DAWN50); } @@ -1022,7 +1022,7 @@ public: { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_ATTACK_UNARMED); temp->SetWalk(false); - temp->SetSpeed(MOVE_RUN, 2.0f); + temp->SetSpeedRate(MOVE_RUN, 2.0f); temp->HandleEmoteCommand(EMOTE_STATE_ATTACK_UNARMED); temp->GetMotionMaster()->MovePoint(0, fLichPositionX, fLichPositionY, fLichPositionZ); } @@ -1030,7 +1030,7 @@ public: { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_ATTACK_UNARMED); temp->SetWalk(false); - temp->SetSpeed(MOVE_RUN, 2.0f); + temp->SetSpeedRate(MOVE_RUN, 2.0f); temp->GetMotionMaster()->MovePoint(0, fLichPositionX, fLichPositionY, fLichPositionZ); } } @@ -1044,33 +1044,33 @@ public: if (Creature* temp = ObjectAccessor::GetCreature(*me, uiMaxwellGUID)) { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - temp->SetSpeed(MOVE_RUN, 6.0f); + temp->SetSpeedRate(MOVE_RUN, 6.0f); temp->SetStandState(UNIT_STAND_STATE_DEAD); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[14]); } if (Creature* temp = ObjectAccessor::GetCreature(*me, uiKorfaxGUID)) { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - temp->SetSpeed(MOVE_RUN, 6.0f); + temp->SetSpeedRate(MOVE_RUN, 6.0f); temp->SetStandState(UNIT_STAND_STATE_DEAD); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[11]); } if (Creature* temp = ObjectAccessor::GetCreature(*me, uiEligorGUID)) { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - temp->SetSpeed(MOVE_RUN, 6.0f); + temp->SetSpeedRate(MOVE_RUN, 6.0f); temp->SetStandState(UNIT_STAND_STATE_DEAD); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[17]); } if (Creature* temp = ObjectAccessor::GetCreature(*me, uiDefenderGUID[0])) { - temp->SetSpeed(MOVE_RUN, 6.0f); + temp->SetSpeedRate(MOVE_RUN, 6.0f); temp->SetStandState(UNIT_STAND_STATE_DEAD); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[0].GetPositionWithOffset({ float(rand32() % 10), float(rand32() % 10), 0.0f, 0.0f })); } if (Creature* temp = ObjectAccessor::GetCreature(*me, uiEarthshatterGUID[0])) { - temp->SetSpeed(MOVE_RUN, 6.0f); + temp->SetSpeedRate(MOVE_RUN, 6.0f); temp->SetStandState(UNIT_STAND_STATE_DEAD); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[0].GetPositionWithOffset({ float(rand32() % 10), float(rand32() % 10), 0.0f, 0.0f })); } @@ -1093,7 +1093,7 @@ public: break; case 46: // Darion stand up, "not today" - me->SetSpeed(MOVE_RUN, 1.0f); + me->SetSpeedRate(MOVE_RUN, 1.0f); me->SetWalk(true); me->SetStandState(UNIT_STAND_STATE_STAND); Talk(SAY_LIGHT_OF_DAWN53); @@ -1153,7 +1153,7 @@ public: temp->AI()->Talk(EMOTE_LIGHT_OF_DAWN16); temp->CastSpell(temp, SPELL_TIRION_CHARGE, false); // jumping charge temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY2H); - temp->SetSpeed(MOVE_RUN, 3.0f); // workarounds, make Tirion still running + temp->SetSpeedRate(MOVE_RUN, 3.0f); // workarounds, make Tirion still running temp->SetWalk(false); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[2]); if (Creature* lktemp = ObjectAccessor::GetCreature(*me, uiLichKingGUID)) @@ -1171,7 +1171,7 @@ public: case 54: if (Creature* temp = ObjectAccessor::GetCreature(*me, uiLichKingGUID)) { - temp->SetSpeed(MOVE_RUN, 1.0f); + temp->SetSpeedRate(MOVE_RUN, 1.0f); me->SetWalk(true); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[29]); // 26 } @@ -1208,7 +1208,7 @@ public: if (Creature* temp = ObjectAccessor::GetCreature(*me, uiTirionGUID)) // Tirion runs to Darion { temp->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE); - temp->SetSpeed(MOVE_RUN, 1.0f); + temp->SetSpeedRate(MOVE_RUN, 1.0f); temp->GetMotionMaster()->MovePoint(0, LightofDawnLoc[6]); } JumpToNextStep(2500); diff --git a/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp b/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp index 74b300919d8..f7a1c18c234 100644 --- a/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletMonastery/boss_headless_horseman.cpp @@ -450,7 +450,7 @@ public: me->SetVisible(false); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); me->SetDisableGravity(true); - me->SetSpeed(MOVE_WALK, 5.0f, true); + me->SetSpeedRate(MOVE_WALK, 5.0f); wp_reached = false; count = 0; say_timer = 3000; diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp index d9b481d7b6d..fded249f9ca 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp @@ -249,7 +249,7 @@ public: me->CastStop(SPELL_FOG_BREATH); me->RemoveAurasDueToSpell(SPELL_FOG_BREATH); me->StopMoving(); - me->SetSpeed(MOVE_RUN, 2.0f); + me->SetSpeedRate(MOVE_RUN, 2.0f); events.ScheduleEvent(EVENT_CLEAVE, urand(5000, 10000)); events.ScheduleEvent(EVENT_CORROSION, urand(10000, 20000)); @@ -530,7 +530,7 @@ public: npc_felmyst_vaporAI(Creature* creature) : ScriptedAI(creature) { me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); - me->SetSpeed(MOVE_RUN, 0.8f); + me->SetSpeedRate(MOVE_RUN, 0.8f); } void Reset() override { } diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_akilzon.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_akilzon.cpp index 791f68bae31..72e76ba52bf 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_akilzon.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_akilzon.cpp @@ -425,7 +425,7 @@ class npc_akilzon_eagle : public CreatureScript if (Unit* target = ObjectAccessor::GetUnit(*me, TargetGUID)) DoCast(target, SPELL_EAGLE_SWOOP, true); TargetGUID.Clear(); - me->SetSpeed(MOVE_RUN, 1.2f); + me->SetSpeedRate(MOVE_RUN, 1.2f); EagleSwoop_Timer = urand(5000, 10000); } } @@ -454,7 +454,7 @@ class npc_akilzon_eagle : public CreatureScript { target->GetContactPoint(me, x, y, z); z += 2; - me->SetSpeed(MOVE_RUN, 5.0f); + me->SetSpeedRate(MOVE_RUN, 5.0f); TargetGUID = target->GetGUID(); } me->GetMotionMaster()->MovePoint(0, x, y, z); diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp index 0407cb6cd7c..188f9d0cc03 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_nalorakk.cpp @@ -142,7 +142,7 @@ class boss_nalorakk : public CreatureScript me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); inMove = false; waitTimer = 0; - me->SetSpeed(MOVE_RUN, 2); + me->SetSpeedRate(MOVE_RUN, 2); me->SetWalk(false); }else { diff --git a/src/server/scripts/EasternKingdoms/ZulAman/boss_zuljin.cpp b/src/server/scripts/EasternKingdoms/ZulAman/boss_zuljin.cpp index 96c0798ae05..a1c2369cf66 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/boss_zuljin.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/boss_zuljin.cpp @@ -347,7 +347,7 @@ class boss_zuljin : public CreatureScript Vortex->CastSpell(Vortex, SPELL_CYCLONE_PASSIVE, true); Vortex->CastSpell(Vortex, SPELL_CYCLONE_VISUAL, true); Vortex->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); - Vortex->SetSpeed(MOVE_RUN, 1.0f); + Vortex->SetSpeedRate(MOVE_RUN, 1.0f); Vortex->AI()->AttackStart(SelectTarget(SELECT_TARGET_RANDOM, 0)); DoZoneInCombat(Vortex); } @@ -438,7 +438,7 @@ class boss_zuljin : public CreatureScript { if (me->GetVictim()) TankGUID = me->EnsureVictim()->GetGUID(); - me->SetSpeed(MOVE_RUN, 5.0f); + me->SetSpeedRate(MOVE_RUN, 5.0f); AttackStart(target); // change victim Claw_Rage_Timer = 0; Claw_Loop_Timer = 500; @@ -462,7 +462,7 @@ class boss_zuljin : public CreatureScript if (Claw_Counter == 12) { Claw_Rage_Timer = urand(15000, 20000); - me->SetSpeed(MOVE_RUN, 1.2f); + me->SetSpeedRate(MOVE_RUN, 1.2f); AttackStart(ObjectAccessor::GetUnit(*me, TankGUID)); TankGUID.Clear(); return; @@ -487,7 +487,7 @@ class boss_zuljin : public CreatureScript if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0)) { TankGUID = me->EnsureVictim()->GetGUID(); - me->SetSpeed(MOVE_RUN, 5.0f); + me->SetSpeedRate(MOVE_RUN, 5.0f); AttackStart(target); // change victim Lynx_Rush_Timer = 0; Claw_Counter = 0; @@ -510,7 +510,7 @@ class boss_zuljin : public CreatureScript if (Claw_Counter == 9) { Lynx_Rush_Timer = urand(15000, 20000); - me->SetSpeed(MOVE_RUN, 1.2f); + me->SetSpeedRate(MOVE_RUN, 1.2f); AttackStart(ObjectAccessor::GetUnit(*me, TankGUID)); TankGUID.Clear(); } diff --git a/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp b/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp index 8c781bb9001..b794a653791 100644 --- a/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp +++ b/src/server/scripts/EasternKingdoms/eastern_kingdoms_script_loader.cpp @@ -89,7 +89,6 @@ void AddSC_boss_sulfuron(); void AddSC_boss_majordomo(); void AddSC_boss_ragnaros(); void AddSC_instance_molten_core(); -void AddSC_instance_ragefire_chasm(); //Ragefire Chasm void AddSC_the_scarlet_enclave(); //Scarlet Enclave void AddSC_the_scarlet_enclave_c1(); void AddSC_the_scarlet_enclave_c2(); @@ -268,7 +267,6 @@ void AddEasternKingdomsScripts() AddSC_boss_majordomo(); AddSC_boss_ragnaros(); AddSC_instance_molten_core(); - AddSC_instance_ragefire_chasm(); //Ragefire Chasm AddSC_the_scarlet_enclave(); //Scarlet Enclave AddSC_the_scarlet_enclave_c1(); AddSC_the_scarlet_enclave_c2(); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp index 44cbdec2cb5..0526dcfa630 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/EscapeFromDurnholdeKeep/old_hillsbrad.cpp @@ -479,12 +479,12 @@ public: void DoMount() { me->Mount(SKARLOC_MOUNT_MODEL); - me->SetSpeed(MOVE_RUN, SPEED_MOUNT); + me->SetSpeedRate(MOVE_RUN, SPEED_MOUNT); } void DoUnmount() { me->Dismount(); - me->SetSpeed(MOVE_RUN, SPEED_RUN); + me->SetSpeedRate(MOVE_RUN, SPEED_RUN); } void EnterCombat(Unit* /*who*/) override { diff --git a/src/server/scripts/Kalimdor/OnyxiasLair/boss_onyxia.cpp b/src/server/scripts/Kalimdor/OnyxiasLair/boss_onyxia.cpp index 7da15b1fdce..71ebe870e3d 100644 --- a/src/server/scripts/Kalimdor/OnyxiasLair/boss_onyxia.cpp +++ b/src/server/scripts/Kalimdor/OnyxiasLair/boss_onyxia.cpp @@ -205,7 +205,7 @@ public: PointData = GetMoveData(); MovePoint = PointData->LocIdEnd; - me->SetSpeed(MOVE_FLIGHT, 1.5f); + me->SetSpeedRate(MOVE_FLIGHT, 1.5f); me->GetMotionMaster()->MovePoint(8, MiddleRoomLocation); } } @@ -220,7 +220,7 @@ public: PointData = GetMoveData(); if (PointData) { - me->SetSpeed(MOVE_FLIGHT, 1.0f); + me->SetSpeedRate(MOVE_FLIGHT, 1.0f); me->GetMotionMaster()->MovePoint(PointData->LocId, PointData->fX, PointData->fY, PointData->fZ); } break; @@ -250,7 +250,7 @@ public: if (Creature * trigger = me->SummonCreature(NPC_TRIGGER, MiddleRoomLocation, TEMPSUMMON_CORPSE_DESPAWN)) triggerGUID = trigger->GetGUID(); me->GetMotionMaster()->MoveTakeoff(11, Phase2Floating); - me->SetSpeed(MOVE_FLIGHT, 1.0f); + me->SetSpeedRate(MOVE_FLIGHT, 1.0f); Talk(SAY_PHASE_2_TRANS); instance->SetData(DATA_ONYXIA_PHASE, Phase); events.ScheduleEvent(EVENT_WHELP_SPAWN, 5000); diff --git a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp index f1969a063d6..87edf4781d4 100644 --- a/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp +++ b/src/server/scripts/Kalimdor/kalimdor_script_loader.cpp @@ -47,6 +47,7 @@ void AddSC_boss_meathook(); void AddSC_culling_of_stratholme(); void AddSC_instance_culling_of_stratholme(); void AddSC_instance_dire_maul(); //Dire Maul +void AddSC_instance_ragefire_chasm(); //Ragefire Chasm void AddSC_boss_celebras_the_cursed(); //Maraudon void AddSC_boss_landslide(); void AddSC_boss_noxxion(); @@ -143,6 +144,7 @@ void AddKalimdorScripts() AddSC_culling_of_stratholme(); AddSC_instance_culling_of_stratholme(); AddSC_instance_dire_maul(); //Dire Maul + AddSC_instance_ragefire_chasm(); //Ragefire Chasm AddSC_boss_celebras_the_cursed(); //Maraudon AddSC_boss_landslide(); AddSC_boss_noxxion(); diff --git a/src/server/scripts/Kalimdor/zone_azshara.cpp b/src/server/scripts/Kalimdor/zone_azshara.cpp index 1ed95c16a0d..4847ac46542 100644 --- a/src/server/scripts/Kalimdor/zone_azshara.cpp +++ b/src/server/scripts/Kalimdor/zone_azshara.cpp @@ -404,7 +404,7 @@ public: DoCast(me, SPELL_PERIODIC_DEPTH_CHARGE); me->SetHover(true); me->SetSwim(true); - me->SetSpeed(MOVE_RUN, 0.85f, true); + me->SetSpeedRate(MOVE_RUN, 0.85f); me->GetMotionMaster()->MovementExpired(); me->GetMotionMaster()->MovePoint(CurrWP, WPs[CurrWP]); Escape = true; diff --git a/src/server/scripts/Kalimdor/zone_desolace.cpp b/src/server/scripts/Kalimdor/zone_desolace.cpp index 6880d8ee38f..3f4a905147f 100644 --- a/src/server/scripts/Kalimdor/zone_desolace.cpp +++ b/src/server/scripts/Kalimdor/zone_desolace.cpp @@ -89,7 +89,7 @@ public: me->UpdateEntry(NPC_TAMED_KODO); me->CombatStop(); me->DeleteThreatList(); - me->SetSpeed(MOVE_RUN, 0.6f, true); + me->SetSpeedRate(MOVE_RUN, 0.6f); me->GetMotionMaster()->MoveFollow(caster, PET_FOLLOW_DIST, me->GetFollowAngle()); me->setActive(true); } diff --git a/src/server/scripts/Kalimdor/zone_durotar.cpp b/src/server/scripts/Kalimdor/zone_durotar.cpp index 05caf9cca3a..62a041e7798 100644 --- a/src/server/scripts/Kalimdor/zone_durotar.cpp +++ b/src/server/scripts/Kalimdor/zone_durotar.cpp @@ -483,8 +483,8 @@ class spell_mount_check : public SpellScriptLoader else if (!owner->IsMounted() && target->IsMounted()) target->Dismount(); - target->SetSpeed(MOVE_RUN, owner->GetSpeedRate(MOVE_RUN)); - target->SetSpeed(MOVE_WALK, owner->GetSpeedRate(MOVE_WALK)); + target->SetSpeedRate(MOVE_RUN, owner->GetSpeedRate(MOVE_RUN)); + target->SetSpeedRate(MOVE_WALK, owner->GetSpeedRate(MOVE_WALK)); } void Register() override diff --git a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_jedoga_shadowseeker.cpp b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_jedoga_shadowseeker.cpp index ab09dd45710..3c11fd5d9a6 100644 --- a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_jedoga_shadowseeker.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_jedoga_shadowseeker.cpp @@ -454,11 +454,11 @@ public: float distance = me->GetDistance(JedogaPosition[1]); if (distance < 9.0f) - me->SetSpeed(MOVE_WALK, 0.5f, true); + me->SetSpeedRate(MOVE_WALK, 0.5f); else if (distance < 15.0f) - me->SetSpeed(MOVE_WALK, 0.75f, true); + me->SetSpeedRate(MOVE_WALK, 0.75f); else if (distance < 20.0f) - me->SetSpeed(MOVE_WALK, 1.0f, true); + me->SetSpeedRate(MOVE_WALK, 1.0f); me->GetMotionMaster()->Clear(false); me->GetMotionMaster()->MovePoint(1, JedogaPosition[1]); diff --git a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp index e25f64f61aa..04b62f77e9a 100644 --- a/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/Ahnkahet/boss_prince_taldaram.cpp @@ -178,7 +178,7 @@ class boss_prince_taldaram : public CreatureScript if (Unit* embraceTarget = GetEmbraceTarget()) { me->GetMotionMaster()->Clear(); - me->SetSpeed(MOVE_WALK, 2.0f, true); + me->SetSpeedRate(MOVE_WALK, 2.0f); me->GetMotionMaster()->MoveChase(embraceTarget); } events.ScheduleEvent(EVENT_VANISHED, 1300); @@ -188,7 +188,7 @@ class boss_prince_taldaram : public CreatureScript DoCast(embraceTarget, SPELL_EMBRACE_OF_THE_VAMPYR); Talk(SAY_FEED); me->GetMotionMaster()->Clear(); - me->SetSpeed(MOVE_WALK, 1.0f, true); + me->SetSpeedRate(MOVE_WALK, 1.0f); me->GetMotionMaster()->MoveChase(me->GetVictim()); events.ScheduleEvent(EVENT_FEEDING, 20000); break; diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp index c89510211b9..7440984d7c5 100644 --- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp +++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_anubarak_trial.cpp @@ -804,7 +804,7 @@ class npc_anubarak_spike : public CreatureScript void StartChase(Unit* who) { DoCast(who, SPELL_MARK); - me->SetSpeed(MOVE_RUN, 0.5f); + me->SetSpeedRate(MOVE_RUN, 0.5f); // make sure the Spine will really follow the one he should me->getThreatManager().clearReferences(); me->SetInCombatWithZone(); diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp index 4c2b92da0ea..3ac85809fa2 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp @@ -2580,7 +2580,7 @@ class npc_quel_delar_sword : public CreatureScript void Reset() override { _events.Reset(); - me->SetSpeed(MOVE_FLIGHT, 4.5f, true); + me->SetSpeedRate(MOVE_FLIGHT, 4.5f); DoCast(SPELL_WHIRLWIND_VISUAL); if (_intro) _events.ScheduleEvent(EVENT_QUEL_DELAR_INIT, 0); diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_krickandick.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_krickandick.cpp index 03f12e46bc3..b24a36da3fb 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_krickandick.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_krickandick.cpp @@ -418,7 +418,7 @@ class boss_krick : public CreatureScript case EVENT_OUTRO_6: if (Creature* tyrannus = ObjectAccessor::GetCreature(*me, _instanceScript->GetGuidData(DATA_TYRANNUS_EVENT))) { - tyrannus->SetSpeed(MOVE_FLIGHT, 3.5f, true); + tyrannus->SetSpeedRate(MOVE_FLIGHT, 3.5f); tyrannus->GetMotionMaster()->MovePoint(1, outroPos[4]); _tyrannusGUID = tyrannus->GetGUID(); } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp index 166e9739a95..51711f9ded5 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp @@ -1069,7 +1069,7 @@ class npc_blood_queen_lana_thel : public CreatureScript if (Creature* summon = DoSummon(NPC_FLOATING_TRIGGER, triggerPos, 15000, TEMPSUMMON_TIMED_DESPAWN)) { summon->CastSpell(summon, SPELL_OOC_INVOCATION_VISUAL, true); - summon->SetSpeed(MOVE_FLIGHT, 0.15f, true); // todo: creature is swimming, check if this is blizzlike or not. + summon->SetSpeedRate(MOVE_FLIGHT, 0.15f); // todo: creature is swimming, check if this is blizzlike or not. summon->GetMotionMaster()->MovePoint(0, triggerEndPos); } } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_lord_marrowgar.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_lord_marrowgar.cpp index 056231285e2..9f20799df82 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_lord_marrowgar.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_lord_marrowgar.cpp @@ -142,7 +142,7 @@ class boss_lord_marrowgar : public CreatureScript void Reset() override { _Reset(); - me->SetSpeed(MOVE_RUN, _baseSpeed, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed); me->RemoveAurasDueToSpell(SPELL_BONE_STORM); me->RemoveAurasDueToSpell(SPELL_BERSERK); events.ScheduleEvent(EVENT_ENABLE_BONE_SLICE, 10000); @@ -224,7 +224,7 @@ class boss_lord_marrowgar : public CreatureScript case EVENT_BONE_STORM_BEGIN: if (Aura* pStorm = me->GetAura(SPELL_BONE_STORM)) pStorm->SetDuration(int32(_boneStormDuration)); - me->SetSpeed(MOVE_RUN, _baseSpeed*3.0f, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed*3.0f); Talk(SAY_BONE_STORM); events.ScheduleEvent(EVENT_BONE_STORM_END, _boneStormDuration+1); // no break here @@ -242,7 +242,7 @@ class boss_lord_marrowgar : public CreatureScript if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) me->GetMotionMaster()->MovementExpired(); me->GetMotionMaster()->MoveChase(me->GetVictim()); - me->SetSpeed(MOVE_RUN, _baseSpeed, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed); events.CancelEvent(EVENT_BONE_STORM_MOVE); events.ScheduleEvent(EVENT_ENABLE_BONE_SLICE, 10000); if (!IsHeroic()) diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp index 280a0aaa13a..fe6e4081326 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp @@ -369,21 +369,21 @@ class boss_professor_putricide : public CreatureScript { case POINT_FESTERGUT: instance->SetBossState(DATA_FESTERGUT, IN_PROGRESS); // needed here for delayed gate close - me->SetSpeed(MOVE_RUN, _baseSpeed, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed); DoAction(ACTION_FESTERGUT_GAS); if (Creature* festergut = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FESTERGUT))) festergut->CastSpell(festergut, SPELL_GASEOUS_BLIGHT_LARGE, false, NULL, NULL, festergut->GetGUID()); break; case POINT_ROTFACE: instance->SetBossState(DATA_ROTFACE, IN_PROGRESS); // needed here for delayed gate close - me->SetSpeed(MOVE_RUN, _baseSpeed, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed); DoAction(ACTION_ROTFACE_OOZE); events.ScheduleEvent(EVENT_ROTFACE_OOZE_FLOOD, 25000, 0, PHASE_ROTFACE); break; case POINT_TABLE: // stop attack me->GetMotionMaster()->MoveIdle(); - me->SetSpeed(MOVE_RUN, _baseSpeed, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed); if (GameObject* table = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_PUTRICIDE_TABLE))) me->SetFacingToObject(table); // operating on new phase already @@ -418,7 +418,7 @@ class boss_professor_putricide : public CreatureScript { case ACTION_FESTERGUT_COMBAT: SetPhase(PHASE_FESTERGUT); - me->SetSpeed(MOVE_RUN, _baseSpeed*2.0f, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed*2.0f); me->GetMotionMaster()->MovePoint(POINT_FESTERGUT, festergutWatchPos); me->SetReactState(REACT_PASSIVE); DoZoneInCombat(me); @@ -435,7 +435,7 @@ class boss_professor_putricide : public CreatureScript case ACTION_ROTFACE_COMBAT: { SetPhase(PHASE_ROTFACE); - me->SetSpeed(MOVE_RUN, _baseSpeed*2.0f, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed*2.0f); me->GetMotionMaster()->MovePoint(POINT_ROTFACE, rotfaceWatchPos); me->SetReactState(REACT_PASSIVE); _oozeFloodStage = 0; @@ -477,7 +477,7 @@ class boss_professor_putricide : public CreatureScript events.ScheduleEvent(EVENT_ROTFACE_DIES, 4500, 0, PHASE_ROTFACE); break; case ACTION_CHANGE_PHASE: - me->SetSpeed(MOVE_RUN, _baseSpeed*2.0f, true); + me->SetSpeedRate(MOVE_RUN, _baseSpeed*2.0f); events.DelayEvents(30000); me->AttackStop(); if (!IsHeroic()) diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp index 0b129f3aa89..caa37465165 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp @@ -315,7 +315,7 @@ class boss_sindragosa : public CreatureScript me->SetCanFly(true); me->SetDisableGravity(true); me->SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); - me->SetSpeed(MOVE_FLIGHT, 4.0f); + me->SetSpeedRate(MOVE_FLIGHT, 4.0f); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); float moveTime = me->GetExactDist(&SindragosaFlyPos) / (me->GetSpeed(MOVE_FLIGHT) * 0.001f); me->m_Events.AddEvent(new FrostwyrmLandEvent(*me, SindragosaLandPos), me->m_Events.CalculateTime(uint64(moveTime) + 250)); @@ -351,7 +351,7 @@ class boss_sindragosa : public CreatureScript me->RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); me->SetHomePosition(SindragosaLandPos); me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); - me->SetSpeed(MOVE_FLIGHT, 2.5f); + me->SetSpeedRate(MOVE_FLIGHT, 2.5f); // Sindragosa enters combat as soon as she lands DoZoneInCombat(); @@ -697,7 +697,7 @@ class npc_spinestalker : public CreatureScript return; me->setActive(true); - me->SetSpeed(MOVE_FLIGHT, 2.0f); + me->SetSpeedRate(MOVE_FLIGHT, 2.0f); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); float moveTime = me->GetExactDist(&SpinestalkerFlyPos) / (me->GetSpeed(MOVE_FLIGHT) * 0.001f); me->m_Events.AddEvent(new FrostwyrmLandEvent(*me, SpinestalkerLandPos), me->m_Events.CalculateTime(uint64(moveTime) + 250)); @@ -834,7 +834,7 @@ class npc_rimefang : public CreatureScript return; me->setActive(true); - me->SetSpeed(MOVE_FLIGHT, 2.0f); + me->SetSpeedRate(MOVE_FLIGHT, 2.0f); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); float moveTime = me->GetExactDist(&RimefangFlyPos) / (me->GetSpeed(MOVE_FLIGHT) * 0.001f); me->m_Events.AddEvent(new FrostwyrmLandEvent(*me, RimefangLandPos), me->m_Events.CalculateTime(uint64(moveTime) + 250)); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp index 4675989228a..4a0a8217af8 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp @@ -763,7 +763,7 @@ class boss_the_lich_king : public CreatureScript { summons.Summon(summon); summon->SetReactState(REACT_PASSIVE); - summon->SetSpeed(MOVE_FLIGHT, 0.5f); + summon->SetSpeedRate(MOVE_FLIGHT, 0.5f); summon->GetMotionMaster()->MoveRandom(10.0f); if (!events.IsInPhase(PHASE_FROSTMOURNE)) summon->m_Events.AddEvent(new VileSpiritActivateEvent(summon), summon->m_Events.CalculateTime(15000)); @@ -1448,7 +1448,7 @@ class npc_valkyr_shadowguard : public CreatureScript _events.Reset(); me->SetReactState(REACT_PASSIVE); DoCast(me, SPELL_WINGS_OF_THE_DAMNED, false); - me->SetSpeed(MOVE_FLIGHT, 0.642857f, true); + me->SetSpeedRate(MOVE_FLIGHT, 0.642857f); } void IsSummonedBy(Unit* /*summoner*/) override diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp index 412615afad4..1e2eb06a2d5 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_gluth.cpp @@ -74,9 +74,14 @@ enum States STATE_ZOMBIE_TOBE_EATEN = 3 }; +enum SummonGroups +{ + SUMMON_GROUP_CHOW_10MAN = 1, + SUMMON_GROUP_CHOW_25MAN = 2 +}; + enum Misc { - NPC_ZOMBIE_CHOW = 16360, EVENT_GLUTH_ZOMBIE_BEHAVIOR = 10495, // unused. event handled by spell_gluth_decimate_SpellScript::HandleEvent DATA_ZOMBIE_STATE = 1, ACTION_DECIMATE_EVENT = 2, @@ -103,7 +108,7 @@ public: zombieToBeEatenGUID.Clear(); state = STATE_GLUTH_NORMAL; me->SetReactState(REACT_AGGRESSIVE); - me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f / baseMoveSpeed[MOVE_RUN]); + me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f); } void EnterCombat(Unit* /*who*/) override @@ -177,12 +182,9 @@ public: break; case EVENT_SUMMON: if (Is25ManRaid()) // one wave each 10s. one wave=1 zombie in 10man and 2 zombies in 25man. - { - DoSummon(NPC_ZOMBIE_CHOW, PosSummon[1]); - DoSummon(NPC_ZOMBIE_CHOW, PosSummon[2]); - } + me->SummonCreatureGroup(SUMMON_GROUP_CHOW_25MAN); else - DoSummon(NPC_ZOMBIE_CHOW, PosSummon[0]); + me->SummonCreatureGroup(SUMMON_GROUP_CHOW_10MAN); events.Repeat(Seconds(10)); break; case EVENT_SEARCH_ZOMBIE_SINGLE: @@ -191,19 +193,19 @@ public: for (SummonList::const_iterator itr = summons.begin(); !zombie && itr != summons.end(); ++itr) { zombie=ObjectAccessor::GetCreature(*me, *itr); - if (zombie == nullptr || !zombie->IsAlive() || !zombie->IsWithinDistInMap(me, 10.0)) + if (!zombie || !zombie->IsAlive() || !zombie->IsWithinDistInMap(me, 10.0)) zombie = nullptr; } if (zombie) { - zombieToBeEatenGUID = zombie->GetGUID(); // save for later use// the soon-to-be-eaten zombie should stop moving and stop attacking + zombieToBeEatenGUID = zombie->GetGUID(); // save for later use // the soon-to-be-eaten zombie should stop moving and stop attacking zombie->AI()->SetData(DATA_ZOMBIE_STATE, STATE_ZOMBIE_TOBE_EATEN); // gluth should stop AAs on his primary target and turn toward the zombie (2 yards away). He then pauses for a few seconds. - me->SetSpeed(MOVE_RUN, 36.0f / baseMoveSpeed[MOVE_RUN]); + me->SetSpeed(MOVE_RUN, 36.0f); me->SetReactState(ReactStates::REACT_PASSIVE); me->AttackStop(); @@ -228,7 +230,7 @@ public: zombieToBeEatenGUID = ObjectGuid::Empty; state = STATE_GLUTH_NORMAL; - me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f / baseMoveSpeed[MOVE_RUN]); + me->SetSpeed(UnitMoveType::MOVE_RUN, 12.0f); // and then return on primary target me->SetReactState(REACT_AGGRESSIVE); @@ -321,10 +323,7 @@ public: bool Validate(SpellInfo const* /*spellInfo*/) override { - if (sSpellMgr->GetSpellInfo(SPELL_DECIMATE_DMG)) - return true; - else - return false; + return (sSpellMgr->GetSpellInfo(SPELL_DECIMATE_DMG) != nullptr); } void Register() override @@ -411,7 +410,6 @@ public: if (gluth && timer>1600 && me->GetExactDist2d(gluth) > 10.0 && me->CanFreeMove()) // it takes about 1600 ms for the animation to cycle. This way, the animation looks relatively smooth. { me->GetMotionMaster()->MovePoint(0, gluth->GetPosition()); // isn't dynamic. So, to take into account Gluth's movement, it must be called periodicly. - //me->GetMotionMaster()->MoveFollow(gluth, 0, 0); // the zombies will stand still too far away from Gluth at the end of their movement. Also doesnt seem to work with SetWalk(true) timer = 0; } @@ -435,7 +433,6 @@ public: // at this point, the zombie should be non attacking and non moving. me->SetWalk(true); // it doesnt seem to work with MoveFollow() (but it does work with MovePoint()). - //me->SetSpeed(UnitMoveType::MOVE_RUN, baseMoveSpeed[UnitMoveType::MOVE_WALK] / baseMoveSpeed[UnitMoveType::MOVE_RUN]); // Hack to stay in run mode but with walk speed in case using MoveFollow() timer = 1000; } diff --git a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp index 277ca793a8f..f000d7a3ef7 100644 --- a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp +++ b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp @@ -377,7 +377,7 @@ public: me->SetDisableGravity(true); me->SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); // TO DO: find what in core is making boss slower than in retail (when correct speed data) or find missing movement flag update or forced spline change - me->SetSpeed(MOVE_FLIGHT, _flySpeed * 0.25f); + me->SetSpeedRate(MOVE_FLIGHT, _flySpeed * 0.25f); if (_despawned) DoAction(ACTION_HANDLE_RESPAWN); @@ -603,7 +603,7 @@ public: me->SetRespawnDelay(respawnDelay); // Set speed to normal value - me->SetSpeed(MOVE_FLIGHT, _flySpeed); + me->SetSpeedRate(MOVE_FLIGHT, _flySpeed); me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); me->RemoveAllAuras(); @@ -1165,7 +1165,7 @@ public: _instance = creature->GetInstanceScript(); me->SetReactState(REACT_PASSIVE); // TO DO: These were a bit faster than what they should be. Not sure what is the reason. - me->SetSpeed(MOVE_FLIGHT, 1.25f); + me->SetSpeedRate(MOVE_FLIGHT, 1.25f); } void Initialize() @@ -1274,7 +1274,7 @@ public: me->SetReactState(REACT_PASSIVE); // TO DO: Something is wrong with calculations for flying creatures that are on WP/Cyclic path. // They should get the same difference as to when at ground from run creature switch to walk. - me->SetSpeed(MOVE_FLIGHT, 0.45f); + me->SetSpeedRate(MOVE_FLIGHT, 0.45f); } void Reset() override @@ -1566,7 +1566,7 @@ public: { me->DespawnOrUnsummon(2050); me->SetOrientation(2.5f); - me->SetSpeed(MOVE_FLIGHT, 1.0f, true); + me->SetSpeedRate(MOVE_FLIGHT, 1.0f); Position pos = me->GetPosition(); pos.m_positionX += 10.0f; pos.m_positionY += 10.0f; diff --git a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp index e1a4a6ed7b8..3d9ea97b136 100644 --- a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp +++ b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp @@ -375,7 +375,7 @@ class npc_ruby_emerald_amber_drake : public CreatureScript { me->DespawnOrUnsummon(2050); me->SetOrientation(2.5f); - me->SetSpeed(MOVE_FLIGHT, 1.0f, true); + me->SetSpeedRate(MOVE_FLIGHT, 1.0f); Talk(SAY_DRAKES_TAKEOFF); Position pos = me->GetPosition(); Position offset = { 10.0f, 10.0f, 12.0f, 0.0f }; diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_ionar.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_ionar.cpp index a98da2c6e3d..8f7687d0fca 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_ionar.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_ionar.cpp @@ -174,7 +174,7 @@ public: { if (pSpark->IsAlive()) { - pSpark->SetSpeed(MOVE_RUN, 2.0f); + pSpark->SetSpeedRate(MOVE_RUN, 2.0f); pSpark->GetMotionMaster()->Clear(); pSpark->GetMotionMaster()->MovePoint(DATA_POINT_CALLBACK, pos); } @@ -355,7 +355,7 @@ public: { Position pos = ionar->GetPosition(); - me->SetSpeed(MOVE_RUN, 2.0f); + me->SetSpeedRate(MOVE_RUN, 2.0f); me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MovePoint(DATA_POINT_CALLBACK, pos); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp index 0714f2426bc..8aa443cba3f 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp @@ -703,7 +703,7 @@ class boss_leviathan_mk_ii : public CreatureScript if (Unit* turret = me->GetVehicleKit()->GetPassenger(3)) turret->KillSelf(); - me->SetSpeed(MOVE_RUN, 1.5f, true); + me->SetSpeedRate(MOVE_RUN, 1.5f); me->GetMotionMaster()->MovePoint(WP_MKII_P1_IDLE, VehicleRelocation[WP_MKII_P1_IDLE]); } else if (events.IsInPhase(PHASE_VOL7RON)) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp index f5337b2dca5..363f5907048 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp @@ -366,7 +366,7 @@ class boss_razorscale : public CreatureScript _EnterCombat(); if (Creature* controller = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_RAZORSCALE_CONTROL))) controller->AI()->DoAction(ACTION_HARPOON_BUILD); - me->SetSpeed(MOVE_FLIGHT, 3.0f, true); + me->SetSpeedRate(MOVE_FLIGHT, 3.0f); me->SetReactState(REACT_PASSIVE); phase = PHASE_GROUND; events.SetPhase(PHASE_GROUND); @@ -550,7 +550,7 @@ class boss_razorscale : public CreatureScript me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED | UNIT_FLAG_PACIFIED); me->SetReactState(REACT_AGGRESSIVE); me->RemoveAurasDueToSpell(SPELL_HARPOON_TRIGGER); - me->SetSpeed(MOVE_FLIGHT, 1.0f, true); + me->SetSpeedRate(MOVE_FLIGHT, 1.0f); PermaGround = true; DoCastAOE(SPELL_FLAMEBREATH); events.ScheduleEvent(EVENT_FLAME, 15000, 0, PHASE_PERMAGROUND); @@ -677,7 +677,7 @@ class npc_expedition_commander : public CreatureScript if (Creature* summonedEngineer = me->SummonCreature(NPC_ENGINEER, PosEngSpawn, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 3000)) { summonedEngineer->SetWalk(false); - summonedEngineer->SetSpeed(MOVE_RUN, 0.5f); + summonedEngineer->SetSpeedRate(MOVE_RUN, 0.5f); summonedEngineer->SetHomePosition(PosEngRepair[n]); summonedEngineer->GetMotionMaster()->MoveTargetedHome(); Engineer[n] = summonedEngineer->GetGUID(); diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp index 1af45f3031a..ee71c696d7c 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_palehoof.cpp @@ -746,7 +746,7 @@ public: //! HACK: Creature's can't have MOVEMENTFLAG_FLYING me->AddUnitMovementFlag(MOVEMENTFLAG_FLYING); me->RemoveAurasDueToSpell(SPELL_ORB_VISUAL); - me->SetSpeed(MOVE_FLIGHT, 0.5f); + me->SetSpeedRate(MOVE_FLIGHT, 0.5f); } void UpdateAI(uint32 diff) override diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp index d7b65093898..7615217a794 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp @@ -209,7 +209,7 @@ public: Initialize(); Summons.DespawnAll(); - me->SetSpeed(MOVE_FLIGHT, 3.0f); + me->SetSpeedRate(MOVE_FLIGHT, 3.0f); if ((ObjectAccessor::GetCreature(*me, m_uiGraufGUID) == NULL) && !me->IsMounted()) me->SummonCreature(NPC_GRAUF, Location[0].GetPositionX(), Location[0].GetPositionY(), Location[0].GetPositionZ(), 3.0f); instance->SetBossState(DATA_SKADI_THE_RUTHLESS, NOT_STARTED); diff --git a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp index 7902c585509..3c651e10a1e 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp @@ -745,7 +745,7 @@ public: if (!Trigger) return; - Trigger->SetSpeed(MOVE_WALK, 3); + Trigger->SetSpeedRate(MOVE_WALK, 3); Trigger->SetWalk(true); Trigger->GetMotionMaster()->MovePoint(0, final.x, final.y, final.z); @@ -1524,7 +1524,7 @@ public: void BeginWalk() { me->SetWalk(false); - me->SetSpeed(MOVE_RUN, 1.0f); + me->SetSpeedRate(MOVE_RUN, 1.0f); me->GetMotionMaster()->MovePoint(0, AkamaWP[WalkCount].x, AkamaWP[WalkCount].y, AkamaWP[WalkCount].z); } diff --git a/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp b/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp index 7e1215488e1..1a9074c2464 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_shade_of_akama.cpp @@ -238,8 +238,8 @@ public: if (me->GetAuraCount(SPELL_SHADE_SOUL_CHANNEL_2) <= 3) { moveSpeed = (2.0f - (0.6f * me->GetAuraCount(SPELL_SHADE_SOUL_CHANNEL_2))); - me->SetSpeed(MOVE_WALK, moveSpeed / 2.5f); - me->SetSpeed(MOVE_RUN, (moveSpeed * 2) / 7); + me->SetSpeedRate(MOVE_WALK, moveSpeed / 2.5f); + me->SetSpeedRate(MOVE_RUN, (moveSpeed * 2) / 7); me->ClearUnitState(UNIT_STATE_ROOT); } else diff --git a/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp b/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp index ae4d17bdad4..0999f1fcd6b 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_supremus.cpp @@ -117,7 +117,7 @@ public: DummyEntryCheckPredicate pred; summons.DoAction(EVENT_VOLCANO, pred); events.ScheduleEvent(EVENT_HATEFUL_STRIKE, 5000, GCD_CAST, PHASE_STRIKE); - me->SetSpeed(MOVE_RUN, 1.2f); + me->SetSpeedRate(MOVE_RUN, 1.2f); me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, false); me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, false); } @@ -126,7 +126,7 @@ public: phase = PHASE_CHASE; events.ScheduleEvent(EVENT_VOLCANO, 5000, GCD_CAST, PHASE_CHASE); events.ScheduleEvent(EVENT_SWITCH_TARGET, 10000, 0, PHASE_CHASE); - me->SetSpeed(MOVE_RUN, 0.9f); + me->SetSpeedRate(MOVE_RUN, 0.9f); me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true); me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true); } diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp index e24499c3aee..3ddf0fec416 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp @@ -585,8 +585,8 @@ public: void Reset() override { - me->SetSpeed(MOVE_WALK, 0.6f); // walk - me->SetSpeed(MOVE_RUN, 0.6f); // run + me->SetSpeedRate(MOVE_WALK, 0.6f); // walk + me->SetSpeedRate(MOVE_RUN, 0.6f); // run Initialize(); //search for nearest waypoint (up on stairs) diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp index 8d117f7c3ef..f72b9a8d2ca 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp @@ -244,7 +244,7 @@ public: CheckChannelers(); Initialize(); me->SetCanDualWield(true); - me->SetSpeed(MOVE_RUN, 2.0f, true); + me->SetSpeedRate(MOVE_RUN, 2.0f); me->SetDisplayId(MODEL_NIGHTELF); me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID , 0); me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID+1, 0); diff --git a/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/boss_ahune.cpp b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/boss_ahune.cpp new file mode 100644 index 00000000000..9b53b62cdee --- /dev/null +++ b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/boss_ahune.cpp @@ -0,0 +1,1051 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "CreatureTextMgr.h" +#include "LFGMgr.h" +#include "ScriptedGossip.h" +#include "ScriptedCreature.h" +#include "ScriptMgr.h" +#include "SpellAuraEffects.h" +#include "SpellScript.h" +#include "the_slave_pens.h" + +enum Spells +{ + // Ahune + SPELL_SYNCH_HEALTH = 46430, + SPELL_AHUNES_SHIELD = 45954, + SPELL_STAY_SUBMERGED = 46981, + SPELL_AHUNE_SELF_STUN = 46416, + SPELL_AHUNE_ACHIEVEMENT = 62043, + SPELL_AHUNE_SPANKY_HANDS = 46146, + SPELL_COLD_SLAP = 46145, + SPELL_RESURFACE = 46402, + SPELL_SUBMERGED = 37751, + SPELL_STAND = 37752, + + //Earther Ring Flamecaller + SPELL_FIND_OPENING_VISUAL = 45964, + SPELL_FIND_OPENING_BEAM_END = 46333, + SPELL_FIND_OPENING_TRIGGER = 46341, + SPELL_FIND_OPENING_CHANNEL = 46345, + SPELL_BONFIRE_VISUAL = 46339, + SPELL_FOUND_OPENING = 46421, + + //Ahune Bunny + SPELL_SUMMON_COLDWEAVE = 46143, + SPELL_SUMMON_FROSTWIND = 46382, + SPELL_SUMMON_HAILSTONE = 46176, + SPELL_SUMMONING_VISUAL_1 = 45937, + SPELL_SUMMONING_RHYME_AURA = 45926, + SPELL_SUMMONING_RHYME_BONFIRE = 45930, + SPELL_FORCE_WHISP_FLIGHT = 46603, + SPELL_SHAMANS_LOOK_FOR_OPENING = 46422, + SPELL_CLOSE_OPENING_VISUAL = 46236, + SPELL_ICE_BOMBARD = 46397, + SPELL_ICE_BOMBARDMENT_DEST_PICKER = 46398, + SPELL_ICE_BOMBARDMENT = 46396, + + // Ice Spear + SPELL_SUMMON_ICE_SPEAR_BUNNY = 46359, + SPELL_ICE_SPEAR_KNOCKBACK = 46360, + SPELL_SUMMON_ICE_SPEAR_GO = 46369, + SPELL_ICE_SPEAR_AURA = 46371, + SPELL_ICE_SPEAR_TARGET_PICKER = 46372, + SPELL_ICE_SPEAR_DELAY = 46878, + SPELL_ICE_SPEAR_VISUAL = 75498, + + // Slippery Floor + SPELL_SLIPPERY_FLOOR_AMBIENT = 46314, + SPELL_SLIPPERY_FLOOR_PERIODIC = 46320, + SPELL_SLIPPERY_FLOOR_SLIP = 45947, + + // Frozen Core + SPELL_SUICIDE = 45254, + SPELL_SUMMON_LOOT_MISSILE = 45941, + SPELL_FROZEN_CORE_GETS_HIT = 46810, + SPELL_MINION_DESPAWNER = 46843, + SPELL_GHOST_DISGUISE = 46786 +}; + +enum Emotes +{ + EMOTE_EARTHEN_ASSAULT = 0, + EMOTE_RETREAT = 0, + EMOTE_RESURFACE = 1 +}; + +enum Says +{ + SAY_PLAYER_TEXT_1 = 0, + SAY_PLAYER_TEXT_2 = 1, + SAY_PLAYER_TEXT_3 = 2 +}; + +enum Events +{ + EVENT_EMERGE = 1, + EVENT_INITIAL_EMERGE = 2, + EVENT_SYNCH_HEALTH = 3, + EVENT_FOUND_OPENING = 4, + EVENT_LOOKFOROPENING_0 = 5, + EVENT_LOOKFOROPENING_1 = 6, + EVENT_LOOKFOROPENING_2 = 7, + EVENT_SUMMON_HAILSTONE = 8, + EVENT_SUMMON_COLDWEAVE = 9, + EVENT_SUMMON_FROSTWIND = 10, + EVENT_SUMMON_AHUNE = 11, + EVENT_CLOSE_OPENING = 12, + EVENT_AHUNE_PHASE_ONE = 13, + EVENT_AHUNE_PHASE_TWO = 14, + EVENT_START_LOOKING_FOR_OPENING = 15, + EVENT_STOP_LOOKING_FOR_OPENING = 16 +}; + +enum Actions +{ + ACTION_START_EVENT = -2574500, + ACTION_AHUNE_RETREAT = -2586500, + ACTION_AHUNE_RESURFACE = -2586501, + ACTION_EMOTE_RESURFACE = -2575400 +}; + +enum Phases +{ + PHASE_ONE = 0, + PHASE_TWO = 1 +}; + +enum Points +{ + POINT_FLAMECALLER_000, + POINT_FLAMECALLER_001, + POINT_FLAMECALLER_002 +}; + +enum Misc +{ + MAX_FLAMECALLERS = 3 +}; + +Position const SummonPositions[] = +{ + { -99.1021f, -233.7526f, -1.22307f, 1.588250f }, // Ahune + { -98.0151f, -230.4555f, -1.21089f, 1.797689f }, // Frozen Core + { -143.172f, -147.6801f, -3.16113f, 4.852015f }, // Bonfire Bunny 000 + { -134.304f, -145.7803f, -1.70332f, 4.677482f }, // Bonfire Bunny 001 + { -125.036f, -144.2065f, -1.91660f, 4.991642f }, // Bonfire Bunny 002 + { -69.8121f, -162.4954f, -2.30451f, 1.710423f }, // Wisp Source Bunny + { -98.1029f, -230.7864f, -10.8085f, 1.448623f } // Wisp Dest Bunny +}; + +Position const FlameCallerSpots[] = +{ + { -145.2233f, -137.5543f, -1.59056f, 5.427049f }, + { -137.4383f, -136.4050f, -1.72384f, 5.336747f }, + { -129.0413f, -132.1494f, -2.09285f, 5.460842f } +}; + +class boss_ahune : public CreatureScript +{ +public: + boss_ahune() : CreatureScript("boss_ahune") { } + + struct boss_ahuneAI : public BossAI + { + boss_ahuneAI(Creature* creature) : BossAI(creature, DATA_AHUNE) + { + Initialize(); + } + + void Initialize() + { + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE); + } + + void EnterCombat(Unit* /*who*/) override + { + _EnterCombat(); + events.ScheduleEvent(EVENT_INITIAL_EMERGE, 4); + events.ScheduleEvent(EVENT_SYNCH_HEALTH, 3000); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (Creature* ahuneBunny = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_AHUNE_BUNNY))) + ahuneBunny->AI()->EnterEvadeMode(); + summons.DespawnAll(); + me->DespawnOrUnsummon(); + } + + void JustDied(Unit* /*killer*/) override + { + _JustDied(); + instance->DoCastSpellOnPlayers(SPELL_AHUNE_ACHIEVEMENT); + + if (Creature* ahuneBunny = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_AHUNE_BUNNY))) + me->Kill(ahuneBunny); + if (Creature* frozenCore = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FROZEN_CORE))) + me->Kill(frozenCore); + + Map::PlayerList const& players = me->GetMap()->GetPlayers(); + if (!players.isEmpty()) + { + if (Group* group = players.begin()->GetSource()->GetGroup()) + if (group->isLFGGroup()) + sLFGMgr->FinishDungeon(group->GetGUID(), 286); + } + } + + void JustSummoned(Creature* summon) override + { + BossAI::JustSummoned(summon); + } + + void DoAction(int32 action) override + { + if (action == ACTION_AHUNE_RETREAT) + { + Submerge(); + events.ScheduleEvent(EVENT_EMERGE, 35000); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + + while (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_INITIAL_EMERGE: + DoCast(me, SPELL_STAND); + DoCast(me, SPELL_AHUNE_SPANKY_HANDS); + DoCast(me, SPELL_AHUNES_SHIELD); + break; + case EVENT_EMERGE: + Emerge(); + break; + case EVENT_SYNCH_HEALTH: + if (Creature* frozenCore = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FROZEN_CORE))) + DoCast(frozenCore, SPELL_SYNCH_HEALTH); + else + DoCast(me, SPELL_SUICIDE); + events.ScheduleEvent(EVENT_SYNCH_HEALTH, 3000); + break; + default: + break; + } + } + DoMeleeAttackIfReady(); + } + + void Emerge() + { + if (Creature* frozenCore = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FROZEN_CORE))) + frozenCore->AI()->DoAction(ACTION_AHUNE_RESURFACE); + + DoCast(me, SPELL_AHUNES_SHIELD); + me->RemoveAurasDueToSpell(SPELL_AHUNE_SELF_STUN); + me->RemoveAurasDueToSpell(SPELL_STAY_SUBMERGED); + DoCast(me, SPELL_STAND); + DoCast(me, SPELL_RESURFACE, true); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + events.ScheduleEvent(EVENT_SYNCH_HEALTH, 3000); + } + + void Submerge() + { + if (Creature* frozenCore = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FROZEN_CORE))) + frozenCore->AI()->DoAction(ACTION_AHUNE_RETREAT); + me->RemoveAurasDueToSpell(SPELL_AHUNES_SHIELD); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + DoCast(me, SPELL_SUBMERGED, true); + DoCast(me, SPELL_AHUNE_SELF_STUN, true); + DoCast(me, SPELL_STAY_SUBMERGED, true); + me->HandleEmoteCommand(EMOTE_ONESHOT_SUBMERGE); + events.Reset(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<boss_ahuneAI>(creature); + } +}; + +class npc_frozen_core : public CreatureScript +{ +public: + npc_frozen_core() : CreatureScript("npc_frozen_core") { } + + struct npc_frozen_coreAI : public ScriptedAI + { + npc_frozen_coreAI(Creature* creature) : ScriptedAI(creature) + { + _instance = me->GetInstanceScript(); + Initialize(); + } + + void Initialize() + { + me->SetReactState(REACT_PASSIVE); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_DISABLE_MOVE); + DoCast(me, SPELL_FROZEN_CORE_GETS_HIT); + DoCast(me, SPELL_ICE_SPEAR_AURA); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + DoCast(SPELL_MINION_DESPAWNER); + if (Creature* ahune = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_AHUNE))) + ahune->AI()->EnterEvadeMode(); + } + + void JustDied(Unit* /*killer*/) override + { + if (Creature* ahune = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_AHUNE))) + me->Kill(ahune); + + DoCast(SPELL_SUMMON_LOOT_MISSILE); + DoCast(SPELL_MINION_DESPAWNER); + } + + void DoAction(int32 action) override + { + if (action == ACTION_AHUNE_RETREAT) + { + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC); + me->RemoveAurasDueToSpell(SPELL_ICE_SPEAR_AURA); + _events.ScheduleEvent(EVENT_SYNCH_HEALTH, 3000, 0, PHASE_TWO); + } + else if (action == ACTION_AHUNE_RESURFACE) + { + _events.Reset(); + DoCast(me, SPELL_ICE_SPEAR_AURA); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_DISABLE_MOVE); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_SYNCH_HEALTH: + if (Creature* ahune = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_AHUNE))) + DoCast(ahune, SPELL_SYNCH_HEALTH); + else + DoCast(me, SPELL_SUICIDE); + _events.ScheduleEvent(EVENT_SYNCH_HEALTH, 3000); + break; + default: + break; + } + } + } + + private: + InstanceScript* _instance; + EventMap _events; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_frozen_coreAI>(creature); + } +}; + +class npc_ahune_bunny : public CreatureScript +{ +public: + npc_ahune_bunny() : CreatureScript("npc_ahune_bunny") { } + + struct npc_ahune_bunnyAI : public ScriptedAI + { + npc_ahune_bunnyAI(Creature* creature) : ScriptedAI(creature), _summons(me) + { + _instance = me->GetInstanceScript(); + _submerged = false; + } + + void JustSummoned(Creature* summon) override + { + if (summon->GetEntry() == NPC_AHUNE) + return; + + summon->SetInCombatWithZone(); + _summons.Summon(summon); + } + + void JustDied(Unit* /*killer*/) override + { + _summons.DespawnAll(); + ResetFlameCallers(); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + _EnterEvadeMode(); + _summons.DespawnAll(); + ResetFlameCallers(); + + me->SummonGameObject(GO_ICE_STONE, -69.90455f, -162.2449f, -2.366563f, 2.426008f, 0.0f, 0.0f, 0.9366722f, 0.3502074f, 0); + } + + void DoAction(int32 action) override + { + if (action == ACTION_START_EVENT) + { + DoCast(me, SPELL_SUMMONING_VISUAL_1); + me->SummonCreature(NPC_WHISP_SOURCE_BUNNY, SummonPositions[5], TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(NPC_WHISP_DEST_BUNNY, SummonPositions[6], TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(NPC_SHAMAN_BONFIRE_BUNNY_000, SummonPositions[2], TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(NPC_SHAMAN_BONFIRE_BUNNY_001, SummonPositions[3], TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(NPC_SHAMAN_BONFIRE_BUNNY_002, SummonPositions[4], TEMPSUMMON_MANUAL_DESPAWN); + + for (uint8 counter = 0; counter < MAX_FLAMECALLERS; ++counter) + if (Creature* flameCaller = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FLAMECALLER_000 + counter))) + flameCaller->GetMotionMaster()->MovePoint(counter, FlameCallerSpots[counter].GetPosition()); + + _submerged = false; + _events.Reset(); + _events.SetPhase(PHASE_ONE); + _events.ScheduleEvent(EVENT_SUMMON_AHUNE, 10000); + _events.ScheduleEvent(EVENT_START_LOOKING_FOR_OPENING, 14000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_SUMMON_COLDWEAVE, 22000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_SUMMON_HAILSTONE, 14000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_AHUNE_PHASE_TWO, 108000, 0, PHASE_ONE); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_START_LOOKING_FOR_OPENING: + Talk(EMOTE_EARTHEN_ASSAULT); + for (uint8 counter = 0; counter < MAX_FLAMECALLERS; ++counter) + if (Creature* flamecaller = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FLAMECALLER_000 + counter))) + DoCast(flamecaller, SPELL_SHAMANS_LOOK_FOR_OPENING, true); + break; + case EVENT_SUMMON_HAILSTONE: + DoCast(SPELL_SUMMON_HAILSTONE); + break; + case EVENT_SUMMON_COLDWEAVE: + DoCast(SPELL_SUMMON_COLDWEAVE); + DoCast(SPELL_SUMMON_COLDWEAVE); + _events.ScheduleEvent(EVENT_SUMMON_COLDWEAVE, 8000, 0, PHASE_ONE); + if (_submerged) + _events.ScheduleEvent(EVENT_SUMMON_FROSTWIND, 4000, 0, PHASE_ONE); + break; + case EVENT_SUMMON_FROSTWIND: + DoCast(SPELL_SUMMON_FROSTWIND); + break; + case EVENT_SUMMON_AHUNE: + if (TempSummon* ahune = me->SummonCreature(NPC_AHUNE, SummonPositions[0], TEMPSUMMON_DEAD_DESPAWN)) + { + ahune->SummonCreature(NPC_FROZEN_CORE, SummonPositions[1], TEMPSUMMON_CORPSE_DESPAWN); + ahune->SetInCombatWithZone(); + DoCast(ahune, SPELL_RESURFACE); + } + break; + case EVENT_CLOSE_OPENING: + if (Creature* flamecaller = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FLAMECALLER_000))) + flamecaller->AI()->DoAction(ACTION_EMOTE_RESURFACE); + DoCast(SPELL_CLOSE_OPENING_VISUAL); + DoCast(me, SPELL_ICE_BOMBARD); + break; + case EVENT_AHUNE_PHASE_TWO: + if (Creature* flamecaller = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FLAMECALLER_000))) + DoCast(flamecaller, SPELL_FOUND_OPENING); + if (Creature* ahune = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_AHUNE))) + ahune->AI()->DoAction(ACTION_AHUNE_RETREAT); + _events.Reset(); + _events.SetPhase(PHASE_TWO); + _events.ScheduleEvent(EVENT_CLOSE_OPENING, 25000, 0, PHASE_TWO); + _events.ScheduleEvent(EVENT_AHUNE_PHASE_ONE, 35000, 0, PHASE_TWO); + break; + case EVENT_AHUNE_PHASE_ONE: + _submerged = true; + _events.Reset(); + _events.SetPhase(PHASE_ONE); + _events.ScheduleEvent(EVENT_SUMMON_COLDWEAVE, 8000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_SUMMON_HAILSTONE, 5000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_START_LOOKING_FOR_OPENING, 5000, 0, PHASE_ONE); + _events.ScheduleEvent(EVENT_AHUNE_PHASE_TWO, 100000, 0, PHASE_ONE); + break; + default: + break; + } + } + } + + void ResetFlameCallers() + { + for (uint8 counter = 0; counter < MAX_FLAMECALLERS; ++counter) + if (Creature* flamecaller = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_FLAMECALLER_000 + counter))) + flamecaller->AI()->EnterEvadeMode(); + } + + private: + InstanceScript* _instance; + EventMap _events; + SummonList _summons; + bool _submerged; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_ahune_bunnyAI>(creature); + } +}; + +class npc_earthen_ring_flamecaller : public CreatureScript +{ +public: + npc_earthen_ring_flamecaller() : CreatureScript("npc_earthen_ring_flamecaller") { } + + struct npc_earthen_ring_flamecallerAI : public ScriptedAI + { + npc_earthen_ring_flamecallerAI(Creature* creature) : ScriptedAI(creature) + { + _instance = me->GetInstanceScript(); + } + + void Reset() override + { + _events.Reset(); + } + + void MovementInform(uint32 motionType, uint32 pointId) override + { + if (motionType != POINT_MOTION_TYPE) + return; + + switch (pointId) + { + case POINT_FLAMECALLER_000: + _mySpot = POINT_FLAMECALLER_000; + me->SetOrientation(FlameCallerSpots[_mySpot].GetOrientation()); + break; + case POINT_FLAMECALLER_001: + _mySpot = POINT_FLAMECALLER_001; + me->SetOrientation(FlameCallerSpots[_mySpot].GetOrientation()); + break; + case POINT_FLAMECALLER_002: + _mySpot = POINT_FLAMECALLER_002; + me->SetOrientation(FlameCallerSpots[_mySpot].GetOrientation()); + break; + default: + break; + } + + DoCast(me, SPELL_FIND_OPENING_CHANNEL); + } + + void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override + { + switch (spellInfo->Id) + { + case SPELL_SHAMANS_LOOK_FOR_OPENING: + _events.ScheduleEvent(EVENT_LOOKFOROPENING_0, 17000); + break; + case SPELL_FOUND_OPENING: + _events.ScheduleEvent(EVENT_FOUND_OPENING, 0); + break; + default: + break; + } + } + + void DoAction(int action) override + { + if (action == ACTION_EMOTE_RESURFACE) + Talk(EMOTE_RESURFACE); + } + + void UpdateAI(uint32 diff) override + { + _events.Update(diff); + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_LOOKFOROPENING_0: + LookOpening(true, 0); + _events.ScheduleEvent(EVENT_LOOKFOROPENING_1, 26000); + break; + case EVENT_LOOKFOROPENING_1: + LookOpening(true, 1); + _events.ScheduleEvent(EVENT_LOOKFOROPENING_2, 25000); + break; + case EVENT_LOOKFOROPENING_2: + LookOpening(true, 2); + _events.ScheduleEvent(EVENT_STOP_LOOKING_FOR_OPENING, 27000); + break; + case EVENT_STOP_LOOKING_FOR_OPENING: + LookOpening(false, _mySpot); + break; + case EVENT_FOUND_OPENING: + Talk(EMOTE_RETREAT); + default: + break; + } + } + } + + void LookOpening(bool activate, uint8 spot) + { + if (_mySpot != spot) + return; + + if (Creature* bonfireBunny = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_BONFIRE_BUNNY_000 + spot))) + if (Creature* beamBunny = ObjectAccessor::GetCreature(*me, _instance->GetGuidData(DATA_BEAM_BUNNY_000 + spot))) + { + if (activate) + { + DoCast(bonfireBunny, SPELL_FIND_OPENING_TRIGGER); + bonfireBunny->CastSpell(beamBunny, SPELL_FIND_OPENING_VISUAL, true); + } + else + { + DoCast(me, SPELL_FIND_OPENING_CHANNEL); + bonfireBunny->CastStop(); + beamBunny->RemoveAurasDueToSpell(SPELL_FIND_OPENING_BEAM_END); + } + } + } + + private: + EventMap _events; + InstanceScript* _instance; + uint8 _mySpot; + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return GetInstanceAI<npc_earthen_ring_flamecallerAI>(creature); + } +}; + +class go_ahune_ice_stone : public GameObjectScript +{ +public: + go_ahune_ice_stone() : GameObjectScript("go_ahune_ice_stone") { } + + bool OnGossipSelect(Player* player, GameObject* go, uint32 /*sender*/, uint32 /*action*/) + { + InstanceScript* instance = go->GetInstanceScript(); + if (!instance) + return false; + + player->PlayerTalkClass->ClearMenus(); + + if (Creature* ahuneBunny = ObjectAccessor::GetCreature(*go, instance->GetGuidData(DATA_AHUNE_BUNNY))) + { + ahuneBunny->AI()->DoAction(ACTION_START_EVENT); + ahuneBunny->SetInCombatWithZone(); + } + if (Creature* luma = ObjectAccessor::GetCreature(*go, instance->GetGuidData(DATA_LUMA_SKYMOTHER))) + luma->CastSpell(player, SPELL_SUMMONING_RHYME_AURA, true); + player->CLOSE_GOSSIP_MENU(); + go->Delete(); + + return true; + } +}; + +// 46430 - Synch Health +class spell_ahune_synch_health : public SpellScriptLoader +{ +public: + spell_ahune_synch_health() : SpellScriptLoader("spell_ahune_synch_health") { } + + class spell_ahune_synch_health_SpellScript : public SpellScript + { + PrepareSpellScript(spell_ahune_synch_health_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_SYNCH_HEALTH)) + return false; + return true; + } + + void HandleScript(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + if (Unit* caster = GetCaster()) + target->SetHealth(caster->GetHealth()); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_ahune_synch_health_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_ahune_synch_health_SpellScript(); + } +}; + +// 45926 - Summoning Rhyme Aura +class spell_summoning_rhyme_aura : public SpellScriptLoader +{ +public: + spell_summoning_rhyme_aura() : SpellScriptLoader("spell_summoning_rhyme_aura") { } + + class spell_summoning_rhyme_aura_AuraScript : public AuraScript + { + PrepareAuraScript(spell_summoning_rhyme_aura_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_FORCE_WHISP_FLIGHT) || !sSpellMgr->GetSpellInfo(SPELL_SUMMONING_RHYME_BONFIRE)) + return false; + return true; + } + + void PeriodicTick(AuraEffect const* aurEff) + { + Creature* caster = GetCaster()->ToCreature(); + Player* player = GetTarget()->ToPlayer(); + + if (!caster || !player) + return; + + player->CastSpell(player, SPELL_FORCE_WHISP_FLIGHT); + + switch (aurEff->GetTickNumber()) + { + case 1: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_TEXT_1, NULL, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, player); + player->CastSpell(player, SPELL_SUMMONING_RHYME_BONFIRE, true); + break; + case 2: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_TEXT_2, NULL, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, player); + break; + case 3: + sCreatureTextMgr->SendChat(caster, SAY_PLAYER_TEXT_3, NULL, CHAT_MSG_SAY, LANG_UNIVERSAL, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, player); + Remove(); + break; + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_summoning_rhyme_aura_AuraScript::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_summoning_rhyme_aura_AuraScript(); + } +}; + +// 46878 - Summon Ice Spear Delayer +class spell_summon_ice_spear_delayer : public SpellScriptLoader +{ +public: + spell_summon_ice_spear_delayer() : SpellScriptLoader("spell_summon_ice_spear_delayer") { } + + class spell_summon_ice_spear_delayer_AuraScript : public AuraScript + { + PrepareAuraScript(spell_summon_ice_spear_delayer_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_SUMMON_ICE_SPEAR_GO) || !sSpellMgr->GetSpellInfo(SPELL_ICE_SPEAR_KNOCKBACK)) + return false; + return true; + } + + void PeriodicTick(AuraEffect const* aurEff) + { + if (Creature* caster = GetCaster()->ToCreature()) + switch (aurEff->GetTickNumber()) + { + case 1: + caster->CastSpell(caster, SPELL_SUMMON_ICE_SPEAR_GO); + break; + case 3: + if (GameObject* spike = caster->FindNearestGameObject(GO_ICE_SPEAR, 3.0f)) + spike->UseDoorOrButton(); + caster->AI()->DoCastAOE(SPELL_ICE_SPEAR_KNOCKBACK, true); + break; + case 5: + if (GameObject* spike = caster->FindNearestGameObject(GO_ICE_SPEAR, 3.0f)) + spike->Delete(); + caster->DespawnOrUnsummon(); + break; + default: + break; + } + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_summon_ice_spear_delayer_AuraScript::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_summon_ice_spear_delayer_AuraScript(); + } +}; + +// 46371 - Ice Spear Control Aura +class spell_ice_spear_control_aura : public SpellScriptLoader +{ +public: + spell_ice_spear_control_aura() : SpellScriptLoader("spell_ice_spear_control_aura") { } + + class spell_ice_spear_control_aura_AuraScript : public AuraScript + { + PrepareAuraScript(spell_ice_spear_control_aura_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_ICE_SPEAR_TARGET_PICKER)) + return false; + return true; + } + + void PeriodicTick(AuraEffect const* /*aurEff*/) + { + if (Unit* caster = GetCaster()) + caster->CastSpell(caster, SPELL_ICE_SPEAR_TARGET_PICKER); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_ice_spear_control_aura_AuraScript::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_ice_spear_control_aura_AuraScript(); + } +}; + +// 46372 - Ice Spear Target Picker +class spell_ice_spear_target_picker : public SpellScriptLoader +{ +public: + spell_ice_spear_target_picker() : SpellScriptLoader("spell_ice_spear_target_picker") { } + + class spell_ice_spear_target_picker_SpellScript : public SpellScript + { + PrepareSpellScript(spell_ice_spear_target_picker_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_SUMMON_ICE_SPEAR_BUNNY)) + return false; + return true; + } + + void HandleDummy(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitUnit(), SPELL_SUMMON_ICE_SPEAR_BUNNY, true); + } + + void Register() + { + OnEffectHitTarget += SpellEffectFn(spell_ice_spear_target_picker_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_ice_spear_target_picker_SpellScript(); + } +}; + +// 46320 - Spell Slippery Floor Periodic +class spell_slippery_floor_periodic : public SpellScriptLoader +{ +public: + spell_slippery_floor_periodic() : SpellScriptLoader("spell_slippery_floor_periodic") { } + + class spell_slippery_floor_periodic_SpellScript : public SpellScript + { + PrepareSpellScript(spell_slippery_floor_periodic_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_SLIPPERY_FLOOR_SLIP)) + return false; + return true; + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + if (Unit* target = GetHitUnit()) + if (target->isMoving()) + target->CastSpell(target, SPELL_SLIPPERY_FLOOR_SLIP, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_slippery_floor_periodic_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_slippery_floor_periodic_SpellScript(); + } +}; + +// 46146 - Ahune Spanky Hands +class spell_ahune_spanky_hands : public SpellScriptLoader +{ +public: + spell_ahune_spanky_hands() : SpellScriptLoader("spell_ahune_spanky_hands") { } + + class spell_ahune_spanky_hands_AuraScript : public AuraScript + { + PrepareAuraScript(spell_ahune_spanky_hands_AuraScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_COLD_SLAP)) + return false; + return true; + } + + void HandleProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + PreventDefaultAction(); + GetTarget()->CastSpell(eventInfo.GetProcTarget(), SPELL_COLD_SLAP, true); + } + + void Register() override + { + OnEffectProc += AuraEffectProcFn(spell_ahune_spanky_hands_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_ahune_spanky_hands_AuraScript(); + } +}; + +class spell_ahune_minion_despawner : public SpellScriptLoader +{ +public: + spell_ahune_minion_despawner() : SpellScriptLoader("spell_ahune_minion_despawner") { } + + class spell_ahune_minion_despawner_SpellScript : public SpellScript + { + PrepareSpellScript(spell_ahune_minion_despawner_SpellScript); + + void HandleScript(SpellEffIndex /*effIndex*/) + { + if (GetHitCreature()) + GetHitCreature()->DespawnOrUnsummon(); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_ahune_minion_despawner_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_ahune_minion_despawner_SpellScript(); + } +}; + +// 46398 - Spell Ice Bombardment Dest Picker +class spell_ice_bombardment_dest_picker : public SpellScriptLoader +{ +public: + spell_ice_bombardment_dest_picker() : SpellScriptLoader("spell_ice_bombardment_dest_picker") { } + + class spell_ice_bombardment_dest_picker_SpellScript : public SpellScript + { + PrepareSpellScript(spell_ice_bombardment_dest_picker_SpellScript); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_ICE_BOMBARDMENT)) + return false; + return true; + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + GetCaster()->CastSpell(GetHitDest()->GetPositionX(), GetHitDest()->GetPositionY(), GetHitDest()->GetPositionZ(), SPELL_ICE_BOMBARDMENT, true); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_ice_bombardment_dest_picker_SpellScript::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_DUMMY); + } + }; + + SpellScript* GetSpellScript() const override + { + return new spell_ice_bombardment_dest_picker_SpellScript(); + } +}; + +void AddSC_boss_ahune() +{ + new boss_ahune(); + new npc_frozen_core(); + new npc_earthen_ring_flamecaller(); + new npc_ahune_bunny(); + new go_ahune_ice_stone(); + new spell_ahune_synch_health(); + new spell_summoning_rhyme_aura(); + new spell_summon_ice_spear_delayer(); + new spell_ice_spear_control_aura(); + new spell_slippery_floor_periodic(); + new spell_ahune_spanky_hands(); + new spell_ahune_minion_despawner(); + new spell_ice_spear_target_picker(); + new spell_ice_bombardment_dest_picker(); +} diff --git a/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/instance_the_slave_pens.cpp b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/instance_the_slave_pens.cpp index 87a07cd1e5e..0d85a8f0d86 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/instance_the_slave_pens.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/instance_the_slave_pens.cpp @@ -31,15 +31,125 @@ class instance_the_slave_pens : public InstanceMapScript public: instance_the_slave_pens() : InstanceMapScript(SPScriptName, 547) { } + struct instance_the_slave_pens_InstanceMapScript : public InstanceScript + { + instance_the_slave_pens_InstanceMapScript(Map* map) : InstanceScript(map) + { + counter = DATA_FLAMECALLER_000; + } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_AHUNE: + AhuneGUID = creature->GetGUID(); + break; + case NPC_FROZEN_CORE: + FrozenCoreGUID = creature->GetGUID(); + break; + case NPC_AHUNE_LOC_BUNNY: + AhuneBunnyGUID = creature->GetGUID(); + break; + case NPC_SHAMAN_BONFIRE_BUNNY_000: + BonfireBunnyGUIDs[0] = creature->GetGUID(); + break; + case NPC_SHAMAN_BONFIRE_BUNNY_001: + BonfireBunnyGUIDs[1] = creature->GetGUID(); + break; + case NPC_SHAMAN_BONFIRE_BUNNY_002: + BonfireBunnyGUIDs[2] = creature->GetGUID(); + break; + case NPC_SHAMAN_BEAM_BUNNY_000: + BeamBunnyGUIDs[0] = creature->GetGUID(); + break; + case NPC_SHAMAN_BEAM_BUNNY_001: + BeamBunnyGUIDs[1] = creature->GetGUID(); + break; + case NPC_SHAMAN_BEAM_BUNNY_002: + BeamBunnyGUIDs[2] = creature->GetGUID(); + break; + case NPC_LUMA_SKYMOTHER: + LumaGUID = creature->GetGUID(); + break; + case NPC_EARTHEN_RING_FLAMECALLER: + SetGuidData(counter, creature->GetGUID()); + ++counter; + break; + default: + break; + } + } + + void SetGuidData(uint32 data, ObjectGuid guid) + { + switch (data) + { + case DATA_FLAMECALLER_000: + FlameCallerGUIDs[0] = guid; + break; + case DATA_FLAMECALLER_001: + FlameCallerGUIDs[1] = guid; + break; + case DATA_FLAMECALLER_002: + FlameCallerGUIDs[2] = guid; + break; + default: + break; + } + } + + ObjectGuid GetGuidData(uint32 type) const override + { + switch (type) + { + case DATA_AHUNE: + return AhuneGUID; + case DATA_AHUNE_BUNNY: + return AhuneBunnyGUID; + case DATA_FROZEN_CORE: + return FrozenCoreGUID; + case DATA_FLAMECALLER_000: + return FlameCallerGUIDs[0]; + case DATA_FLAMECALLER_001: + return FlameCallerGUIDs[1]; + case DATA_FLAMECALLER_002: + return FlameCallerGUIDs[2]; + case DATA_BONFIRE_BUNNY_000: + return BonfireBunnyGUIDs[0]; + case DATA_BONFIRE_BUNNY_001: + return BonfireBunnyGUIDs[1]; + case DATA_BONFIRE_BUNNY_002: + return BonfireBunnyGUIDs[2]; + case DATA_BEAM_BUNNY_000: + return BeamBunnyGUIDs[0]; + case DATA_BEAM_BUNNY_001: + return BeamBunnyGUIDs[1]; + case DATA_BEAM_BUNNY_002: + return BeamBunnyGUIDs[2]; + case DATA_LUMA_SKYMOTHER: + return LumaGUID; + default: + break; + } + return ObjectGuid::Empty; + } + + protected: + ObjectGuid AhuneGUID; + ObjectGuid AhuneBunnyGUID; + ObjectGuid FrozenCoreGUID; + ObjectGuid LumaGUID; + ObjectGuid FlameCallerGUIDs[3]; + ObjectGuid BonfireBunnyGUIDs[3]; + ObjectGuid BeamBunnyGUIDs[3]; + uint8 counter; + }; + InstanceScript* GetInstanceScript(InstanceMap* map) const override { return new instance_the_slave_pens_InstanceMapScript(map); } - - struct instance_the_slave_pens_InstanceMapScript : public InstanceScript - { - instance_the_slave_pens_InstanceMapScript(Map* map) : InstanceScript(map) { } - }; }; void AddSC_instance_the_slave_pens() diff --git a/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/the_slave_pens.h b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/the_slave_pens.h index 95e6e44121e..544e98fd206 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/the_slave_pens.h +++ b/src/server/scripts/Outland/CoilfangReservoir/TheSlavePens/the_slave_pens.h @@ -27,7 +27,43 @@ enum DataTypes { DATA_MENNU_THE_BETRAYER = 1, DATA_ROKMAR_THE_CRACKLER = 2, - DATA_QUAGMIRRAN = 3 + DATA_QUAGMIRRAN = 3, + DATA_AHUNE = 4, + DATA_AHUNE_BUNNY = 5, + DATA_FROZEN_CORE = 6, + DATA_FLAMECALLER_000 = 7, + DATA_FLAMECALLER_001 = 8, + DATA_FLAMECALLER_002 = 9, + DATA_BONFIRE_BUNNY_000 = 10, + DATA_BONFIRE_BUNNY_001 = 11, + DATA_BONFIRE_BUNNY_002 = 12, + DATA_BEAM_BUNNY_000 = 13, + DATA_BEAM_BUNNY_001 = 14, + DATA_BEAM_BUNNY_002 = 15, + DATA_LUMA_SKYMOTHER = 16 +}; + +enum CreaturesIds +{ + NPC_AHUNE = 25740, + NPC_FROZEN_CORE = 25865, + NPC_LUMA_SKYMOTHER = 25697, + NPC_AHUNE_LOC_BUNNY = 25745, + NPC_EARTHEN_RING_FLAMECALLER = 25754, + NPC_SHAMAN_BONFIRE_BUNNY_000 = 25971, + NPC_SHAMAN_BONFIRE_BUNNY_001 = 25972, + NPC_SHAMAN_BONFIRE_BUNNY_002 = 25973, + NPC_SHAMAN_BEAM_BUNNY_000 = 25964, + NPC_SHAMAN_BEAM_BUNNY_001 = 25965, + NPC_SHAMAN_BEAM_BUNNY_002 = 25966, + NPC_WHISP_DEST_BUNNY = 26120, + NPC_WHISP_SOURCE_BUNNY = 26121 +}; + +enum GameObjectIds +{ + GO_ICE_SPEAR = 188077, + GO_ICE_STONE = 187882 }; #endif // SLAVE_PENS_H diff --git a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_vazruden_the_herald.cpp b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_vazruden_the_herald.cpp index 4e20e6b0953..9b8220596c9 100644 --- a/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_vazruden_the_herald.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/HellfireRamparts/boss_vazruden_the_herald.cpp @@ -372,7 +372,7 @@ class boss_vazruden_the_herald : public CreatureScript if (summon->GetEntry() == NPC_NAZAN) { summon->SetDisableGravity(true); - summon->SetSpeed(MOVE_FLIGHT, 2.5f); + summon->SetSpeedRate(MOVE_FLIGHT, 2.5f); if (victim) AttackStartNoMove(victim); } diff --git a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp index 4faab709e16..c29d560529d 100644 --- a/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/ShatteredHalls/boss_warchief_kargath_bladefist.cpp @@ -109,7 +109,7 @@ class boss_warchief_kargath_bladefist : public CreatureScript { removeAdds(); _Reset(); - me->SetSpeed(MOVE_RUN, 2); + me->SetSpeedRate(MOVE_RUN, 2); me->SetWalk(false); Initialize(); @@ -231,7 +231,7 @@ class boss_warchief_kargath_bladefist : public CreatureScript { // stop bladedance InBlade = false; - me->SetSpeed(MOVE_RUN, 2); + me->SetSpeedRate(MOVE_RUN, 2); me->GetMotionMaster()->MoveChase(me->GetVictim()); Blade_Dance_Timer = 30000; Wait_Timer = 0; @@ -264,7 +264,7 @@ class boss_warchief_kargath_bladefist : public CreatureScript Wait_Timer = 1; InBlade = true; Blade_Dance_Timer = 0; - me->SetSpeed(MOVE_RUN, 4); + me->SetSpeedRate(MOVE_RUN, 4); return; } else diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp index 56010c09897..20d96ecd29c 100644 --- a/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp +++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_alar.cpp @@ -130,7 +130,7 @@ class boss_alar : public CreatureScript _Reset(); me->SetDisplayId(me->GetNativeDisplayId()); - me->SetSpeed(MOVE_RUN, DefaultMoveSpeedRate); + me->SetSpeedRate(MOVE_RUN, DefaultMoveSpeedRate); //me->SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, 10); //me->SetFloatValue(UNIT_FIELD_COMBATREACH, 10); me->ApplySpellImmune(0, IMMUNITY_SCHOOL, SPELL_SCHOOL_MASK_FIRE, true); @@ -178,7 +178,7 @@ class boss_alar : public CreatureScript me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); me->AttackStop(); me->SetTarget(ObjectGuid::Empty); - me->SetSpeed(MOVE_RUN, 5.0f); + me->SetSpeedRate(MOVE_RUN, 5.0f); me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MovePoint(0, waypoint[5][0], waypoint[5][1], waypoint[5][2]); } @@ -261,7 +261,7 @@ class boss_alar : public CreatureScript case WE_REVIVE: me->SetUInt32Value(UNIT_FIELD_BYTES_1, UNIT_STAND_STATE_STAND); me->SetFullHealth(); - me->SetSpeed(MOVE_RUN, DefaultMoveSpeedRate); + me->SetSpeedRate(MOVE_RUN, DefaultMoveSpeedRate); me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE); DoZoneInCombat(); DoCast(me, SPELL_REBIRTH, true); diff --git a/src/server/scripts/Outland/TempestKeep/Mechanar/boss_nethermancer_sepethrea.cpp b/src/server/scripts/Outland/TempestKeep/Mechanar/boss_nethermancer_sepethrea.cpp index 3aa2674aec6..d45e17bd28d 100644 --- a/src/server/scripts/Outland/TempestKeep/Mechanar/boss_nethermancer_sepethrea.cpp +++ b/src/server/scripts/Outland/TempestKeep/Mechanar/boss_nethermancer_sepethrea.cpp @@ -176,7 +176,7 @@ class npc_ragin_flames : public CreatureScript Initialize(); me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_MAGIC, true); me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, true); - me->SetSpeed(MOVE_RUN, DUNGEON_MODE(0.5f, 0.7f)); + me->SetSpeedRate(MOVE_RUN, DUNGEON_MODE(0.5f, 0.7f)); } void EnterCombat(Unit* /*who*/) override diff --git a/src/server/scripts/Outland/TempestKeep/botanica/boss_warp_splinter.cpp b/src/server/scripts/Outland/TempestKeep/botanica/boss_warp_splinter.cpp index 856649c6c5e..e64c0fe9f5e 100644 --- a/src/server/scripts/Outland/TempestKeep/botanica/boss_warp_splinter.cpp +++ b/src/server/scripts/Outland/TempestKeep/botanica/boss_warp_splinter.cpp @@ -167,7 +167,7 @@ class boss_warp_splinter : public CreatureScript { Initialize(); - me->SetSpeed(MOVE_RUN, 0.7f, true); + me->SetSpeedRate(MOVE_RUN, 0.7f); } void EnterCombat(Unit* /*who*/) override diff --git a/src/server/scripts/Outland/outland_script_loader.cpp b/src/server/scripts/Outland/outland_script_loader.cpp index 91ba4e5689f..ed2c21da6c3 100644 --- a/src/server/scripts/Outland/outland_script_loader.cpp +++ b/src/server/scripts/Outland/outland_script_loader.cpp @@ -72,6 +72,7 @@ void AddSC_instance_the_slave_pens(); void AddSC_boss_mennu_the_betrayer(); void AddSC_boss_rokmar_the_crackler(); void AddSC_boss_quagmirran(); +void AddSC_boss_ahune(); // Coilfang Reservoir - The Underbog void AddSC_instance_the_underbog(); @@ -193,6 +194,7 @@ void AddOutlandScripts() AddSC_boss_mennu_the_betrayer(); AddSC_boss_rokmar_the_crackler(); AddSC_boss_quagmirran(); + AddSC_boss_ahune(); // Coilfang Reservoir - The Underbog AddSC_instance_the_underbog(); diff --git a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp index 404cdc7ceb2..668c484dd63 100644 --- a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp +++ b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp @@ -159,7 +159,7 @@ public: else TC_LOG_ERROR("scripts", "TRINITY: npc_ancestral_wolf can not obtain owner or owner is not a player."); - creature->SetSpeed(MOVE_WALK, 1.5f); + creature->SetSpeedRate(MOVE_WALK, 1.5f); Reset(); } @@ -192,7 +192,7 @@ public: if (ryga->IsAlive() && !ryga->IsInCombat()) { ryga->SetWalk(true); - ryga->SetSpeed(MOVE_WALK, 1.5f); + ryga->SetSpeedRate(MOVE_WALK, 1.5f); ryga->GetMotionMaster()->MovePoint(0, 517.340698f, 3885.03975f, 190.455978f, true); Reset(); } @@ -778,7 +778,7 @@ public: me->AddAura(SPELL_JULES_THREATENS_AURA, me); me->SetCanFly(true); - me->SetSpeed(MOVE_RUN, 0.2f); + me->SetSpeedRate(MOVE_RUN, 0.2f); me->SetFacingTo(3.207566f); me->GetMotionMaster()->MoveJump(exorcismPos[2], 2.0f, 2.0f); @@ -798,7 +798,7 @@ public: break; case ACTION_JULES_MOVE_HOME: wpreached = false; - me->SetSpeed(MOVE_RUN, 1.0f); + me->SetSpeedRate(MOVE_RUN, 1.0f); me->GetMotionMaster()->MovePoint(11, exorcismPos[2]); events.CancelEvent(EVENT_SUMMON_SKULL); diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index 80b3a00774b..113b14a0d54 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -103,8 +103,8 @@ class npc_pet_dk_ebon_gargoyle : public CreatureScript //! HACK: Creature's can't have MOVEMENTFLAG_FLYING // Fly Away me->SetCanFly(true); - me->SetSpeed(MOVE_FLIGHT, 0.75f, true); - me->SetSpeed(MOVE_RUN, 0.75f, true); + me->SetSpeedRate(MOVE_FLIGHT, 0.75f); + me->SetSpeedRate(MOVE_RUN, 0.75f); float x = me->GetPositionX() + 20 * std::cos(me->GetOrientation()); float y = me->GetPositionY() + 20 * std::sin(me->GetOrientation()); float z = me->GetPositionZ() + 40; diff --git a/src/server/scripts/ScriptLoader.cpp b/src/server/scripts/ScriptLoader.cpp deleted file mode 100644 index 57c848d5981..00000000000 --- a/src/server/scripts/ScriptLoader.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "ScriptLoader.h" -#include "World.h" - -void AddSpellsScripts(); -void AddCommandsScripts(); - -#ifdef SCRIPTS -void AddWorldScripts(); -void AddEasternKingdomsScripts(); -void AddKalimdorScripts(); -void AddOutlandScripts(); -void AddNorthrendScripts(); -void AddEventsScripts(); -void AddPetScripts(); -void AddOutdoorPvPScripts(); -void AddCustomScripts(); -#endif - -void AddScripts() -{ - AddSpellsScripts(); - AddCommandsScripts(); -#ifdef SCRIPTS - AddWorldScripts(); - AddEasternKingdomsScripts(); - AddKalimdorScripts(); - AddOutlandScripts(); - AddNorthrendScripts(); - AddEventsScripts(); - AddPetScripts(); - AddOutdoorPvPScripts(); - AddCustomScripts(); -#endif -} diff --git a/src/server/scripts/ScriptLoader.cpp.in.cmake b/src/server/scripts/ScriptLoader.cpp.in.cmake new file mode 100644 index 00000000000..33c336a9a93 --- /dev/null +++ b/src/server/scripts/ScriptLoader.cpp.in.cmake @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +// This file was created automatically from your script configuration! +// Use CMake to reconfigure this file, never change it on your own! + +#cmakedefine TRINITY_IS_DYNAMIC_SCRIPTLOADER + +#include "Define.h" +#include <vector> +#include <string> + +@TRINITY_SCRIPTS_FORWARD_DECL@ +#ifdef TRINITY_IS_DYNAMIC_SCRIPTLOADER +# include "revision_data.h" +# define TC_SCRIPT_API TC_API_EXPORT +extern "C" { + +/// Exposed in script modules to return the script module revision hash. +TC_SCRIPT_API char const* GetScriptModuleRevisionHash() +{ + return _HASH; +} + +/// Exposed in script module to return the name of the script module +/// contained in this shared library. +TC_SCRIPT_API char const* GetScriptModule() +{ + return "@TRINITY_CURRENT_SCRIPT_PROJECT@"; +} + +#else +# include "ScriptLoader.h" +# define TC_SCRIPT_API +#endif + +/// Exposed in script modules to register all scripts to the ScriptMgr. +TC_SCRIPT_API void AddScripts() +{ +@TRINITY_SCRIPTS_INVOKE@} + +/// Exposed in script modules to get the build directive of the module. +TC_SCRIPT_API char const* GetBuildDirective() +{ + return _BUILD_DIRECTIVE; +} + +#ifdef TRINITY_IS_DYNAMIC_SCRIPTLOADER +} // extern "C" +#endif diff --git a/src/server/scripts/PrecompiledHeaders/ScriptPCH.cpp b/src/server/scripts/ScriptPCH.cpp index 41fecf3c60d..41fecf3c60d 100644 --- a/src/server/scripts/PrecompiledHeaders/ScriptPCH.cpp +++ b/src/server/scripts/ScriptPCH.cpp diff --git a/src/server/scripts/PrecompiledHeaders/ScriptPCH.h b/src/server/scripts/ScriptPCH.h index 1cd25309055..1cd25309055 100644 --- a/src/server/scripts/PrecompiledHeaders/ScriptPCH.h +++ b/src/server/scripts/ScriptPCH.h diff --git a/src/server/scripts/World/boss_emerald_dragons.cpp b/src/server/scripts/World/boss_emerald_dragons.cpp index 1f89720803d..65dec74414b 100644 --- a/src/server/scripts/World/boss_emerald_dragons.cpp +++ b/src/server/scripts/World/boss_emerald_dragons.cpp @@ -205,7 +205,7 @@ class npc_dream_fog : public CreatureScript } // Seeping fog movement is slow enough for a player to be able to walk backwards and still outpace it me->SetWalk(true); - me->SetSpeed(MOVE_WALK, 0.75f); + me->SetSpeedRate(MOVE_WALK, 0.75f); } else _roamTimer -= diff; diff --git a/src/server/shared/DataStores/DBCStore.h b/src/server/shared/DataStores/DBCStore.h index b93bbdaea12..7c2cab1e36a 100644 --- a/src/server/shared/DataStores/DBCStore.h +++ b/src/server/shared/DataStores/DBCStore.h @@ -25,6 +25,121 @@ #include "DatabaseWorkerPool.h" #include "Implementation/WorldDatabase.h" #include "DatabaseEnv.h" +#include <G3D/Vector3.h> +#include <G3D/AABox.h> + + // Structures for M4 file. Source: https://wowdev.wiki +template<typename T> +struct M2SplineKey +{ + T p0; + T p1; + T p2; +}; + +struct M2Header +{ + char Magic[4]; // "MD20" + uint32 Version; // The version of the format. + uint32 lName; // Length of the model's name including the trailing \0 + uint32 ofsName; // Offset to the name, it seems like models can get reloaded by this name.should be unique, i guess. + uint32 GlobalModelFlags; // 0x0001: tilt x, 0x0002: tilt y, 0x0008: add 2 fields in header, 0x0020: load .phys data (MoP+), 0x0080: has _lod .skin files (MoP?+), 0x0100: is camera related. + uint32 nGlobalSequences; + uint32 ofsGlobalSequences; // A list of timestamps. + uint32 nAnimations; + uint32 ofsAnimations; // Information about the animations in the model. + uint32 nAnimationLookup; + uint32 ofsAnimationLookup; // Mapping of global IDs to the entries in the Animation sequences block. + uint32 nBones; // MAX_BONES = 0x100 + uint32 ofsBones; // Information about the bones in this model. + uint32 nKeyBoneLookup; + uint32 ofsKeyBoneLookup; // Lookup table for key skeletal bones. + uint32 nVertices; + uint32 ofsVertices; // Vertices of the model. + uint32 nViews; // Views (LOD) are now in .skins. + uint32 nSubmeshAnimations; + uint32 ofsSubmeshAnimations; // Submesh color and alpha animations definitions. + uint32 nTextures; + uint32 ofsTextures; // Textures of this model. + uint32 nTransparency; + uint32 ofsTransparency; // Transparency of textures. + uint32 nUVAnimation; + uint32 ofsUVAnimation; + uint32 nTexReplace; + uint32 ofsTexReplace; // Replaceable Textures. + uint32 nRenderFlags; + uint32 ofsRenderFlags; // Blending modes / render flags. + uint32 nBoneLookupTable; + uint32 ofsBoneLookupTable; // A bone lookup table. + uint32 nTexLookup; + uint32 ofsTexLookup; // The same for textures. + uint32 nTexUnits; // possibly removed with cata?! + uint32 ofsTexUnits; // And texture units. Somewhere they have to be too. + uint32 nTransLookup; + uint32 ofsTransLookup; // Everything needs its lookup. Here are the transparencies. + uint32 nUVAnimLookup; + uint32 ofsUVAnimLookup; + G3D::AABox BoundingBox; // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height + float BoundingSphereRadius; + G3D::AABox CollisionBox; + float CollisionSphereRadius; + uint32 nBoundingTriangles; + uint32 ofsBoundingTriangles; // Our bounding volumes. Similar structure like in the old ofsViews. + uint32 nBoundingVertices; + uint32 ofsBoundingVertices; + uint32 nBoundingNormals; + uint32 ofsBoundingNormals; + uint32 nAttachments; + uint32 ofsAttachments; // Attachments are for weapons etc. + uint32 nAttachLookup; + uint32 ofsAttachLookup; // Of course with a lookup. + uint32 nEvents; + uint32 ofsEvents; // Used for playing sounds when dying and a lot else. + uint32 nLights; + uint32 ofsLights; // Lights are mainly used in loginscreens but in wands and some doodads too. + uint32 nCameras; // Format of Cameras changed with version 271! + uint32 ofsCameras; // The cameras are present in most models for having a model in the Character-Tab. + uint32 nCameraLookup; + uint32 ofsCameraLookup; // And lookup-time again. + uint32 nRibbonEmitters; + uint32 ofsRibbonEmitters; // Things swirling around. See the CoT-entrance for light-trails. + uint32 nParticleEmitters; + uint32 ofsParticleEmitters; // Spells and weapons, doodads and loginscreens use them. Blood dripping of a blade? Particles. + uint32 nBlendMaps; // This has to deal with blending. Exists IFF (flags & 0x8) != 0. When set, textures blending is overriden by the associated array. See M2/WotLK#Blend_mode_overrides + uint32 ofsBlendMaps; // Same as above. Points to an array of uint16 of nBlendMaps entries -- From WoD information.}; +}; + +struct M2Array +{ + uint32_t number; + uint32 offset_elements; +}; +struct M2Track +{ + uint16_t interpolation_type; + uint16_t global_sequence; + M2Array timestamps; + M2Array values; +}; + +struct M2Camera +{ + uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table. + float fov; // No radians, no degrees. Multiply by 35 to get degrees. + float far_clip; + float near_clip; + M2Track positions; // How the camera's position moves. Should be 3*3 floats. + G3D::Vector3 position_base; + M2Track target_positions; // How the target moves. Should be 3*3 floats. + G3D::Vector3 target_position_base; + M2Track rolldata; // The camera can have some roll-effect. Its 0 to 2*Pi. +}; + +struct FlyByCamera +{ + uint32 timeStamp; + G3D::Vector4 locations; +}; struct SqlDbc { diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt index 7923b239854..0de8d6054f3 100644 --- a/src/server/worldserver/CMakeLists.txt +++ b/src/server/worldserver/CMakeLists.txt @@ -48,6 +48,7 @@ set_target_properties(worldserver PROPERTIES LINK_FLAGS "${worldserver_LINK_FLAG target_link_libraries(worldserver PUBLIC scripts + game gsoap readline) @@ -68,6 +69,11 @@ set_target_properties(worldserver FOLDER "server") +# Add all dynamic projects as dependency to the worldserver +if (WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES) + add_dependencies(worldserver ${WORLDSERVER_DYNAMIC_SCRIPT_MODULES_DEPENDENCIES}) +endif() + if( WIN32 ) if ( MSVC ) add_custom_command(TARGET worldserver diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index c047c1fafc3..2b5553b4bb0 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -39,6 +39,7 @@ #include "InstanceSaveMgr.h" #include "ObjectAccessor.h" #include "ScriptMgr.h" +#include "ScriptReloadMgr.h" #include "ScriptLoader.h" #include "OutdoorPvP/OutdoorPvPMgr.h" #include "BattlegroundMgr.h" @@ -271,6 +272,7 @@ extern int main(int argc, char** argv) sOutdoorPvPMgr->Die(); sMapMgr->UnloadAll(); // unload all grids (including locked in memory) sScriptMgr->Unload(); + sScriptReloadMgr->Unload(); // set server offline LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm); diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 7c512d8ae6e..c2694c640c5 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -12,6 +12,7 @@ # SERVER LOGGING # SERVER SETTINGS # UPDATE SETTINGS +# HOTSWAP SETTINGS # WARDEN SETTINGS # PLAYER INTERACTION # CREATURE SETTINGS @@ -1280,6 +1281,78 @@ Updates.CleanDeadRefMaxCount = 3 ################################################################################################### ################################################################################################### +# HOTSWAP SETTINGS +# +# HotSwap.Enabled (Requires compilation with DYNAMIC_LINKING=1) +# Description: Enables dynamic script hotswapping. +# Reloads scripts on changes. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.Enabled = 1 + +# +# HotSwap.ScriptDir +# Description: Directory containing the script shared libraries (.dll/.so). +# Example: "/usr/local/scripts" +# Default: "scripts" + +HotSwap.ScriptDir = "scripts" + +# HotSwap.EnableReCompiler +# Description: Enables the dynamic script recompiler. +# Watches your script source directories and recompiles the +# script modules on changes. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableReCompiler = 1 + +# HotSwap.EnableEarlyTermination +# Description: Terminate the build of a module when an associated +# source file was changed meanwhile. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableEarlyTermination = 1 + +# HotSwap.EnableBuildFileRecreation +# Description: Recreate build files when sources to a module +# were added or removed. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableBuildFileRecreation = 1 + +# +# HotSwap.EnableInstall +# Description: Enables cmake install after automatic builds have finished +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnableInstall = 1 + +# +# HotSwap.EnablePrefixCorrection +# Description: Allows the core to automatic set the CMAKE_INSTALL_PREFIX +# to it's current location in the filesystem. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +HotSwap.EnablePrefixCorrection = 1 + +# HotSwap.ReCompilerBuildType +# Description: Defines the build type of the builds invoked by the recompiler. +# Default: "" - Built-in build type of the module is used. +# "Release" - Release builds only +# "Debug" - Debug builds only + +HotSwap.ReCompilerBuildType = "" + +# +################################################################################################### + +################################################################################################### # WARDEN SETTINGS # # Warden.Enabled @@ -3220,7 +3293,6 @@ AuctionHouseBot.Class.Key = 1 AuctionHouseBot.Class.Misc = 5 AuctionHouseBot.Class.Glyph = 3 - # ################################################################################################### @@ -3551,6 +3623,7 @@ Logger.mmaps=3,Server #Logger.rbac=3,Console Server #Logger.scripts=3,Console Server #Logger.scripts.ai=3,Console Server +#Logger.scripts.hotswap=3,Console Server #Logger.server.authserver=3,Console Server #Logger.spells=3,Console Server #Logger.spells.periodic=3,Console Server diff --git a/src/tools/map_extractor/System.cpp b/src/tools/map_extractor/System.cpp index f3a761fd437..1d84fc75d27 100644 --- a/src/tools/map_extractor/System.cpp +++ b/src/tools/map_extractor/System.cpp @@ -53,12 +53,13 @@ char input_path[MAX_PATH_LENGTH] = "."; // ************************************************** enum Extract { - EXTRACT_MAP = 1, - EXTRACT_DBC = 2 + EXTRACT_MAP = 1, + EXTRACT_DBC = 2, + EXTRACT_CAMERA = 4 }; // Select data for extract -int CONF_extract = EXTRACT_MAP | EXTRACT_DBC; +int CONF_extract = EXTRACT_MAP | EXTRACT_DBC | EXTRACT_CAMERA; // This option allow limit minimum height to some value (Allow save some memory) bool CONF_allow_height_limit = true; float CONF_use_minHeight = -500.0f; @@ -103,7 +104,7 @@ void Usage(char* prg) "%s -[var] [value]\n"\ "-i set input path (max %d characters)\n"\ "-o set output path (max %d characters)\n"\ - "-e extract only MAP(1)/DBC(2) - standard: both(3)\n"\ + "-e extract only MAP(1)/DBC(2)/Camera(4) - standard: all(7)\n"\ "-f height stored as int (less map size but lost some accuracy) 1 by default\n"\ "Example: %s -f 0 -i \"c:\\games\\game\"", prg, MAX_PATH_LENGTH - 1, MAX_PATH_LENGTH - 1, prg); exit(1); @@ -151,7 +152,7 @@ void HandleArgs(int argc, char * arg[]) if(c + 1 < argc) // all ok { CONF_extract=atoi(arg[(c++) + 1]); - if(!(CONF_extract > 0 && CONF_extract < 4)) + if(!(CONF_extract > 0 && CONF_extract < 8)) Usage(arg[0]); } else @@ -1025,6 +1026,56 @@ void ExtractDBCFiles(int locale, bool basicLocale) printf("Extracted %u DBC files\n\n", count); } +void ExtractCameraFiles(int locale, bool basicLocale) +{ + printf("Extracting camera files...\n"); + DBCFile camdbc("DBFilesClient\\CinematicCamera.dbc"); + + if (!camdbc.open()) + { + printf("Unable to open CinematicCamera.dbc. Camera extract aborted.\n"); + return; + } + + // get camera file list from DBC + std::vector<std::string> camerafiles; + size_t cam_count = camdbc.getRecordCount(); + + for (uint32 i = 0; i < cam_count; ++i) + { + std::string camFile(camdbc.getRecord(i).getString(1)); + size_t loc = camFile.find(".mdx"); + if (loc != std::string::npos) + camFile.replace(loc, 4, ".m2"); + camerafiles.push_back(std::string(camFile)); + } + + std::string path = output_path; + path += "/Cameras/"; + CreateDir(path); + if (!basicLocale) + { + path += langs[locale]; + path += "/"; + CreateDir(path); + } + + // extract M2s + uint32 count = 0; + for (std::string thisFile : camerafiles) + { + std::string filename = path; + filename += (thisFile.c_str() + strlen("Cameras\\")); + + if (boost::filesystem::exists(filename)) + continue; + + if (ExtractFile(thisFile.c_str(), filename)) + ++count; + } + printf("Extracted %u camera files\n", count); +} + void LoadLocaleMPQFiles(int const locale) { std::string fileName = Trinity::StringFormat("%s/Data/%s/locale-%s.MPQ", input_path, langs[locale], langs[locale]); @@ -1111,6 +1162,19 @@ int main(int argc, char * arg[]) return 0; } + if (CONF_extract & EXTRACT_CAMERA) + { + printf("Using locale: %s\n", langs[FirstLocale]); + + // Open MPQs + LoadLocaleMPQFiles(FirstLocale); + LoadCommonMPQFiles(); + + ExtractCameraFiles(FirstLocale, true); + // Close MPQs + CloseMPQFiles(); + } + if (CONF_extract & EXTRACT_MAP) { printf("Using locale: %s\n", langs[FirstLocale]); |