diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index b03c488e7d..d26be8b9ae 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -27,13 +27,13 @@ jobs: - OPTIONAL_DEFINES: "" TYPE: "default" - - OPTIONAL_DEFINES: "-DBUILD_EXTRACTORS=ON -DBUILD_PLAYERBOT=ON -DBUILD_AHBOT=ON -DBUILD_RECASTDEMOMOD=ON -DBUILD_GIT_ID=ON" + - OPTIONAL_DEFINES: "-DBUILD_EXTRACTORS=ON -DBUILD_PLAYERBOTS=ON -DBUILD_AHBOT=ON -DBUILD_RECASTDEMOMOD=ON -DBUILD_GIT_ID=ON" TYPE: "with-all" - - OPTIONAL_DEFINES: "-DBUILD_PLAYERBOT=ON -DBUILD_AHBOT=ON" + - OPTIONAL_DEFINES: "-DBUILD_PLAYERBOTS=ON -DBUILD_AHBOT=ON" TYPE: "with-playerbot-ahbot" - - OPTIONAL_DEFINES: "-DBUILD_PLAYERBOT=ON" + - OPTIONAL_DEFINES: "-DBUILD_PLAYERBOTS=ON" TYPE: "with-playerbot" - OPTIONAL_DEFINES: "-DBUILD_AHBOT=ON" @@ -170,8 +170,8 @@ jobs: - [Default Download](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.DEFAULT_ARCH_NAME}}) - [All Options Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.ALL_ARCH_NAME}}) - [AHBot Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.AB_ARCH_NAME}}) - - [PlayerBot Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.PB_ARCH_NAME}}) - - [AHBot & PlayerBot Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.PB_AB_ARCH_NAME}}) + - [PlayerBots Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.PB_ARCH_NAME}}) + - [AHBot & PlayerBots Enabled](${{github.server_url}}/${{ github.repository }}/releases/download/latest/${{env.PB_AB_ARCH_NAME}}) If you find any bugs or issues please report them [here](https://github.com/cmangos/issues/issues/new/choose). footer: Created by the CMaNGOS Team! diff --git a/.gitignore b/.gitignore index 041d34d401..3b2390e4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,8 @@ cmake_install.cmake # recastnavigation directory needs exception !dep/recastnavigation/RecastDemo/Build/ /_build/ + +# +# Module files +# +src/modules/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index acd335f89a..9325cb0ae0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,6 +329,18 @@ if(NOT BUILD_GAME_SERVER AND BUILD_DEPRECATED_PLAYERBOT) message(STATUS "BUILD_DEPRECATED_PLAYERBOT forced to OFF due to BUILD_GAME_SERVER is not set") endif() +if(BUILD_PLAYERBOTS) + if(BUILD_DEPRECATED_PLAYERBOT) + set(BUILD_DEPRECATED_PLAYERBOT OFF) + message(STATUS "BUILD_DEPRECATED_PLAYERBOT forced to OFF because BUILD_PLAYERBOTS is set") + endif() + + if(NOT BUILD_GAME_SERVER) + set(BUILD_PLAYERBOTS OFF) + message(STATUS "BUILD_PLAYERBOTS forced to OFF due to BUILD_GAME_SERVER is not set") + endif() +endif() + if(PCH) if(${CMAKE_VERSION} VERSION_LESS "3.16") message("PCH is not supported by your CMake version") diff --git a/cmake/options.cmake b/cmake/options.cmake index a7b4526595..e180ee0e70 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -6,6 +6,7 @@ option(BUILD_GAME_SERVER "Build game server" option(BUILD_LOGIN_SERVER "Build login server" ON) option(BUILD_EXTRACTORS "Build map/dbc/vmap/mmap extractors" OFF) option(BUILD_SCRIPTDEV "Build ScriptDev. (OFF Speedup build)" ON) +option(BUILD_PLAYERBOTS "Build Playerbots mod" OFF) option(BUILD_AHBOT "Build Auction House Bot mod" OFF) option(BUILD_METRICS "Build Metrics, generate data for Grafana" OFF) option(BUILD_RECASTDEMOMOD "Build map/vmap/mmap viewer" OFF) @@ -33,6 +34,7 @@ message(STATUS BUILD_GAME_SERVER Build game server (core server) BUILD_LOGIN_SERVER Build login server (auth server) BUILD_EXTRACTORS Build map/dbc/vmap/mmap extractor + BUILD_PLAYERBOTS Build Playerbots mod BUILD_AHBOT Build Auction House Bot mod BUILD_METRICS Build Metrics, generate data for Grafana BUILD_RECASTDEMOMOD Build map/vmap/mmap viewer diff --git a/cmake/showoptions.cmake b/cmake/showoptions.cmake index 83103f6968..7918ceceeb 100644 --- a/cmake/showoptions.cmake +++ b/cmake/showoptions.cmake @@ -67,6 +67,12 @@ else() message(STATUS "Build OLD Playerbot : No (default)") endif() +if(BUILD_PLAYERBOTS) + message(STATUS "Build Playerbots : Yes") +else() + message(STATUS "Build Playerbots : No (default)") +endif() + if(BUILD_EXTRACTORS) message(STATUS "Build extractors : Yes") else() diff --git a/contrib/mmap/src/MapBuilder.cpp b/contrib/mmap/src/MapBuilder.cpp index f6e397af6b..cf2030e80a 100644 --- a/contrib/mmap/src/MapBuilder.cpp +++ b/contrib/mmap/src/MapBuilder.cpp @@ -31,6 +31,36 @@ using namespace VMAP; +void rcModAlmostUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, + const float* verts, int /*nv*/, + const int* tris, int nt, + unsigned char* areas) +{ + rcIgnoreUnused(ctx); + + const float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI); + + float norm[3]; + + for (int i = 0; i < nt; ++i) + { + if (areas[i] & RC_WALKABLE_AREA) + { + const int* tri = &tris[i * 3]; + + float e0[3], e1[3]; + rcVsub(e0, &verts[tri[1] * 3], &verts[tri[0] * 3]); + rcVsub(e1, &verts[tri[2] * 3], &verts[tri[0] * 3]); + rcVcross(norm, e0, e1); + rcVnormalize(norm); + + // Check if the face is walkable. + if (norm[1] <= walkableThr) + areas[i] = NAV_AREA_GROUND_STEEP; //Slopes between 50 and 60. Walkable for mobs, unwalkable for players. + } + } +} + void from_json(const json& j, rcConfig& config) { config.tileSize = MMAP::VERTEX_PER_TILE; @@ -1004,6 +1034,10 @@ namespace MMAP unsigned char* triFlags = new unsigned char[tTriCount]; memset(triFlags, NAV_AREA_GROUND, tTriCount * sizeof(unsigned char)); rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); + + // mark almost unwalkable triangles with steep flag + rcModAlmostUnwalkableTriangles(m_rcContext, 50.0f, tVerts, tVertCount, tTris, tTriCount, triFlags); + rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, tileCfg.walkableClimb); delete[] triFlags; diff --git a/sql/create/db_create_mysql.sql b/sql/create/db_create_mysql.sql index 96c7248f15..2d4f047de4 100644 --- a/sql/create/db_create_mysql.sql +++ b/sql/create/db_create_mysql.sql @@ -8,10 +8,10 @@ CREATE DATABASE `tbcrealmd` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE USER IF NOT EXISTS 'mangos'@'localhost' IDENTIFIED BY 'mangos'; -GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbcmangos`.* TO 'mangos'@'localhost'; +GRANT INDEX, SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbcmangos`.* TO 'mangos'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbclogs`.* TO 'mangos'@'localhost'; -GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbccharacters`.* TO 'mangos'@'localhost'; +GRANT INDEX SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbccharacters`.* TO 'mangos'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `tbcrealmd`.* TO 'mangos'@'localhost'; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba37dd1c3e..ddde74c2e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,35 @@ if(BUILD_GAME_SERVER OR BUILD_LOGIN_SERVER OR BUILD_EXTRACTORS) add_subdirectory(shared) endif() +# Playerbots module +if(BUILD_PLAYERBOTS) + include(FetchContent) + + FetchContent_Declare( + PlayerBots + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/modules/PlayerBots" + GIT_REPOSITORY "https://github.com/cmangos/playerbots.git" + GIT_TAG "master" + ) + + FetchContent_GetProperties(PlayerBots) + if (NOT playerbots_POPULATED) + FetchContent_Populate(PlayerBots) + message(STATUS "Playerbots module source dir: ${playerbots_SOURCE_DIR}") + else() + message(STATUS "Playerbots module already populated: ${playerbots_POPULATED}") + endif() + + add_subdirectory(${playerbots_SOURCE_DIR}) + +else() + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/PlayerBots) + message(STATUS "PlayerBots module exists, but not building. You can remove it manually if you don't need it. (rm -rf ${CMAKE_CURRENT_SOURCE_DIR}/modules/PlayerBots)") + + #file(REMOVE_RECURSE ${CMAKE_CURRENT_SOURCE_DIR}/modules/PlayerBots) + endif() +endif() + if(BUILD_GAME_SERVER) add_subdirectory(game) add_subdirectory(mangosd) @@ -28,4 +57,4 @@ endif() if(BUILD_LOGIN_SERVER) add_subdirectory(realmd) -endif() +endif() \ No newline at end of file diff --git a/src/game/BattleGround/BattleGround.cpp b/src/game/BattleGround/BattleGround.cpp index e5e51eb37d..90ba790107 100644 --- a/src/game/BattleGround/BattleGround.cpp +++ b/src/game/BattleGround/BattleGround.cpp @@ -1683,6 +1683,22 @@ uint32 BattleGround::GetSingleCreatureGuid(uint8 event1, uint8 event2) return ObjectGuid(); } +/** + Function returns a gameobject guid from event map + @param event1 + @param event2 +*/ +uint32 BattleGround::GetSingleGameObjectGuid(uint8 event1, uint8 event2) +{ + auto itr = m_eventObjects[MAKE_PAIR32(event1, event2)].gameobjects.begin(); + if (itr != m_eventObjects[MAKE_PAIR32(event1, event2)].gameobjects.end()) + { + return *itr; + } + + return ObjectGuid(); +} + /** Method that handles gameobject load from DB event map diff --git a/src/game/BattleGround/BattleGround.h b/src/game/BattleGround/BattleGround.h index e746c71b54..5a59893dff 100644 --- a/src/game/BattleGround/BattleGround.h +++ b/src/game/BattleGround/BattleGround.h @@ -561,6 +561,9 @@ class BattleGround // Get creature guid from event uint32 GetSingleCreatureGuid(uint8 /*event1*/, uint8 /*event2*/); + // Get gameobject guid from event + uint32 GetSingleGameObjectGuid(uint8 /*event1*/, uint8 /*event2*/); + // Handle door events void OpenDoorEvent(uint8 /*event1*/, uint8 event2 = 0); bool IsDoorEvent(uint8 /*event1*/, uint8 /*event2*/) const; diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index abb56176d4..45a1d33b1f 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -51,6 +51,17 @@ if(NOT BUILD_DEPRECATED_PLAYERBOT) endforeach() endif() +if(NOT BUILD_PLAYERBOTS) + # exclude Playerbots folder + set (EXCLUDE_DIR "PlayerBots/") + foreach (TMP_PATH ${LIBRARY_SRCS}) + string (FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND) + if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1) + list(REMOVE_ITEM LIBRARY_SRCS ${TMP_PATH}) + endif () + endforeach() +endif() + set(PCH_BASE_FILENAME "pchdef") # exclude pchdef files set (EXCLUDE_FILE "${PCH_BASE_FILENAME}") @@ -83,16 +94,27 @@ target_link_libraries(${LIBRARY_NAME} PRIVATE zlib ) -# include additionals headers -set(ADDITIONAL_INCLUDE_DIRS - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/vmap - ${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouseBot - ${CMAKE_CURRENT_SOURCE_DIR}/BattleGround - ${CMAKE_CURRENT_SOURCE_DIR}/OutdoorPvP - ${CMAKE_CURRENT_SOURCE_DIR}/PlayerBot - ${CMAKE_BINARY_DIR} -) +# TO DO: Remove this if when old playerbots get removed +if(NOT BUILD_PLAYERBOTS) + set(ADDITIONAL_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/vmap + ${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouseBot + ${CMAKE_CURRENT_SOURCE_DIR}/BattleGround + ${CMAKE_CURRENT_SOURCE_DIR}/OutdoorPvP + ${CMAKE_CURRENT_SOURCE_DIR}/PlayerBot + ${CMAKE_BINARY_DIR} + ) +else() + set(ADDITIONAL_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/vmap + ${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouseBot + ${CMAKE_CURRENT_SOURCE_DIR}/BattleGround + ${CMAKE_CURRENT_SOURCE_DIR}/OutdoorPvP + ${CMAKE_BINARY_DIR} + ) +endif() target_include_directories(${LIBRARY_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} @@ -100,6 +122,12 @@ target_include_directories(${LIBRARY_NAME} PRIVATE ${Boost_INCLUDE_DIRS} ) +if(BUILD_PLAYERBOTS) + target_link_libraries(${LIBRARY_NAME} PUBLIC playerbots) + target_include_directories(${LIBRARY_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/src/modules/PlayerBots) + add_dependencies(${LIBRARY_NAME} playerbots) +endif() + if(UNIX) # Both systems don't have libdl and don't need them if (NOT (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD")) @@ -127,6 +155,11 @@ if (BUILD_DEPRECATED_PLAYERBOT) add_definitions(-DBUILD_DEPRECATED_PLAYERBOT) endif() +# Define ENABLE_PLAYERBOTS if need +if (BUILD_PLAYERBOTS) + add_definitions(-DENABLE_PLAYERBOTS) +endif() + if (MSVC) set_target_properties(${LIBRARY_NAME} PROPERTIES PROJECT_LABEL "Game") endif() diff --git a/src/game/Chat/Chat.cpp b/src/game/Chat/Chat.cpp index 95acd8e3c8..fdee2742c8 100644 --- a/src/game/Chat/Chat.cpp +++ b/src/game/Chat/Chat.cpp @@ -34,6 +34,12 @@ #include "Pools/PoolManager.h" #include "GameEvents/GameEventMgr.h" +#ifdef ENABLE_PLAYERBOTS +#include "ahbot/AhBot.h" +#include "playerbot/playerbot.h" +#include "playerbot/PlayerbotAIConfig.h" +#endif + #include // Supported shift-links (client generated and server side) @@ -942,6 +948,14 @@ ChatCommand* ChatHandler::getCommandTable() { "auction", SEC_ADMINISTRATOR, false, nullptr, "", auctionCommandTable }, #ifdef BUILD_AHBOT { "ahbot", SEC_ADMINISTRATOR, true, nullptr, "", ahbotCommandTable }, +#endif +#ifdef ENABLE_PLAYERBOTS +#ifndef BUILD_AHBOT + { "ahbot", SEC_GAMEMASTER, true, &ChatHandler::HandleAhBotCommand, "", nullptr }, +#endif + { "rndbot", SEC_GAMEMASTER, true, &ChatHandler::HandleRandomPlayerbotCommand, "", nullptr }, + { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", nullptr }, + { "pmon", SEC_GAMEMASTER, true, &ChatHandler::HandlePerfMonCommand, "", nullptr }, #endif { "cast", SEC_ADMINISTRATOR, false, nullptr, "", castCommandTable }, { "character", SEC_GAMEMASTER, true, nullptr, "", characterCommandTable}, diff --git a/src/game/Chat/Chat.h b/src/game/Chat/Chat.h index 4f50fe5246..b3a1abcfbf 100644 --- a/src/game/Chat/Chat.h +++ b/src/game/Chat/Chat.h @@ -99,7 +99,9 @@ class ChatHandler static bool HasEscapeSequences(const char* message); static bool CheckEscapeSequences(const char* message); - bool HasSentErrorMessage() const { return sentErrorMessage;} + bool HasSentErrorMessage() const { return sentErrorMessage; } + + WorldSession* GetSession() { return m_session; } /** * \brief Prepare SMSG_GM_MESSAGECHAT/SMSG_MESSAGECHAT @@ -768,6 +770,13 @@ class ChatHandler bool HandlePlayerbotCommand(char* args); #endif +#ifdef ENABLE_PLAYERBOTS + bool HandlePlayerbotCommand(char* args); + bool HandleRandomPlayerbotCommand(char* args); + bool HandleAhBotCommand(char* args); + bool HandlePerfMonCommand(char* args); +#endif + bool HandleArenaFlushPointsCommand(char* args); bool HandleArenaSeasonRewardsCommand(char* args); bool HandleArenaDataReset(char* args); diff --git a/src/game/Chat/ChatHandler.cpp b/src/game/Chat/ChatHandler.cpp index 484f9726ac..a512303b28 100644 --- a/src/game/Chat/ChatHandler.cpp +++ b/src/game/Chat/ChatHandler.cpp @@ -37,6 +37,11 @@ #include "GMTickets/GMTicketMgr.h" #include "Anticheat/Anticheat.hpp" +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/playerbot.h" +#include "playerbot/RandomPlayerbotMgr.h" +#endif + bool WorldSession::CheckChatMessage(std::string& msg, bool addon/* = false*/) { #ifdef BUILD_DEPRECATED_PLAYERBOT @@ -192,6 +197,23 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (!CheckChatMessage(msg)) return; +#ifdef ENABLE_PLAYERBOTS + if (GetSecurity() > SEC_PLAYER && GetPlayer()->IsGameMaster()) + { + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", TEAM_BOTH_ALLOWED, lang); + } + else + { + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); + } + + // apply to own bots + if (_player->GetPlayerbotMgr()) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg, lang); + } +#endif + if (type == CHAT_MSG_SAY) { GetPlayer()->Say(msg, lang); @@ -278,6 +300,17 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) } } +#ifdef ENABLE_PLAYERBOTS + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + + if (msg.find("BOT\t") != 0) //These are spoofed SendAddonMessage with channel "WHISPER". +#endif + GetPlayer()->Whisper(msg, lang, player->GetObjectGuid()); if (lang != LANG_ADDON && !m_anticheat->IsSilenced()) @@ -316,6 +349,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) ChatHandler::BuildChatPacket(data, ChatMsg(type), msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false, group->GetMemberGroup(GetPlayer()->GetObjectGuid())); +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + break; } case CHAT_MSG_GUILD: @@ -340,6 +386,23 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (Guild* guild = sGuildMgr.GetGuildById(GetPlayer()->GetGuildId())) guild->BroadcastToGuild(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); +#ifdef ENABLE_PLAYERBOTS + PlayerbotMgr* mgr = GetPlayer()->GetPlayerbotMgr(); + if (mgr && GetPlayer()->GetGuildId()) + { + for (PlayerBotMap::const_iterator it = mgr->GetPlayerBotsBegin(); it != mgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetGuildId() == GetPlayer()->GetGuildId()) + { + bot->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + } + } + } + + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); +#endif + break; } case CHAT_MSG_OFFICER: @@ -400,6 +463,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false); + +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; case CHAT_MSG_RAID_LEADER: { @@ -431,6 +508,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_LEADER, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false); + +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; case CHAT_MSG_RAID_WARNING: @@ -461,6 +552,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_WARNING, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, true); + +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; case CHAT_MSG_BATTLEGROUND: @@ -533,6 +638,24 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (lang != LANG_ADDON && (chn->HasFlag(Channel::ChannelFlags::CHANNEL_FLAG_GENERAL) || chn->IsStatic())) m_anticheat->Channel(msg); + +#ifdef ENABLE_PLAYERBOTS + // if GM apply to all random bots + if (GetSecurity() > SEC_PLAYER && GetPlayer()->IsGameMaster()) + { + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", TEAM_BOTH_ALLOWED, lang); + } + else + { + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); + } + + // apply to own bots + if (_player->GetPlayerbotMgr() && chn->GetFlags() & 0x18) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg, lang); + } +#endif } } } break; diff --git a/src/game/Entities/Camera.cpp b/src/game/Entities/Camera.cpp index 703f66d1b3..7bb04215a5 100644 --- a/src/game/Entities/Camera.cpp +++ b/src/game/Entities/Camera.cpp @@ -145,6 +145,11 @@ template void Camera::UpdateVisibilityOf(DynamicObject*, UpdateData&, WorldObjec void Camera::UpdateVisibilityForOwner(bool addToWorld) { +#ifdef ENABLE_PLAYERBOTS + if (!m_owner.isRealPlayer()) + return; +#endif + MaNGOS::VisibleNotifier notifier(*this); Cell::VisitAllObjects(m_source, notifier, addToWorld ? MAX_VISIBILITY_DISTANCE : m_source->GetVisibilityData().GetVisibilityDistance(), false); notifier.Notify(); diff --git a/src/game/Entities/CharacterHandler.cpp b/src/game/Entities/CharacterHandler.cpp index e3554b25b5..617efe4c62 100644 --- a/src/game/Entities/CharacterHandler.cpp +++ b/src/game/Entities/CharacterHandler.cpp @@ -44,6 +44,11 @@ #include "PlayerBot/Base/PlayerbotMgr.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/playerbot.h" +#include "playerbot/PlayerbotAIConfig.h" +#endif + // config option SkipCinematics supported values enum CinematicsSkipMode { @@ -65,6 +70,108 @@ class LoginQueryHolder : public SqlQueryHolder bool Initialize(); }; +#ifdef ENABLE_PLAYERBOTS +class PlayerbotLoginQueryHolder : public LoginQueryHolder +{ +private: + uint32 masterAccountId; + PlayerbotHolder* playerbotHolder; + +public: + PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, uint32 guid) + : LoginQueryHolder(accountId, ObjectGuid(HIGHGUID_PLAYER, guid)), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) { } + +public: + uint32 GetMasterAccountId() const { return masterAccountId; } + PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; } +}; + +void PlayerbotHolder::AddPlayerBot(uint32 playerGuid, uint32 masterAccount) +{ + // has bot already been added? + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, playerGuid); + Player* bot = sObjectMgr.GetPlayer(guid); + + if (bot && bot->IsInWorld()) + return; + + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (accountId == 0) + return; + + PlayerbotLoginQueryHolder* holder = new PlayerbotLoginQueryHolder(this, masterAccount, accountId, playerGuid); + if (!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + return; + } + + CharacterDatabase.DelayQueryHolder(this, &PlayerbotHolder::HandlePlayerBotLoginCallback, holder); +} + +void PlayerbotHolder::HandlePlayerBotLoginCallback(QueryResult* dummy, SqlQueryHolder* holder) +{ + if (!holder) + return; + + PlayerbotLoginQueryHolder* lqh = (PlayerbotLoginQueryHolder*)holder; + uint32 masterAccount = lqh->GetMasterAccountId(); + + WorldSession* masterSession = masterAccount ? sWorld.FindSession(masterAccount) : NULL; + uint32 botAccountId = lqh->GetAccountId(); + WorldSession* botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, 1, 0, LOCALE_enUS, "", 0, 0, false); + botSession->SetNoAnticheat(); + + // has bot already been added? + if (sObjectMgr.GetPlayer(lqh->GetGuid())) + return; + + uint32 guid = lqh->GetGuid().GetRawValue(); + + botSession->HandlePlayerLogin(lqh); // will delete lqh + + Player* bot = botSession->GetPlayer(); + if (!bot) + { + sLog.outError("Error logging in bot %d, please try to reset all random bots", guid); + return; + } + + bot->RemovePlayerbotMgr(); + + sRandomPlayerbotMgr.OnPlayerLogin(bot); + + bool allowed = false; + if (botAccountId == masterAccount) + { + allowed = true; + } + else if (masterSession && sPlayerbotAIConfig.allowGuildBots && bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId()) + { + allowed = true; + } + else if (sPlayerbotAIConfig.IsInRandomAccountList(botAccountId)) + { + allowed = true; + } + + if (allowed) + { + OnBotLogin(bot); + return; + } + + if (masterSession) + { + ChatHandler ch(masterSession); + ch.PSendSysMessage("You are not allowed to control bot %s", bot->GetName()); + } + + LogoutPlayerBot(bot->GetObjectGuid()); + sLog.outError("Attempt to add not allowed bot %s, please try to reset all random bots", bot->GetName()); +} +#endif + bool LoginQueryHolder::Initialize() { SetSize(MAX_PLAYER_LOGIN_QUERY); @@ -128,9 +235,30 @@ class CharacterHandler { if (!holder) return; +#ifdef ENABLE_PLAYERBOTS + WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); + if (!session) + { + delete holder; + return; + } + + ObjectGuid guid = ((LoginQueryHolder*)holder)->GetGuid(); + session->HandlePlayerLogin((LoginQueryHolder*)holder); + + Player* player = session->GetPlayer(); + if (player) + { + player->CreatePlayerbotMgr(); + player->GetPlayerbotMgr()->OnPlayerLogin(player); + sRandomPlayerbotMgr.OnPlayerLogin(player); + } +#else if (WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId())) session->HandlePlayerLogin((LoginQueryHolder*)holder); +#endif } + #ifdef BUILD_DEPRECATED_PLAYERBOT // This callback is different from the normal HandlePlayerLoginCallback in that it // sets up the bot's world session and also stores the pointer to the bot player in the master's @@ -501,6 +629,36 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data) return; } +#ifdef ENABLE_PLAYERBOTS + if (pCurrChar && pCurrChar->GetPlayerbotAI()) + { + WorldSession* botSession = pCurrChar->GetSession(); + SetPlayer(pCurrChar, playerGuid); + _player->SetSession(this); + _logoutTime = time(0); + + m_sessionDbcLocale = botSession->m_sessionDbcLocale; + m_sessionDbLocaleIndex = botSession->m_sessionDbLocaleIndex; + + PlayerbotMgr* mgr = _player->GetPlayerbotMgr(); + if (!mgr || mgr->GetMaster() != _player) + { + _player->RemovePlayerbotMgr(); + _player->CreatePlayerbotMgr(); + _player->GetPlayerbotMgr()->OnPlayerLogin(_player); + + if (sRandomPlayerbotMgr.GetPlayerBot(playerGuid)) + { + sRandomPlayerbotMgr.MovePlayerBot(playerGuid, _player->GetPlayerbotMgr()); + } + else + { + _player->GetPlayerbotMgr()->OnBotLogin(_player); + } + } + } +#endif + if (_player) { // player is reconnecting @@ -1241,4 +1399,4 @@ void WorldSession::HandleSetPlayerDeclinedNamesOpcode(WorldPacket& recv_data) SendPacket(data, true); sWorld.InvalidatePlayerDataToAllClient(guid); -} +} \ No newline at end of file diff --git a/src/game/Entities/EntitiesMgr.h b/src/game/Entities/EntitiesMgr.h index add9333db9..5e4888fc2f 100644 --- a/src/game/Entities/EntitiesMgr.h +++ b/src/game/Entities/EntitiesMgr.h @@ -45,6 +45,7 @@ typedef std::unordered_set WorldObjectUnSet; typedef std::list UnitList; typedef std::list CreatureList; typedef std::list GameObjectList; +typedef std::list DynamicObjectList; typedef std::list CorpseList; typedef std::list PlayerList; typedef std::unordered_set PlayerSet; diff --git a/src/game/Entities/Object.cpp b/src/game/Entities/Object.cpp index 3ca9218b4a..197e6ea706 100644 --- a/src/game/Entities/Object.cpp +++ b/src/game/Entities/Object.cpp @@ -2555,8 +2555,14 @@ struct WorldObjectChangeAccumulator { // send self fields changes in another way, otherwise // with new camera system when player's camera too far from player, camera wouldn't receive packets and changes from player - if (i_object.isType(TYPEMASK_PLAYER)) - i_object.BuildUpdateDataForPlayer((Player*)&i_object, i_updateDatas); + if (i_object.IsPlayer()) + { + Player* plr = static_cast(&i_object); +#ifdef ENABLE_PLAYERBOTS + if (plr->isRealPlayer()) +#endif + i_object.BuildUpdateDataForPlayer(plr, i_updateDatas); + } } void Visit(CameraMapType& m) @@ -2564,8 +2570,15 @@ struct WorldObjectChangeAccumulator for (auto& iter : m) { Player* owner = iter.getSource()->GetOwner(); +#ifdef ENABLE_PLAYERBOTS + if (owner->isRealPlayer()) + { +#endif if (owner != &i_object && owner->HasAtClient(&i_object)) i_object.BuildUpdateDataForPlayer(owner, i_updateDatas); +#ifdef ENABLE_PLAYERBOTS + } +#endif } } diff --git a/src/game/Entities/PetitionsHandler.cpp b/src/game/Entities/PetitionsHandler.cpp index 0ecd63625d..10cb401d13 100644 --- a/src/game/Entities/PetitionsHandler.cpp +++ b/src/game/Entities/PetitionsHandler.cpp @@ -517,6 +517,10 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recv_data) // client doesn't allow to sign petition two times by one character, but not check sign by another character from same account // not allow sign another player from already sign player account +#ifdef ENABLE_PLAYERBOTS + if (!_player->GetPlayerbotAI()) + { +#endif queryResult = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE player_account = '%u' AND petitionguid = '%u'", GetAccountId(), petitionLowGuid); if (queryResult) @@ -534,6 +538,9 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recv_data) owner->GetSession()->SendPacket(data); return; } +#ifdef ENABLE_PLAYERBOTS + } +#endif CharacterDatabase.PExecute("INSERT INTO petition_sign (ownerguid,petitionguid, playerguid, player_account) VALUES ('%u', '%u', '%u','%u')", ownerLowGuid, petitionLowGuid, _player->GetGUIDLow(), GetAccountId()); diff --git a/src/game/Entities/Player.cpp b/src/game/Entities/Player.cpp index cf7c43a309..7e409907fd 100644 --- a/src/game/Entities/Player.cpp +++ b/src/game/Entities/Player.cpp @@ -71,6 +71,11 @@ #include "Config/Config.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/playerbot.h" +#include "playerbot/PlayerbotAIConfig.h" +#endif + #include #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -477,9 +482,9 @@ void TradeData::SetAccepted(bool state, bool crosssend /*= false*/) Player::Player(WorldSession* session): Unit(), m_taxiTracker(*this), m_mover(this), m_camera(this), m_reputationMgr(this), m_launched(false) { -#ifdef BUILD_DEPRECATED_PLAYERBOT - m_playerbotAI = 0; - m_playerbotMgr = 0; +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) + m_playerbotAI = nullptr; + m_playerbotMgr = nullptr; #endif m_speakTime = 0; m_speakCount = 0; @@ -693,14 +698,20 @@ Player::~Player() if (m_playerbotAI) { delete m_playerbotAI; - m_playerbotAI = 0; + m_playerbotAI = nullptr; } if (m_playerbotMgr) { delete m_playerbotMgr; - m_playerbotMgr = 0; + m_playerbotMgr = nullptr; } #endif + +#ifdef ENABLE_PLAYERBOTS + RemovePlayerbotAI(); + RemovePlayerbotMgr(); +#endif + delete m_declinedname; } @@ -1565,6 +1576,9 @@ void Player::Update(const uint32 diff) { if (diff >= m_DetectInvTimer) { +#ifdef ENABLE_PLAYERBOTS + if (isRealPlayer()) +#endif HandleStealthedUnitsDetection(); m_DetectInvTimer = GetMap()->IsBattleGroundOrArena() ? 500 : 2000; } @@ -1634,6 +1648,43 @@ void Player::Heartbeat() SendUpdateToOutOfRangeGroupMembers(); } +#ifdef ENABLE_PLAYERBOTS +void Player::CreatePlayerbotAI() +{ + assert(!m_playerbotAI); + m_playerbotAI = std::make_unique(this); +} + +void Player::RemovePlayerbotAI() +{ + m_playerbotAI = nullptr; +} + +void Player::CreatePlayerbotMgr() +{ + assert(!m_playerbotMgr); + m_playerbotMgr = std::make_unique(this); +} + +void Player::RemovePlayerbotMgr() +{ + m_playerbotMgr = nullptr; +} + +void Player::UpdateAI(const uint32 diff, bool minimal) +{ + if (m_playerbotAI) + { + m_playerbotAI->UpdateAI(diff); + } + + if (m_playerbotMgr) + { + m_playerbotMgr->UpdateAI(diff); + } +} +#endif + void Player::SetDeathState(DeathState s) { uint32 ressSpellId = 0; @@ -8655,6 +8706,33 @@ Item* Player::GetItemByPos(uint8 bag, uint8 slot) const return nullptr; } +Item* Player::GetItemByEntry(uint32 item) const +{ + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetEntry() == item) + { + return pItem; + } + } + } + + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (Item* itemPtr = pBag->GetItemByEntry(item)) + { + return itemPtr; + } + } + } + + return nullptr; +} + uint32 Player::GetItemDisplayIdInSlot(uint8 bag, uint8 slot) const { const Item* pItem = GetItemByPos(bag, slot); @@ -13163,7 +13241,11 @@ void Player::AddQuest(Quest const* pQuest, Object* questGiver) questStatusData.uState = QUEST_CHANGED; // quest accept scripts +#ifdef ENABLE_PLAYERBOTS + if (questGiver && this != questGiver) +#else if (questGiver) +#endif { switch (questGiver->GetTypeId()) { @@ -13370,6 +13452,10 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, bool handled = false; +#ifdef ENABLE_PLAYERBOTS + if (this != questGiver) + { +#endif switch (questGiver->GetTypeId()) { case TYPEID_UNIT: @@ -13379,9 +13465,17 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, handled = sScriptDevAIMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest); break; } +#ifdef ENABLE_PLAYERBOTS + } + if (this != questGiver) + { +#endif if (!handled && pQuest->GetQuestCompleteScript() != 0) GetMap()->ScriptsStart(SCRIPT_TYPE_QUEST_END, pQuest->GetQuestCompleteScript(), questGiver, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); +#ifdef ENABLE_PLAYERBOTS + } +#endif // Find spell cast on spell reward if any, then find the appropriate caster and cast it uint32 spellId = pQuest->GetRewSpellCast(); @@ -16949,10 +17043,17 @@ void Player::_SaveInventory() } // update enchantment durations +#ifdef ENABLE_PLAYERBOTS + if (!GetPlayerbotAI()) + { +#endif for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr) { itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration); } +#ifdef ENABLE_PLAYERBOTS + } +#endif // if no changes if (m_itemUpdateQueue.empty()) return; @@ -20984,6 +21085,164 @@ void Player::learnSpellHighRank(uint32 spellid) sSpellMgr.doForHighRanks(spellid, worker); } +#ifdef ENABLE_PLAYERBOTS +void Player::learnClassLevelSpells(bool includeHighLevelQuestRewards) +{ + ChrClassesEntry const* clsEntry = sChrClassesStore.LookupEntry(getClass()); + if (!clsEntry) + return; + + uint32 family = clsEntry->spellfamily; + + // special cases which aren't sourced from trainers and normally require quests to obtain - added here for convenience + ObjectMgr::QuestMap const& qTemplates = sObjectMgr.GetQuestTemplates(); + for (const auto& qTemplate : qTemplates) + { + Quest const* quest = qTemplate.second; + if (!quest) + continue; + + // only class quests player could do + if (quest->GetRequiredClasses() == 0 || !SatisfyQuestClass(quest, false) || !SatisfyQuestRace(quest, false) || !SatisfyQuestLevel(quest, false)) + continue; + + // custom filter for scripting purposes + if (!includeHighLevelQuestRewards && quest->GetMinLevel() >= 60) + continue; + + learnQuestRewardedSpells(quest); + } + + // learn trainer spells + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co) + continue; + + if (co->TrainerType != TRAINER_TYPE_CLASS) + continue; + + if (co->TrainerType == TRAINER_TYPE_CLASS && co->TrainerClass != getClass()) + continue; + + uint32 trainerId = co->TrainerTemplateId; + if (!trainerId) + trainerId = co->Entry; + + TrainerSpellData const* trainer_spells = sObjectMgr.GetNpcTrainerTemplateSpells(trainerId); + if (!trainer_spells) + trainer_spells = sObjectMgr.GetNpcTrainerSpells(trainerId); + + if (!trainer_spells) + continue; + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell) + continue; + + uint32 reqLevel = 0; + + // skip wrong class/race skills + if (!IsSpellFitByClassAndRace(tSpell->learnedSpell, &reqLevel)) + continue; + + if (tSpell->conditionId && !sObjectMgr.IsConditionSatisfied(tSpell->conditionId, this, GetMap(), this, CONDITION_FROM_TRAINER)) + continue; + + // skip spells with first rank learned as talent (and all talents then also) + uint32 first_rank = sSpellMgr.GetFirstSpellInChain(tSpell->learnedSpell); + reqLevel = tSpell->isProvidedReqLevel ? tSpell->reqLevel : std::max(reqLevel, tSpell->reqLevel); + bool isValidTalent = GetTalentSpellCost(first_rank) && HasSpell(first_rank) && reqLevel <= GetLevel(); + + TrainerSpellState state = GetTrainerSpellState(tSpell, reqLevel); + if (state != TRAINER_SPELL_GREEN && !isValidTalent) + continue; + + SpellEntry const* proto = sSpellTemplate.LookupEntry(tSpell->learnedSpell); + if (!proto) + continue; + + // fix activate state for non-stackable low rank (and find next spell for !active case) + if (uint32 nextId = sSpellMgr.GetSpellBookSuccessorSpellId(proto->Id)) + { + if (HasSpell(nextId)) + { + // high rank already known so this must !active + continue; + } + } + + // skip other spell families (minus a few exceptions) + if (proto->SpellFamilyName != family) + { + SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBoundsBySpellId(tSpell->learnedSpell); + if (bounds.first == bounds.second) + continue; + + SkillLineAbilityEntry const* skillInfo = bounds.first->second; + if (!skillInfo) + continue; + + switch (skillInfo->skillId) + { + case SKILL_SUBTLETY: + case SKILL_BEAST_MASTERY: + case SKILL_SURVIVAL: + case SKILL_DEFENSE: + case SKILL_DUAL_WIELD: + case SKILL_FERAL_COMBAT: + case SKILL_PROTECTION: + case SKILL_PLATE_MAIL: + case SKILL_DEMONOLOGY: + case SKILL_ENHANCEMENT: + case SKILL_MAIL: + case SKILL_HOLY2: + case SKILL_LOCKPICKING: + break; + + default: continue; + } + } + + // skip wrong class/race skills + if (!IsSpellFitByClassAndRace(tSpell->learnedSpell)) + continue; + + // skip broken spells + if (!SpellMgr::IsSpellValid(proto, this, false)) + continue; + + if (tSpell->learnedSpell) + { + bool learned = false; + for (int j = 0; j < 3; ++j) + { + if (proto->Effect[j] == SPELL_EFFECT_LEARN_SPELL) + { + uint32 learnedSpell = proto->EffectTriggerSpell[j]; + learnSpell(learnedSpell, false); + learned = true; + } + } + + if (!learned) + { + learnSpell(tSpell->learnedSpell, false); + } + } + else + { + CastSpell(this, tSpell->spell, TRIGGERED_OLD_TRIGGERED); + } + } + } +} +#endif + void Player::_LoadSkills(std::unique_ptr queryResult) { // 0 1 2 @@ -21571,6 +21830,11 @@ AreaLockStatus Player::GetAreaTriggerLockStatus(AreaTrigger const* at, uint32& m if (!GetGroup() || !GetGroup()->IsRaidGroup()) return AREA_LOCKSTATUS_RAID_LOCKED; +#ifdef ENABLE_PLAYERBOTS + if (!isRealPlayer() && GetPlayerbotAI() && GetPlayerbotAI()->CanEnterArea(at)) + return AREA_LOCKSTATUS_OK; +#endif + // Item Requirements: must have requiredItem OR requiredItem2, report the first one that's missing if (at->requiredItem) { diff --git a/src/game/Entities/Player.h b/src/game/Entities/Player.h index dde797eaed..f69ecece96 100644 --- a/src/game/Entities/Player.h +++ b/src/game/Entities/Player.h @@ -64,6 +64,11 @@ struct FactionTemplateEntry; #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +class PlayerbotAI; +class PlayerbotMgr; +#endif + struct AreaTrigger; typedef std::deque PlayerMails; @@ -1136,6 +1141,7 @@ class Player : public Unit Item* GetItemByGuid(ObjectGuid guid) const; Item* GetItemByPos(uint16 pos) const; Item* GetItemByPos(uint8 bag, uint8 slot) const; + Item* GetItemByEntry(uint32 item) const; uint32 GetItemDisplayIdInSlot(uint8 bag, uint8 slot) const; Item* GetWeaponForAttack(WeaponAttackType attackType) const { return GetWeaponForAttack(attackType, false, false); } Item* GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const; @@ -1558,6 +1564,10 @@ class Player : public Unit void learnQuestRewardedSpells(Quest const* quest); void learnSpellHighRank(uint32 spellid); +#ifdef ENABLE_PLAYERBOTS + void learnClassLevelSpells(bool includeHighLevelQuestRewards = false); +#endif + uint32 GetFreeTalentPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS1); } void SetFreeTalentPoints(uint32 points) { SetUInt32Value(PLAYER_CHARACTER_POINTS1, points); } void UpdateFreeTalentPoints(bool resetIfNeed = true); @@ -2264,6 +2274,20 @@ class Player : public Unit bool IsInDuel() const { return duel && duel->startTime != 0; } #endif +#ifdef ENABLE_PLAYERBOTS + // A Player can either have a playerbotMgr (to manage its bots), or have playerbotAI (if it is a bot), or + // neither. Code that enables bots must create the playerbotMgr and set it using SetPlayerbotMgr. + void UpdateAI(const uint32 diff, bool minimal = false); + void CreatePlayerbotAI(); + void RemovePlayerbotAI(); + PlayerbotAI* GetPlayerbotAI() { return m_playerbotAI.get(); } + void CreatePlayerbotMgr(); + void RemovePlayerbotMgr(); + PlayerbotMgr* GetPlayerbotMgr() { return m_playerbotMgr.get(); } + void SetBotDeathTimer() { m_deathTimer = 0; } + bool isRealPlayer() { return m_session && (m_session->GetRemoteAddress() != "disconnected/bot"); } +#endif + virtual UnitAI* AI() override { if (m_charmInfo) return m_charmInfo->GetAI(); return nullptr; } virtual CombatData* GetCombatData() override { if (m_charmInfo && m_charmInfo->GetCombatData()) return m_charmInfo->GetCombatData(); return m_combatData; } virtual CombatData const* GetCombatData() const override { if (m_charmInfo && m_charmInfo->GetCombatData()) return m_charmInfo->GetCombatData(); return m_combatData; } @@ -2581,6 +2605,11 @@ class Player : public Unit PlayerbotMgr* m_playerbotMgr; #endif +#ifdef ENABLE_PLAYERBOTS + std::unique_ptr m_playerbotAI; + std::unique_ptr m_playerbotMgr; +#endif + // Homebind coordinates uint32 m_homebindMapId; uint16 m_homebindAreaId; diff --git a/src/game/Grids/GridNotifiers.cpp b/src/game/Grids/GridNotifiers.cpp index 74aa653162..b4a0a497b5 100644 --- a/src/game/Grids/GridNotifiers.cpp +++ b/src/game/Grids/GridNotifiers.cpp @@ -41,6 +41,11 @@ void VisibleChangesNotifier::Visit(CameraMapType& m) void VisibleNotifier::Notify() { Player& player = *i_camera.GetOwner(); +#ifdef ENABLE_PLAYERBOTS + if (!player.isRealPlayer()) + return; +#endif + // at this moment i_clientGUIDs have guids that not iterate at grid level checks // but exist one case when this possible and object not out of range: transports if (GenericTransport* transport = player.GetTransport()) diff --git a/src/game/Grids/GridNotifiers.h b/src/game/Grids/GridNotifiers.h index 68e5c4d1c9..36485d544e 100644 --- a/src/game/Grids/GridNotifiers.h +++ b/src/game/Grids/GridNotifiers.h @@ -315,6 +315,19 @@ namespace MaNGOS template void Visit(GridRefManager&) {} }; + template + struct DynamicObjectListSearcher + { + DynamicObjectList& i_objects; + Check& i_check; + + DynamicObjectListSearcher(DynamicObjectList& objects, Check& check) : i_objects(objects), i_check(check) {} + + void Visit(DynamicObjectMapType& m); + + template void Visit(GridRefManager&) {} + }; + // Unit searchers // First accepted by Check Unit if any diff --git a/src/game/Grids/GridNotifiersImpl.h b/src/game/Grids/GridNotifiersImpl.h index 6bce939cae..749daee407 100644 --- a/src/game/Grids/GridNotifiersImpl.h +++ b/src/game/Grids/GridNotifiersImpl.h @@ -495,6 +495,16 @@ void MaNGOS::GameObjectListSearcher::Visit(GameObjectMapType& m) i_objects.push_back(itr->getSource()); } +// Dynamicobject searchers + +template +void MaNGOS::DynamicObjectListSearcher::Visit(DynamicObjectMapType& m) +{ + for (DynamicObjectMapType::iterator itr = m.begin(); itr != m.end(); ++itr) + if (i_check(itr->getSource())) + i_objects.push_back(itr->getSource()); +} + // Unit searchers template diff --git a/src/game/Groups/Group.h b/src/game/Groups/Group.h index 7bb035696f..5d7705d878 100644 --- a/src/game/Groups/Group.h +++ b/src/game/Groups/Group.h @@ -275,6 +275,8 @@ class Group InstanceGroupBind* GetBoundInstance(Map* aMap, Difficulty difficulty); BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } + ObjectGuid GetTargetIcon(int index) { return m_targetIcons[index]; } + protected: bool _addMember(ObjectGuid guid, const char* name, bool isAssistant = false); bool _addMember(ObjectGuid guid, const char* name, bool isAssistant, uint8 group); diff --git a/src/game/Maps/Map.cpp b/src/game/Maps/Map.cpp index c71a5e71fa..00ba9ec272 100644 --- a/src/game/Maps/Map.cpp +++ b/src/game/Maps/Map.cpp @@ -44,6 +44,10 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/playerbot.h" +#endif + #include Map::~Map() @@ -161,6 +165,9 @@ Map::Map(uint32 id, time_t expiry, uint32 InstanceId, uint8 SpawnMode) m_activeNonPlayersIter(m_activeNonPlayers.end()), m_onEventNotifiedIter(m_onEventNotifiedObjects.end()), i_gridExpiry(expiry), m_TerrainData(sTerrainMgr.LoadTerrain(id)), i_data(nullptr), i_script_id(0), m_transportsIterator(m_transports.begin()), m_spawnManager(*this), +#ifdef ENABLE_PLAYERBOTS + m_activeZonesTimer(0), hasRealPlayers(false), +#endif m_variableManager(this), m_defaultLight(GetDefaultMapLight(id)) { m_weatherSystem = new WeatherSystem(this); @@ -671,7 +678,6 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor meas("map.update", { { "map_id", std::to_string(i_id) }, @@ -738,20 +744,159 @@ void Map::Update(const uint32& t_diff) #endif } +#ifdef ENABLE_PLAYERBOTS + // Calculate the active zones every 10 seconds (An active zone is a zone where one or more real players are) + constexpr uint32 maxActiveZonesTimer = 10000U; + if (m_activeZonesTimer < maxActiveZonesTimer) + { + m_activeZonesTimer += t_diff; + } + else + { + m_activeZonesTimer = 0U; + m_activeZones.clear(); + + // Recalculate active zones + if (IsContinent() && HasRealPlayers()) + { + for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) + { + Player* plr = m_mapRefIter->getSource(); + if (plr && plr->IsInWorld()) + { + // Only consider real players + if (plr->GetPlayerbotAI() && !plr->GetPlayerbotAI()->IsRealPlayer()) + continue; + + // Ignore afk players + if (plr->isAFK()) + continue; + + // Ignore gm players + if (!plr->isGMVisible()) + continue; + + // Register an active zone when a real player is on the zone + if (find(m_activeZones.begin(), m_activeZones.end(), plr->GetZoneId()) == m_activeZones.end()) + { + m_activeZones.push_back(plr->GetZoneId()); + } + } + } + } + } + + // Reset the has real players flag and check for it again + const bool hadRealPlayers = hasRealPlayers; + hasRealPlayers = false; + + uint32 activePlayers = 0; + uint32 avgDiff = sWorld.GetAverageDiff(); + + // Calculate the chance that the bots in this map should update based on server load and real players online + // (default is a 10% on a avg diff of 100) + float botUpdateChance = avgDiff * 0.1f; + if (!hadRealPlayers) + { + // If no real players are on the map then lower the chances of updating by 300% + botUpdateChance *= 3.0f; + } + + bool shouldUpdateBots = urand(0, (uint32)(botUpdateChance * 100)) < 100; +#endif + /// update players at tick for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { Player* plr = m_mapRefIter->getSource(); if (plr && plr->IsInWorld()) + { +#ifdef ENABLE_PLAYERBOTS + // Determine if the individual bot should update + bool shouldUpdateBot = shouldUpdateBots; + + // Real players should update always (it will update alt bots) + if (!plr->GetPlayerbotAI() || plr->GetPlayerbotAI()->IsRealPlayer()) + { + shouldUpdateBot = true; + hasRealPlayers = true; + } + else + { + // If there are real players in the map, check if the bot is on a zone with players + if (hadRealPlayers) + { + // Check if the bot is in an active zone (or instance) + shouldUpdateBot = IsContinent() ? HasActiveZone(plr->GetZoneId()) : true; + } + + // Check for edge case reasons to force update the bot + if (!shouldUpdateBot) + { + // Force bots to be active if: + // - The bot is playing with a real player + // - The bot is in a battleground + // - The bot is in combat + if ((plr->GetPlayerbotAI() && plr->GetPlayerbotAI()->HasRealPlayerMaster()) || + plr->InBattleGroundQueue() || plr->InBattleGround() || + plr->IsInCombat()) + { + shouldUpdateBot = true; + } + } + } + + // Save the active characters for later logs + if (shouldUpdateBot) + { + activePlayers++; + } +#endif plr->Update(t_diff); + +#ifdef ENABLE_PLAYERBOTS + plr->UpdateAI(t_diff, !shouldUpdateBot); +#endif + } } +#ifdef ENABLE_PLAYERBOTS + // Log the active zones and characters + if (IsContinent() && HasRealPlayers() && HasActiveZones() && m_activeZonesTimer == 0U) + { + sLog.outBasic("Map %u: Active Zones - %u", GetId(), m_activeZones.size()); + sLog.outBasic("Map %u: Active Zone Players - %u of %u", GetId(), activePlayers, m_mapRefManager.getSize()); + } +#endif + for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { Player* player = m_mapRefIter->getSource(); - if (!player->IsInWorld() || !player->IsPositionValid()) + if (!player || !player->IsInWorld() || !player->IsPositionValid()) continue; +#ifdef ENABLE_PLAYERBOTS + // For non-players only load the grid + if (!player->isRealPlayer()) + { + CellPair center = MaNGOS::ComputeCellPair(player->GetPositionX(), player->GetPositionY()).normalize(); + uint32 cell_id = (center.y_coord * TOTAL_NUMBER_OF_CELLS_PER_MAP) + center.x_coord; + + if (!isCellMarked(cell_id)) + { + Cell cell(center); + const uint32 x = cell.GridX(); + const uint32 y = cell.GridY(); + if (!cell.NoCreate() || loaded(GridPair(x, y))) + { + EnsureGridLoaded(player->GetCurrentCell()); + } + } + + continue; + } +#endif + VisitNearbyCellsOf(player, grid_object_update, world_object_update); // If player is using far sight, visit that object too @@ -759,6 +904,19 @@ void Map::Update(const uint32& t_diff) VisitNearbyCellsOf(viewPoint, grid_object_update, world_object_update); } +#ifdef ENABLE_PLAYERBOTS + // Calculate the chance that the objects (non players) should update based on server load and real players online + // (default is a 10% on a avg diff of 100) + float objectUpdateChance = avgDiff * 0.1f; + if (!HasRealPlayers()) + { + // If no real players are on the map then lower the chances of updating by 300% + objectUpdateChance *= 3.0f; + } + + const bool shouldUpdateObjects = urand(0, (uint32)(objectUpdateChance * 100)) < 100; +#endif + // non-player active objects if (!m_activeNonPlayers.empty()) { @@ -774,6 +932,18 @@ void Map::Update(const uint32& t_diff) if (!obj->IsInWorld() || !obj->IsPositionValid()) continue; +#ifdef ENABLE_PLAYERBOTS + // Skip objects on locations away from real players if world is laggy + if (IsContinent() && avgDiff > 100) + { + const bool isInActiveZone = IsContinent() ? HasActiveZone(obj->GetZoneId()) : HasRealPlayers(); + if (!isInActiveZone && !shouldUpdateObjects) + { + continue; + } + } +#endif + objToUpdate.insert(obj); // lets update mobs/objects in ALL visible cells around player! @@ -1237,8 +1407,15 @@ void Map::UpdateObjectVisibility(WorldObject* obj, Cell cell, const CellPair& ce TypeContainerVisitor player_notifier(notifier); cell.Visit(cellpair, player_notifier, *this, *obj, obj->GetVisibilityData().GetVisibilityDistance()); for (auto guid : notifier.GetUnvisitedGuids()) + { if (Player* player = GetPlayer(guid)) + { +#ifdef ENABLE_PLAYERBOTS + if (player->isRealPlayer()) +#endif player->UpdateVisibilityOf(player->GetCamera().GetBody(), obj); + } + } } void Map::SendInitSelf(Player* player) const diff --git a/src/game/Maps/Map.h b/src/game/Maps/Map.h index 5a2f9d58d7..dce640727d 100644 --- a/src/game/Maps/Map.h +++ b/src/game/Maps/Map.h @@ -420,6 +420,12 @@ class Map : public GridRefManager // debug std::set m_objRemoveList; // this will eventually eat up too much memory - only used for debugging VisibleNotifier::Notify() customlog leak +#ifdef ENABLE_PLAYERBOTS + bool HasRealPlayers() { return hasRealPlayers; } + bool HasActiveZones() { return !m_activeZones.empty(); } + bool HasActiveZone(uint32 zoneId) { return find(m_activeZones.begin(), m_activeZones.end(), zoneId) != m_activeZones.end(); } +#endif + private: void LoadMapAndVMap(int gx, int gy); @@ -548,6 +554,12 @@ class Map : public GridRefManager WorldStateVariableManager m_variableManager; +#ifdef ENABLE_PLAYERBOTS + std::vector m_activeZones; + uint32 m_activeZonesTimer; + bool hasRealPlayers; +#endif + ZoneDynamicInfoMap m_zoneDynamicInfo; ZoneDynamicInfoMap m_areaDynamicInfo; uint32 m_defaultLight; diff --git a/src/game/MotionGenerators/MotionMaster.cpp b/src/game/MotionGenerators/MotionMaster.cpp index 0fdb13b4f6..9e26f7d235 100644 --- a/src/game/MotionGenerators/MotionMaster.cpp +++ b/src/game/MotionGenerators/MotionMaster.cpp @@ -417,12 +417,12 @@ void MotionMaster::MovePointTOL(uint32 id, float x, float y, float z, bool takeO Mutate(new PointTOLMovementGenerator(id, x, y, z, takeOff, forcedMovement)); } -void MotionMaster::MovePath(std::vector& path, ForcedMovement forcedMovement, bool flying) +void MotionMaster::MovePath(std::vector& path, ForcedMovement forcedMovement, bool flying, bool cyclic) { - return MovePath(path, 0, forcedMovement, flying); + return MovePath(path, 0, forcedMovement, flying, cyclic); } -void MotionMaster::MovePath(std::vector& path, float o, ForcedMovement forcedMovement, bool flying) +void MotionMaster::MovePath(std::vector& path, float o, ForcedMovement forcedMovement, bool flying, bool cyclic) { if (path.empty()) return; @@ -435,7 +435,7 @@ void MotionMaster::MovePath(std::vector& path, float o, ForcedMove else DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "%s follows a pre-calculated path to X: %f Y: %f Z: %f", m_owner->GetGuidStr().c_str(), x, y, z); - Mutate(new FixedPathMovementGenerator(path, o, forcedMovement, flying)); + Mutate(new FixedPathMovementGenerator(path, o, forcedMovement, flying, 0.0f, 0, cyclic)); } void MotionMaster::MovePath(int32 pathId, WaypointPathOrigin wpOrigin /*= PATH_NO_PATH*/, ForcedMovement forcedMovement, bool flying, float speed, bool cyclic, ObjectGuid guid/* = ObjectGuid()*/) diff --git a/src/game/MotionGenerators/MotionMaster.h b/src/game/MotionGenerators/MotionMaster.h index 8de8c87484..584c1897b1 100644 --- a/src/game/MotionGenerators/MotionMaster.h +++ b/src/game/MotionGenerators/MotionMaster.h @@ -145,8 +145,8 @@ class MotionMaster : private std::stack void MovePoint(uint32 id, Position const& position, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, bool generatePath = true, ObjectGuid guid = ObjectGuid(), uint32 relayId = 0); void MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool generatePath = true); void MovePointTOL(uint32 id, float x, float y, float z, bool takeOff, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE); - void MovePath(std::vector& path, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false); - void MovePath(std::vector& path, float o, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false); + void MovePath(std::vector& path, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, bool cyclic = true); + void MovePath(std::vector& path, float o, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, bool cyclic = true); // MovePath can not change speed or flying mid path due to how it works - if you wish to do that, split it into two paths void MovePath(int32 pathId = 0, WaypointPathOrigin wpOrigin = PATH_NO_PATH, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, float speed = 0.f, bool cyclic = false, ObjectGuid guid = ObjectGuid()); void MoveRetreat(float x, float y, float z, float o, uint32 delay); diff --git a/src/game/MotionGenerators/MovementHandler.cpp b/src/game/MotionGenerators/MovementHandler.cpp index aae1157e44..9ed3c8de73 100644 --- a/src/game/MotionGenerators/MovementHandler.cpp +++ b/src/game/MotionGenerators/MovementHandler.cpp @@ -290,12 +290,28 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) WorldLocation const& dest = plMover->GetTeleportDest(); +#ifdef ENABLE_PLAYERBOTS + // send MSG_MOVE_TELEPORT to observers around old position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); + + // interrupt moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) + { + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Interrupt(*plMover); + } +#endif + plMover->SetDelayedZoneUpdate(false, 0); plMover->SetPosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation, true); plMover->SetFallInformation(0, dest.coord_z); +#ifdef ENABLE_PLAYERBOTS + plMover->m_movementInfo.ChangePosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); +#endif + GenericTransport* currentTransport = nullptr; if (plMover->m_teleportTransport) currentTransport = plMover->GetMap()->GetTransport(plMover->m_teleportTransport); @@ -305,6 +321,26 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) uint32 newzone, newarea; plMover->GetZoneAndAreaId(newzone, newarea); + +#ifdef ENABLE_PLAYERBOTS + // new zone + if (old_zone != newzone) + plMover->UpdateZone(newzone, newarea); + + // honorless target + if (plMover->pvpInfo.inPvPEnforcedArea) + plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); + + // reset moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) + { + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Reset(*plMover); + } + + // send MSG_MOVE_TELEPORT to observers around new position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); +#else plMover->UpdateZone(newzone, newarea); // new zone @@ -314,6 +350,7 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) if (plMover->pvpInfo.inPvPEnforcedArea) plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); } +#endif m_anticheat->Teleport({ dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation }); @@ -549,6 +586,19 @@ void WorldSession::SendKnockBack(Unit* who, float angle, float horizontalSpeed, m_anticheat->KnockBack(horizontalSpeed, -verticalSpeed, vcos, vsin); } +#ifdef ENABLE_PLAYERBOTS +void WorldSession::SendTeleportToObservers(float x, float y, float z, float orientation) +{ + WorldPacket data(MSG_MOVE_TELEPORT, 64); + data << _player->GetPackGUID(); + // copy move info to change position to where player is teleporting + MovementInfo moveInfo = _player->m_movementInfo; + moveInfo.ChangePosition(x, y, z, orientation); + data << moveInfo; + _player->SendMessageToSetExcept(data, _player); +} +#endif + void WorldSession::HandleMoveFlagChangeOpcode(WorldPacket& recv_data) { DEBUG_LOG("%s", recv_data.GetOpcodeName()); diff --git a/src/game/MotionGenerators/PathFinder.cpp b/src/game/MotionGenerators/PathFinder.cpp index 79a5134765..9d071ea905 100644 --- a/src/game/MotionGenerators/PathFinder.cpp +++ b/src/game/MotionGenerators/PathFinder.cpp @@ -50,6 +50,26 @@ PathFinder::PathFinder(const Unit* owner, bool ignoreNormalization) : createFilter(); } +#ifdef ENABLE_PLAYERBOTS +PathFinder::PathFinder() : + m_polyLength(0), m_type(PATHFIND_BLANK), + m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths + m_sourceUnit(nullptr), m_navMesh(nullptr), m_navMeshQuery(nullptr), m_cachedPoints(m_pointPathLimit* VERTEX_SIZE), m_pathPolyRefs(m_pointPathLimit), m_smoothPathPolyRefs(m_pointPathLimit), m_defaultMapId(0) +{ + +} + +PathFinder::PathFinder(uint32 mapId, uint32 instanceId) : + m_polyLength(0), m_type(PATHFIND_BLANK), + m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths + m_sourceUnit(nullptr), m_navMesh(nullptr), m_navMeshQuery(nullptr), m_cachedPoints(m_pointPathLimit* VERTEX_SIZE), m_pathPolyRefs(m_pointPathLimit), m_smoothPathPolyRefs(m_pointPathLimit), m_defaultMapId(mapId) +{ + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + m_defaultNavMeshQuery = mmap->GetNavMeshQuery(mapId, instanceId); + createFilter(); +} +#endif + PathFinder::~PathFinder() { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); @@ -57,7 +77,7 @@ PathFinder::~PathFinder() void PathFinder::SetCurrentNavMesh() { - if (MMAP::MMapFactory::IsPathfindingEnabled(m_sourceUnit->GetMapId(), m_sourceUnit)) + if (m_sourceUnit && MMAP::MMapFactory::IsPathfindingEnabled(m_sourceUnit->GetMapId(), m_sourceUnit)) { MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); if (GenericTransport* transport = m_sourceUnit->GetTransport()) @@ -70,6 +90,17 @@ void PathFinder::SetCurrentNavMesh() m_navMeshQuery = m_defaultNavMeshQuery; } +#ifdef ENABLE_PLAYERBOTS + if (m_navMeshQuery) + m_navMesh = m_navMeshQuery->getAttachedNavMesh(); + + } + else if (!m_sourceUnit && MMAP::MMapFactory::IsPathfindingEnabled(m_defaultMapId, nullptr)) + { + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + m_navMeshQuery = m_defaultNavMeshQuery; +#endif + if (m_navMeshQuery) m_navMesh = m_navMeshQuery->getAttachedNavMesh(); } @@ -93,6 +124,9 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force if (!MaNGOS::IsValidMapCoord(start.x, start.y, start.z)) return false; + if (!m_sourceUnit) + return false; + #ifdef BUILD_METRICS metric::duration meas("pathfinder.calculate", { { "entry", std::to_string(m_sourceUnit->GetEntry()) }, @@ -133,6 +167,130 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force return true; } +#ifdef ENABLE_PLAYERBOTS +void PathFinder::setArea(uint32 mapId, float x, float y, float z, uint32 area, float range) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + + uint16 includeFlags = 0; + uint16 excludeFlags = 0; + + + includeFlags |= (NAV_GROUND | NAV_WATER); + excludeFlags |= (NAV_MAGMA_SLIME | NAV_GROUND_STEEP); + + + m_filter.setIncludeFlags(includeFlags); + m_filter.setExcludeFlags(excludeFlags); + + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + //unsigned int dtResult = INVALID_POLYREF; + //m_navMeshQuery->getNodePool(); + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + static const int MAX_POLYS = 2560; + dtPolyRef m_polys[MAX_POLYS]; + dtPolyRef m_parent[MAX_POLYS]; + int m_npolys; + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return; + + query->findPolysAroundCircle(polyRef, closestPoint, range, &m_filter, m_polys, m_parent, 0, &m_npolys, MAX_POLYS); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return; + + for (int i = 0; i < m_npolys; i++) + { + unsigned char curArea; + dtStatus status = navMesh->getPolyArea(m_polys[i], &curArea); + + if (curArea != 8 && curArea < area) + dtStatus status = navMesh->setPolyArea(m_polys[i], area); + } +} + +uint32 PathFinder::getArea(uint32 mapId, float x, float y, float z) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return 99; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return 99; + + unsigned char area; + + dtStatus status = navMesh->getPolyArea(polyRef, &area); + + return area; +} + +unsigned short PathFinder::getFlags(uint32 mapId, float x, float y, float z) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return 0; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return 0; + + unsigned short flags; + + dtStatus status = navMesh->getPolyFlags(polyRef, &flags); + + return flags; +} + + +void PathFinder::setAreaCost(uint32 area, float cost) +{ + m_filter.setAreaCost(area, cost); +} +#endif + dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef* polyPath, uint32 polyPathSize, const float* point, float* distance, const float maxDist) const { if (!polyPath || !polyPathSize) @@ -215,6 +373,9 @@ dtPolyRef PathFinder::getPolyByLocation(const float* point, float* distance) void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) { + if (!m_sourceUnit) + return; + // *** getting start/end poly logic *** if (m_sourceUnit->GetMap()->IsDungeon()) { @@ -281,6 +442,14 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) buildShotrcut = true; } +#ifdef ENABLE_PLAYERBOTS + if (m_sourceUnit && m_sourceUnit->IsPlayer() && IsPointHigherThan(getActualEndPosition(), getStartPosition())) + { + sLog.outDebug("%s (%u) Path Shortcut skipped: endPoint is higher", m_sourceUnit->GetName(), m_sourceUnit->GetGUIDLow()); + buildShotrcut = false; + } +#endif + if (buildShotrcut) { BuildShortcut(); @@ -477,7 +646,11 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) &m_filter, // polygon search filter m_pathPolyRefs.data(), // [out] path (int*)&m_polyLength, - m_pointPathLimit); // max number of polygons in output path +#ifdef ENABLE_PLAYERBOTS + m_pointPathLimit / 2); +#else + m_pointPathLimit); // max number of polygons in output path +#endif } else { @@ -493,7 +666,11 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) hitNormal, m_pathPolyRefs.data(), (int*)&m_polyLength, +#ifdef ENABLE_PLAYERBOTS + m_pointPathLimit / 2); +#else m_pointPathLimit); +#endif // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != FLT_MAX) @@ -543,6 +720,15 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) { // only happens if we passed bad data to findPath(), or navmesh is messed up sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); + +#ifdef ENABLE_PLAYERBOTS + if (m_sourceUnit && m_sourceUnit->IsPlayer() && IsPointHigherThan(getActualEndPosition(), getStartPosition())) + { + sLog.outDebug("%s (%u) Path Shortcut skipped: endPoint is higher", m_sourceUnit->GetName(), m_sourceUnit->GetGUIDLow()); + return; + } +#endif + BuildShortcut(); m_type = PATHFIND_NOPATH; return; @@ -753,7 +939,7 @@ void PathFinder::BuildPointPath(const float* startPoint, const float* endPoint) void PathFinder::NormalizePath() { - if (!sWorld.getConfig(CONFIG_BOOL_PATH_FIND_NORMALIZE_Z) || m_ignoreNormalization) + if (!sWorld.getConfig(CONFIG_BOOL_PATH_FIND_NORMALIZE_Z) || m_ignoreNormalization || !m_sourceUnit) return; GenericTransport* transport = m_sourceUnit->GetTransport(); @@ -786,11 +972,48 @@ void PathFinder::BuildShortcut() m_type = PATHFIND_SHORTCUT; } +#ifdef ENABLE_PLAYERBOTS +bool PathFinder::IsPointHigherThan(const Vector3& posOne, const Vector3& posTwo) +{ + return posOne.z > posTwo.z; +} +#endif + void PathFinder::createFilter() { uint16 includeFlags = 0; uint16 excludeFlags = 0; +#ifdef ENABLE_PLAYERBOTS + if (!m_sourceUnit || m_sourceUnit->GetTypeId() == TYPEID_PLAYER) + { + // perfect support not possible, just stay 'safe' + if (!m_sourceUnit || ((Player*)m_sourceUnit)->GetPlayerbotAI()) //Blank or bot-navigation + { + includeFlags |= (NAV_GROUND | NAV_WATER); + excludeFlags |= (NAV_MAGMA_SLIME | NAV_GROUND_STEEP); + + m_filter.setAreaCost(9, 20.0f); //Water + m_filter.setAreaCost(12, 5.0f); //Mob proximity + m_filter.setAreaCost(13, 20.0f); //Mob aggro + } + else + { + includeFlags |= (NAV_GROUND | NAV_WATER | NAV_GROUND_STEEP); + excludeFlags |= (NAV_MAGMA_SLIME); + } + } + else if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) + { + Creature* creature = (Creature*)m_sourceUnit; + if (creature->CanWalk()) + includeFlags |= (NAV_GROUND | NAV_GROUND_STEEP); // walk + + // creatures don't take environmental damage + if (creature->CanSwim()) + includeFlags |= (NAV_WATER | NAV_MAGMA_SLIME); // swim + } +#else if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) { Creature* creature = (Creature*)m_sourceUnit; @@ -806,6 +1029,7 @@ void PathFinder::createFilter() // perfect support not possible, just stay 'safe' includeFlags |= (NAV_GROUND | NAV_WATER); } +#endif m_filter.setIncludeFlags(includeFlags); m_filter.setExcludeFlags(excludeFlags); @@ -817,7 +1041,7 @@ void PathFinder::updateFilter() { // allow creatures to cheat and use different movement types if they are moved // forcefully into terrain they can't normally move in - if (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderwater()) + if (m_sourceUnit && (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderwater())) { uint16 includedFlags = m_filter.getIncludeFlags(); includedFlags |= getNavTerrain(m_sourceUnit->GetPositionX(), @@ -831,7 +1055,7 @@ void PathFinder::updateFilter() NavTerrainFlag PathFinder::getNavTerrain(float x, float y, float z) const { GridMapLiquidData data; - if (m_sourceUnit->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data) == LIQUID_MAP_NO_WATER) + if (m_sourceUnit && m_sourceUnit->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data) == LIQUID_MAP_NO_WATER) return NAV_GROUND; switch (data.type_flags) @@ -849,7 +1073,7 @@ NavTerrainFlag PathFinder::getNavTerrain(float x, float y, float z) const bool PathFinder::HaveTile(const Vector3& p) const { - if (m_sourceUnit->GetTransport()) + if (m_sourceUnit && m_sourceUnit->GetTransport()) return true; int tx = -1, ty = -1; diff --git a/src/game/MotionGenerators/PathFinder.h b/src/game/MotionGenerators/PathFinder.h index 6457eb66cb..162295b86f 100644 --- a/src/game/MotionGenerators/PathFinder.h +++ b/src/game/MotionGenerators/PathFinder.h @@ -34,8 +34,15 @@ class Unit; // 74*4.0f=296y number_of_points*interval = max_path_len // this is way more than actual evade range // I think we can safely cut those down even more +#ifdef ENABLE_PLAYERBOTS +// This value is doubled from the original and then used only half by findpath. +// If the same value is used by findpath and findsmooth path no result will be found by the second at max length. +#define MAX_PATH_LENGTH 148 +#define MAX_POINT_PATH_LENGTH 148 +#else #define MAX_PATH_LENGTH 74 #define MAX_POINT_PATH_LENGTH 74 +#endif #define SMOOTH_PATH_STEP_SIZE 4.0f #define SMOOTH_PATH_SLOP 0.3f @@ -89,6 +96,15 @@ class PathFinder PointsArray& getPath() { return m_pathPoints; } PathType getPathType() const { return m_type; } +#ifdef ENABLE_PLAYERBOTS + PathFinder(); + PathFinder(uint32 mapId, uint32 instanceId = 0); + void setArea(uint32 mapId, float x, float y, float z, uint32 area = 1, float range = 10.0f); + uint32 getArea(uint32 mapId, float x, float y, float z); + unsigned short getFlags(uint32 mapId, float x, float y, float z); + void setAreaCost(uint32 area = 1, float cost = 0.0f); +#endif + private: PointsArray m_pathPoints; // our actual (x,y,z) path to the target @@ -143,6 +159,10 @@ class PathFinder void BuildPointPath(const float* startPoint, const float* endPoint); void BuildShortcut(); +#ifdef ENABLE_PLAYERBOTS + bool IsPointHigherThan(const Vector3& posOne, const Vector3& posTwo); +#endif + NavTerrainFlag getNavTerrain(float x, float y, float z) const; void createFilter(); void updateFilter(); diff --git a/src/game/MotionGenerators/PathMovementGenerator.cpp b/src/game/MotionGenerators/PathMovementGenerator.cpp index baee2ace74..74b0ec1e4e 100644 --- a/src/game/MotionGenerators/PathMovementGenerator.cpp +++ b/src/game/MotionGenerators/PathMovementGenerator.cpp @@ -26,8 +26,8 @@ #include -AbstractPathMovementGenerator::AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation, int32 offset/* = 0*/) : - m_pathIndex(offset), m_orientation(orientation), m_firstCycle(false), m_startPoint(0), m_speedChanged(false) +AbstractPathMovementGenerator::AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation, int32 offset/* = 0*/, bool cyclic/* = true*/) : + m_pathIndex(offset), m_orientation(orientation), m_firstCycle(false), m_startPoint(0), m_speedChanged(false), m_cyclic(cyclic) { for (size_t i = 0; i < path.size(); ++i) m_path[i] = { path[i].x, path[i].y, path[i].z, ((i + 1) == path.size() ? orientation : 0), 0, 0 }; @@ -414,4 +414,4 @@ bool TaxiMovementGenerator::Resume(Unit& unit) } return Move(unit); -} +} \ No newline at end of file diff --git a/src/game/MotionGenerators/PathMovementGenerator.h b/src/game/MotionGenerators/PathMovementGenerator.h index f4d91025e8..a139fad625 100644 --- a/src/game/MotionGenerators/PathMovementGenerator.h +++ b/src/game/MotionGenerators/PathMovementGenerator.h @@ -29,7 +29,7 @@ class AbstractPathMovementGenerator : public MovementGenerator { public: - explicit AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation = 0, int32 offset = 0); + explicit AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation = 0, int32 offset = 0, bool cyclic = true); explicit AbstractPathMovementGenerator(const WaypointPath* path, int32 offset = 0, bool cyclic = false, ObjectGuid guid = ObjectGuid()); void Initialize(Unit& owner) override; @@ -61,10 +61,10 @@ class AbstractPathMovementGenerator : public MovementGenerator class FixedPathMovementGenerator : public AbstractPathMovementGenerator { public: - FixedPathMovementGenerator(const Movement::PointsArray &path, float orientation, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0) : - AbstractPathMovementGenerator(path, orientation, offset), m_flying(flying), m_speed(speed), m_forcedMovement(forcedMovement) {} - FixedPathMovementGenerator(const Movement::PointsArray& path, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0) : - FixedPathMovementGenerator(path, 0, forcedMovement, flying, speed, offset) {} + FixedPathMovementGenerator(const Movement::PointsArray& path, float orientation, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = true) : + AbstractPathMovementGenerator(path, orientation, offset, cyclic), m_flying(flying), m_speed(speed), m_forcedMovement(forcedMovement) {} + FixedPathMovementGenerator(const Movement::PointsArray& path, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = true) : + FixedPathMovementGenerator(path, 0, forcedMovement, flying, speed, offset, cyclic) {} FixedPathMovementGenerator(Unit& unit, int32 pathId, WaypointPathOrigin wpOrigin, ForcedMovement forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = false, ObjectGuid guid = ObjectGuid()); FixedPathMovementGenerator(Creature& creature); diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.cpp b/src/game/MotionGenerators/TargetedMovementGenerator.cpp index 3c98f6a8e1..3fe71c05d8 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.cpp +++ b/src/game/MotionGenerators/TargetedMovementGenerator.cpp @@ -580,7 +580,11 @@ void ChaseMovementGenerator::CutPath(Unit& owner, PointsArray& path) { if (this->i_offset != 0.f) // need to cut path until most distant viable point { +#ifdef ENABLE_PLAYERBOTS + const float dist = (i_offset * (owner.IsPlayer() ? 1.0f : CHASE_MOVE_CLOSER_FACTOR)) + (this->i_target->GetCombinedCombatReach(&owner, false) * CHASE_DEFAULT_RANGE_FACTOR); +#else const float dist = (i_offset * CHASE_MOVE_CLOSER_FACTOR) + (this->i_target->GetCombinedCombatReach(&owner, false) * CHASE_DEFAULT_RANGE_FACTOR); +#endif const float distSquared = (dist * dist); float tarX, tarY, tarZ; this->i_target->GetPosition(tarX, tarY, tarZ); @@ -732,7 +736,12 @@ float FollowMovementGenerator::GetSpeed(Unit& owner) const // Followers sync with master's speed when not in combat // Use default speed when a mix of PC and NPC units involved (escorting?) if (owner.HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) == i_target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) + { +#ifdef ENABLE_PLAYERBOTS + if (!(!m_boost && owner.IsPlayer() && !((Player*)(&owner))->isRealPlayer())) //Do not speed up bots when not boosting. +#endif speed = i_target->GetSpeedInMotion(); + } // Catchup boost is not allowed, stop here: if (!IsBoostAllowed(owner)) diff --git a/src/game/Server/DBCStores.cpp b/src/game/Server/DBCStores.cpp index 9578924353..7e4d030c92 100644 --- a/src/game/Server/DBCStores.cpp +++ b/src/game/Server/DBCStores.cpp @@ -84,6 +84,11 @@ DBCStorage sDurabilityCostsStore(DurabilityCostsfmt); DBCStorage sEmotesStore(EmotesEntryfmt); DBCStorage sEmotesTextStore(EmotesTextEntryfmt); +#ifdef ENABLE_PLAYERBOTS +typedef std::tuple EmotesTextSoundKey; +static std::map sEmotesTextSoundMap; +DBCStorage sEmotesTextSoundStore(EmotesTextSoundEntryfmt); +#endif //DBCStorage sFactionStore(FactionEntryfmt); DBCStorage sFactionTemplateStore(FactionTemplateEntryfmt); @@ -117,6 +122,9 @@ DBCStorage sItemExtendedCostStore(ItemExtendedCostEntryf DBCStorage sItemRandomPropertiesStore(ItemRandomPropertiesfmt); DBCStorage sItemRandomSuffixStore(ItemRandomSuffixfmt); DBCStorage sItemSetStore(ItemSetEntryfmt); +#ifdef ENABLE_PLAYERBOTS +DBCStorage sLFGDungeonStore(LFGDungeonEntryfmt); +#endif DBCStorage sLiquidTypeStore(LiquidTypefmt); DBCStorage sLightStore(LightEntryfmt); DBCStorage sLockStore(LockEntryfmt); @@ -315,6 +323,17 @@ void LoadDBCStores(const std::string& dataPath) LoadDBC(availableDbcLocales, bar, bad_dbc_files, sEmotesTextStore, dbcPath, "EmotesText.dbc"); // LoadDBC(availableDbcLocales, bar, bad_dbc_files, sFactionStore, dbcPath, "Faction.dbc"); +#ifdef ENABLE_PLAYERBOTS + LoadDBC(availableDbcLocales, bar, bad_dbc_files, sEmotesTextSoundStore, dbcPath, "EmotesTextSound.dbc"); + for (uint32 i = 0; i < sEmotesTextSoundStore.GetNumRows(); ++i) + { + if (EmotesTextSoundEntry const* entry = sEmotesTextSoundStore.LookupEntry(i)) + { + sEmotesTextSoundMap[EmotesTextSoundKey(entry->EmotesTextId, entry->RaceId, entry->SexId)] = entry; + } + } +#endif + LoadDBC(availableDbcLocales, bar, bad_dbc_files, sFactionTemplateStore, dbcPath, "FactionTemplate.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sGameObjectArtKitStore, dbcPath, "GameObjectArtKit.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sGameObjectDisplayInfoStore, dbcPath, "GameObjectDisplayInfo.dbc"); @@ -347,6 +366,9 @@ void LoadDBCStores(const std::string& dataPath) LoadDBC(availableDbcLocales, bar, bad_dbc_files, sItemRandomPropertiesStore, dbcPath, "ItemRandomProperties.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sItemRandomSuffixStore, dbcPath, "ItemRandomSuffix.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sItemSetStore, dbcPath, "ItemSet.dbc"); +#ifdef ENABLE_PLAYERBOTS + LoadDBC(availableDbcLocales, bar, bad_dbc_files, sLFGDungeonStore, dbcPath, "LFGDungeons.dbc"); +#endif LoadDBC(availableDbcLocales, bar, bad_dbc_files, sLightStore, dbcPath, "Light.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sLiquidTypeStore, dbcPath, "LiquidType.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sLockStore, dbcPath, "Lock.dbc"); @@ -977,3 +999,11 @@ DBCStorage const* GetItemDisplayStore() { return &sItemS DBCStorage const* GetCreatureDisplayStore() { return &sCreatureDisplayInfoStore; } DBCStorage const* GetEmotesStore() { return &sEmotesStore; } DBCStorage const* GetEmotesTextStore() { return &sEmotesTextStore; } + +#ifdef ENABLE_PLAYERBOTS +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender) +{ + auto itr = sEmotesTextSoundMap.find(EmotesTextSoundKey(emote, race, gender)); + return itr != sEmotesTextSoundMap.end() ? itr->second : nullptr; +} +#endif \ No newline at end of file diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index fd34da04b2..e1071f17e4 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -71,12 +71,23 @@ uint32 GetCreatureModelRace(uint32 model_id); uint32 GetDefaultMapLight(uint32 mapId); +#ifdef ENABLE_PLAYERBOTS +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender); +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color); +typedef std::multimap CharSectionsMap; +extern CharSectionsMap sCharSectionMap; +#endif + extern DBCStorage sAreaStore;// recommend access using functions extern DBCStorage sAreaTriggerStore; extern DBCStorage sAuctionHouseStore; extern DBCStorage sBankBagSlotPricesStore; extern DBCStorage sBattlemasterListStore; -// extern DBCStorage sChatChannelsStore; -- accessed using function, no usable index +#ifdef ENABLE_PLAYERBOTS +extern DBCStorage sChatChannelsStore; //has function for access aswell +#else +// extern DBCStorage sChatChannelsStore; //has function for access aswell, no usable index +#endif extern DBCStorage sCharStartOutfitStore; extern DBCStorage sCharTitlesStore; extern DBCStorage sChatChannelsStore; @@ -125,6 +136,9 @@ extern DBCStorage sItemExtendedCostStore; extern DBCStorage sItemRandomPropertiesStore; extern DBCStorage sItemRandomSuffixStore; extern DBCStorage sItemSetStore; +#ifdef ENABLE_PLAYERBOTS +extern DBCStorage sLFGDungeonStore; +#endif extern DBCStorage sLiquidTypeStore; extern DBCStorage sLockStore; extern DBCStorage sMailTemplateStore; diff --git a/src/game/Server/DBCStructure.h b/src/game/Server/DBCStructure.h index 0975877e65..268908db9a 100644 --- a/src/game/Server/DBCStructure.h +++ b/src/game/Server/DBCStructure.h @@ -380,6 +380,21 @@ struct EmotesTextEntry // m_emoteText }; +#ifdef ENABLE_PLAYERBOTS +/** +* \struct EmotesTextSoundEntry +* \brief Entry resenting the text sound for given emote. +*/ +struct EmotesTextSoundEntry +{ + uint32 Id; // 0 + uint32 EmotesTextId; // 1 + uint32 RaceId; // 2 + uint32 SexId; // 3, 0 male / 1 female + uint32 SoundId; // 4 +}; +#endif + struct FactionEntry { uint32 ID; // 0 m_ID @@ -684,6 +699,19 @@ struct LightEntry //uint32 deathParams; // 11 }; +#ifdef ENABLE_PLAYERBOTS +struct LFGDungeonEntry +{ + uint32 ID; // 0 m_ID + char* name[16]; // 1-17 m_name_lang + //uint32 mask // 18 m_name_lang_mask + uint32 minlevel; // 19 m_minLevel + uint32 maxlevel; // 20 m_maxLevel + uint32 type; // 21 m_typeId + uint32 faction; // 22 m_faction +}; +#endif + struct LiquidTypeEntry { uint32 Id; // 0 diff --git a/src/game/Server/DBCfmt.h b/src/game/Server/DBCfmt.h index f235d50562..6ebf560696 100644 --- a/src/game/Server/DBCfmt.h +++ b/src/game/Server/DBCfmt.h @@ -46,6 +46,9 @@ const char DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii"; const char DurabilityQualityfmt[] = "nf"; const char EmotesEntryfmt[] = "nxxiiix"; const char EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx"; +#ifdef ENABLE_PLAYERBOTS +char const EmotesTextSoundEntryfmt[] = "niiii"; +#endif // const char FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiissssssssssssssssxxxxxxxxxxxxxxxxxx"; const char FactionTemplateEntryfmt[] = "niiiiiiiiiiiii"; const char GameObjectArtKitfmt[] = "nxxxxxxx"; @@ -74,6 +77,9 @@ const char ItemExtendedCostEntryfmt[] = "niiiiiiiiiiiii"; const char ItemRandomPropertiesfmt[] = "nxiiixxssssssssssssssssx"; const char ItemRandomSuffixfmt[] = "nssssssssssssssssxxiiiiii"; const char ItemSetEntryfmt[] = "dssssssssssssssssxxxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiiiii"; +#ifdef ENABLE_PLAYERBOTS +const char LFGDungeonEntryfmt[] = "nssssssssssssssssxiiiixx"; +#endif const char LiquidTypefmt[] = "niii"; const char LightEntryfmt[] = "nifffxxxxxxx"; const char LockEntryfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiixxxxxxxx"; diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index d8de33f3d2..382187997b 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -51,6 +51,10 @@ #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/playerbot.h" +#endif + // select opcodes appropriate for processing in Map::Update context for current session state static bool MapSessionFilterHelper(WorldSession* session, OpcodeHandler const& opHandle) { @@ -201,7 +205,7 @@ void WorldSession::SetExpansion(uint8 expansion) /// Send a packet to the client void WorldSession::SendPacket(WorldPacket const& packet, bool forcedSend /*= false*/) const { -#ifdef BUILD_DEPRECATED_PLAYERBOT +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) // Send packet to bot AI if (GetPlayer()) { @@ -393,7 +397,7 @@ bool WorldSession::Update(uint32 /*diff*/) // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer -#ifdef BUILD_DEPRECATED_PLAYERBOT +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) if (_player && _player->GetPlayerbotMgr()) _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); #endif @@ -473,6 +477,11 @@ bool WorldSession::Update(uint32 /*diff*/) } #endif +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer() && GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->UpdateSessions(0); +#endif + // check if we are safe to proceed with logout // logout procedure should happen only in World::UpdateSessions() method!!! switch (m_sessionState) @@ -591,6 +600,33 @@ void WorldSession::UpdateMap(uint32 diff) } } +#ifdef ENABLE_PLAYERBOTS +void WorldSession::HandleBotPackets() +{ + while (!m_recvQueue.empty()) + { + if (_player) + _player->SetCanDelayTeleport(true); + + auto const packet = std::move(m_recvQueue.front()); + m_recvQueue.pop_front(); + OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()]; + (this->*opHandle.handler)(*packet); + + if (_player) + { + // can be not set in fact for login opcode, but this not create problems. + _player->SetCanDelayTeleport(false); + + // we should execute delayed teleports only for alive(!) players + // because we don't want player's ghost teleported from graveyard + if (_player->IsHasDelayedTeleport()) + _player->TeleportTo(_player->m_teleport_dest, _player->m_teleport_options); + } + } +} +#endif + /// %Log the player out void WorldSession::LogoutPlayer() { @@ -612,7 +648,16 @@ void WorldSession::LogoutPlayer() _player->GetPlayerbotMgr()->LogoutAllBots(true); #endif +#ifdef ENABLE_PLAYERBOTS + if (_player->GetPlayerbotMgr() && (!_player->GetPlayerbotAI() || _player->GetPlayerbotAI()->IsRealPlayer())) + _player->GetPlayerbotMgr()->LogoutAllBots(); + + sRandomPlayerbotMgr.OnPlayerLogout(_player); + + sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), m_socket ? GetRemoteAddress().c_str() : "bot", _player->GetName(), _player->GetGUIDLow()); +#else sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), GetRemoteAddress().c_str(), _player->GetName(), _player->GetGUIDLow()); +#endif if (Loot* loot = sLootMgr.GetLoot(_player)) loot->Release(_player); @@ -700,7 +745,8 @@ void WorldSession::LogoutPlayer() ///- Leave all channels before player delete... _player->CleanupChannels(); - + +#ifndef ENABLE_PLAYERBOTS ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. _player->UninviteFromGroup(); @@ -708,6 +754,7 @@ void WorldSession::LogoutPlayer() // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) if (_player->GetGroup() && !_player->GetGroup()->IsRaidGroup() && m_socket && !m_socket->IsClosed()) _player->RemoveFromGroup(); +#endif ///- Send update to group if (Group* group = _player->GetGroup()) @@ -723,7 +770,7 @@ void WorldSession::LogoutPlayer() // GM ticket notification sTicketMgr.OnPlayerOnlineState(*_player, false); -#ifdef BUILD_DEPRECATED_PLAYERBOT +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) // Remember player GUID for update SQL below uint32 guid = _player->GetGUIDLow(); #endif @@ -752,10 +799,10 @@ void WorldSession::LogoutPlayer() static SqlStatementID updChars; -#ifdef BUILD_DEPRECATED_PLAYERBOT - // Set for only character instead of accountid +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) + // Set for only character instead of account id // Different characters can be alive as bots - SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); + stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); stmt.PExecute(guid); #else ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline @@ -1313,3 +1360,10 @@ void WorldSession::HandleWardenDataOpcode(WorldPacket& recv_data) { m_anticheat->WardenPacket(recv_data); } + +#ifdef ENABLE_PLAYERBOTS +void WorldSession::SetNoAnticheat() +{ + m_anticheat.reset(new NullSessionAnticheat(this)); +} +#endif \ No newline at end of file diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 05f77ddcec..7c37f93163 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -269,7 +269,7 @@ class WorldSession char const* GetPlayerName() const; std::string GetChatType(uint32 type); void SetSecurity(AccountTypes security) { _security = security; } -#ifdef BUILD_DEPRECATED_PLAYERBOT +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) // Players connected without socket are bot const std::string GetRemoteAddress() const { return m_socket ? m_socket->GetRemoteAddress() : "disconnected/bot"; } #else @@ -285,7 +285,7 @@ class WorldSession void AssignAnticheat(std::unique_ptr&& anticheat); SessionAnticheatInterface* GetAnticheat() const { return m_anticheat.get(); } -#ifdef BUILD_DEPRECATED_PLAYERBOT +#if defined(BUILD_DEPRECATED_PLAYERBOT) || defined(ENABLE_PLAYERBOTS) void SetNoAnticheat(); #endif @@ -473,6 +473,10 @@ class WorldSession void SendKnockBack(Unit* who, float angle, float horizontalSpeed, float verticalSpeed); void SendPlaySpellVisual(ObjectGuid guid, uint32 spellArtKit) const; +#ifdef ENABLE_PLAYERBOTS + void SendTeleportToObservers(float x, float y, float z, float orientation); +#endif + void SendAuthOk() const; void SendAuthQueued() const; void SendKickReason(uint8 reason, std::string const& string) const; @@ -913,6 +917,10 @@ class WorldSession std::deque GetOutOpcodeHistory(); std::deque GetIncOpcodeHistory(); +#ifdef ENABLE_PLAYERBOTS + void HandleBotPackets(); +#endif + Messager& GetMessager() { return m_messager; } void SetPacketLogging(bool state); diff --git a/src/game/Spells/Spell.cpp b/src/game/Spells/Spell.cpp index b40b322f94..042bd3f1b2 100644 --- a/src/game/Spells/Spell.cpp +++ b/src/game/Spells/Spell.cpp @@ -47,6 +47,10 @@ #include "Spells/Scripts/SpellScript.h" #include "Entities/ObjectGuid.h" +#ifdef ENABLE_PLAYERBOTS +#include "playerbot/PlayerbotAI.h" +#endif + extern pEffect SpellEffects[MAX_SPELL_EFFECTS]; class PrioritizeManaUnitWraper @@ -5170,6 +5174,17 @@ SpellCastResult Spell::CheckCast(bool strict) if (m_spellInfo->MaxTargetLevel && target->GetLevel() > m_spellInfo->MaxTargetLevel) return SPELL_FAILED_HIGHLEVEL; +#ifdef ENABLE_PLAYERBOTS + if (target->IsPlayer()) + { + PlayerbotAI* bot = ((Player*)target)->GetPlayerbotAI(); + if (bot && bot->IsImmuneToSpell(m_spellInfo->Id)) + { + return SPELL_FAILED_IMMUNE; + } + } +#endif + if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_NOT_ON_TRIVIAL) && target->IsTrivialForTarget(m_caster)) return SPELL_FAILED_TARGET_IS_TRIVIAL; } diff --git a/src/game/Spells/SpellEffects.cpp b/src/game/Spells/SpellEffects.cpp index a26a9723c0..bf6de84213 100644 --- a/src/game/Spells/SpellEffects.cpp +++ b/src/game/Spells/SpellEffects.cpp @@ -8536,6 +8536,9 @@ void Spell::EffectSpiritHeal(SpellEffectIndex /*eff_idx*/) if (Player* player = static_cast(unitTarget)) { +#ifdef ENABLE_PLAYERBOTS + player->RemoveAurasDueToSpell(2584); +#endif player->ResurrectPlayer(1.0f); player->SpawnCorpseBones(); diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index 38e201504f..c2660c5372 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -76,6 +76,12 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "ahbot/AhBot.h" +#include "playerbot/PlayerbotAIConfig.h" +#include "playerbot/RandomPlayerbotMgr.h" +#endif + #include #include #include @@ -99,6 +105,13 @@ uint32 World::m_relocation_ai_notify_delay = 1000u; uint32 World::m_currentMSTime = 0; TimePoint World::m_currentTime = TimePoint(); uint32 World::m_currentDiff = 0; +#ifdef ENABLE_PLAYERBOTS +uint32 World::m_currentDiffSum = 0; +uint32 World::m_currentDiffSumIndex = 0; +uint32 World::m_averageDiff = 0; +uint32 World::m_maxDiff = 0; +std::list World::m_histDiff; +#endif /// World constructor World::World() : mail_timer(0), mail_timer_expires(0), m_NextDailyQuestReset(0), m_NextWeeklyQuestReset(0), m_NextMonthlyQuestReset(0), m_opcodeCounters(NUM_MSG_TYPES) @@ -154,6 +167,9 @@ void World::CleanupsBeforeStop() UpdateSessions(1); // real players unload required UpdateSessions call sBattleGroundMgr.DeleteAllBattleGrounds(); // unload battleground templates before different singletons destroyed sMapMgr.UnloadAll(); // unload all grids (including locked in memory) +#ifdef ENABLE_PLAYERBOTS + sRandomPlayerbotMgr.LogoutAllBots(); +#endif } /// Find a session by its id @@ -1435,10 +1451,14 @@ void World::SetInitialWorldSettings() m_timers[WUPDATE_METRICS].SetInterval(1 * IN_MILLISECONDS); #endif // BUILD_METRICS - #ifdef BUILD_DEPRECATED_PLAYERBOT PlayerbotMgr::SetInitialWorldSettings(); #endif + +#ifdef ENABLE_PLAYERBOTS + sPlayerbotAIConfig.Initialize(); +#endif + sLog.outString("---------------------------------------"); sLog.outString(" CMANGOS: World initialized "); sLog.outString("---------------------------------------"); @@ -1502,6 +1522,33 @@ void World::Update(uint32 diff) m_currentTime = std::chrono::time_point_cast(Clock::now()); m_currentDiff = diff; +#ifdef ENABLE_PLAYERBOTS + m_currentDiffSum += diff; + m_currentDiffSumIndex++; + + m_histDiff.push_back(diff); + m_maxDiff = std::max(m_maxDiff, diff); + + while (m_histDiff.size() >= 600) + { + m_currentDiffSum -= m_histDiff.front(); + m_histDiff.pop_front(); + } + + m_averageDiff = (uint32)(m_currentDiffSum / m_histDiff.size()); + + if (m_currentDiffSumIndex && m_currentDiffSumIndex % 60 == 0) + { + sLog.outBasic("Avg Diff: %u. Sessions online: %u.", m_averageDiff, (uint32)GetActiveSessionCount()); + sLog.outBasic("Max Diff: %u.", m_maxDiff); + } + + if (m_currentDiffSum % 3000 == 0) + { + m_maxDiff = *std::max_element(m_histDiff.begin(), m_histDiff.end()); + } +#endif + ///- Update the different timers for (auto& m_timer : m_timers) { @@ -1557,6 +1604,19 @@ void World::Update(uint32 diff) } #endif +#ifdef ENABLE_PLAYERBOTS +#ifndef BUILD_AHBOT + ///
  • Handle AHBot operations + if (m_timers[WUPDATE_AHBOT].Passed()) + { + auctionbot.Update(); + m_timers[WUPDATE_AHBOT].Reset(); + } +#endif + sRandomPlayerbotMgr.UpdateAI(diff); + sRandomPlayerbotMgr.UpdateSessions(diff); +#endif + ///
  • Handle session updates #ifdef BUILD_METRICS auto preSessionTime = std::chrono::time_point_cast(Clock::now()); diff --git a/src/game/World/World.h b/src/game/World/World.h index 950ff6864f..884f251b99 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -655,6 +655,10 @@ class World static uint32 GetCurrentMSTime() { return m_currentMSTime; } static TimePoint GetCurrentClockTime() { return m_currentTime; } static uint32 GetCurrentDiff() { return m_currentDiff; } +#ifdef ENABLE_PLAYERBOTS + static uint32 GetAverageDiff() { return m_averageDiff; } + static uint32 GetMaxDiff() { return m_maxDiff; } +#endif template void ExecuteForAllSessions(T executor) const @@ -781,6 +785,13 @@ class World static uint32 m_currentMSTime; static TimePoint m_currentTime; static uint32 m_currentDiff; +#ifdef ENABLE_PLAYERBOTS + static uint32 m_currentDiffSum; + static uint32 m_currentDiffSumIndex; + static uint32 m_averageDiff; + static uint32 m_maxDiff; + static std::list m_histDiff; +#endif Messager m_messager; diff --git a/src/game/vmap/GameObjectModel.cpp b/src/game/vmap/GameObjectModel.cpp index 9821c7f602..6899f8e7c5 100644 --- a/src/game/vmap/GameObjectModel.cpp +++ b/src/game/vmap/GameObjectModel.cpp @@ -93,6 +93,12 @@ bool GameObjectModel::initialize(const GameObject* const pGo, const GameObjectDi if (!iModel) return false; +#ifdef ENABLE_PLAYERBOTS + // Bots need this to see the game objects + if (it->second.name.find(".m2") != std::string::npos) + iModel->setModelFlags(VMAP::MOD_M2); +#endif + name = it->second.name; iPos = Vector3(pGo->GetPositionX(), pGo->GetPositionY(), pGo->GetPositionZ()); collision_enabled = true; diff --git a/src/mangosd/CMakeLists.txt b/src/mangosd/CMakeLists.txt index 25e3cbe06e..8450387764 100644 --- a/src/mangosd/CMakeLists.txt +++ b/src/mangosd/CMakeLists.txt @@ -94,6 +94,11 @@ if(WIN32) add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_SOURCE_DIR}/src/game/PlayerBot/playerbot.conf.dist.in\" \"$/playerbot.conf.dist\") endif() + + if(BUILD_PLAYERBOTS) + add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_SOURCE_DIR}/src/modules/PlayerBots/playerbot/aiplayerbot.conf.dist.in.tbc\" \"$/aiplayerbot.conf.dist\") + endif() if(BUILD_AHBOT) add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD @@ -134,6 +139,11 @@ if (BUILD_AHBOT) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ahbot.conf.dist DESTINATION ${CONF_DIR}) endif() +# Define ENABLE_PLAYERBOTS if need +if (BUILD_PLAYERBOTS) + add_definitions(-DENABLE_PLAYERBOTS) +endif() + if(MSVC) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/\${BUILD_TYPE}/${EXECUTABLE_NAME}.pdb DESTINATION ${BIN_DIR} CONFIGURATIONS Debug) endif() diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index 3ce6a38aa0..896d769905 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -286,6 +286,11 @@ int Master::Run() // send all still queued mass mails (before DB connections shutdown) sMassMailMgr.Update(true); +#ifdef ENABLE_PLAYERBOTS + // kick and save all players + sWorld.KickAll(true); +#endif + ///- Wait for DB delay threads to end CharacterDatabase.HaltDelayThread(); WorldDatabase.HaltDelayThread(); diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index ad6a4899bd..5058c8c6c1 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -261,6 +261,11 @@ if(APPLE OR "${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD") add_compile_definitions(_GNU_SOURCE) endif() +# Define ENABLE_PLAYERBOTS if need +if (BUILD_PLAYERBOTS) + add_definitions(-DENABLE_PLAYERBOTS) +endif() + # Generate precompiled header if(PCH) target_precompile_headers(${LIBRARY_NAME} PRIVATE "${PCH_FILE_HEADER}")