diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..939f8c9ed2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/modules/Bots"] + path = src/modules/Bots + url = https://github.com/celguar/mangosbot-bots.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 29cfe35b31..e5cd640069 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,6 +327,21 @@ if(NOT BUILD_GAME_SERVER AND BUILD_PLAYERBOT) message(STATUS "BUILD_PLAYERBOT forced to OFF due to BUILD_GAME_SERVER is not set") endif() +if(NOT BUILD_GAME_SERVER AND BUILD_IKE3_BOTS) + set(BUILD_IKE3_BOTS OFF) + message(STATUS "BUILD_PLAYERBOTS forced to OFF due to BUILD_GAME_SERVER is not set") +endif() + +if(BUILD_IKE3_BOTS) + set(BUILD_PLAYERBOT OFF) + message(STATUS "CMaNGOS bots DISABLED because Ike3 bots enabled") +endif() + +if(BUILD_PLAYERBOT) + set(BUILD_IKE3_BOTS OFF) + message(STATUS "Ike3 bots DISABLED because CMaNGOS bots enabled") +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 4c7e333f9c..85a9fd9a5b 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -7,6 +7,7 @@ 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_PLAYERBOT "Build Playerbot mod" OFF) +option(BUILD_IKE3_BOTS "Build ike3 Playerbots" 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) @@ -35,6 +36,7 @@ message(STATUS BUILD_EXTRACTORS Build map/dbc/vmap/mmap extractor BUILD_SCRIPTDEV Build scriptdev. (Disable it to speedup build in dev mode by not including scripts) BUILD_PLAYERBOT Build Playerbot mod + BUILD_IKE3_BOTS Build Ike3 Playerbot 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 1db7f3fea4..ad7fb8420d 100644 --- a/cmake/showoptions.cmake +++ b/cmake/showoptions.cmake @@ -67,6 +67,12 @@ else() message(STATUS "Build Playerbot : No (default)") endif() +if(BUILD_IKE3_BOTS) + message(STATUS "Build ike3 Playerbots : Yes") +else() + message(STATUS "Build ike3 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 9f3ce0bf74..6199ecc457 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_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; @@ -997,6 +1027,7 @@ 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); + 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 4eb9760818..c78fe5dafe 100644 --- a/sql/create/db_create_mysql.sql +++ b/sql/create/db_create_mysql.sql @@ -6,6 +6,8 @@ CREATE DATABASE `classiccharacters` DEFAULT CHARACTER SET utf8 COLLATE utf8_gene CREATE DATABASE `classicrealmd` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE `classicplayerbots` 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 `classicmangos`.* TO 'mangos'@'localhost'; @@ -15,3 +17,5 @@ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE T GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `classiccharacters`.* TO 'mangos'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `classicrealmd`.* TO 'mangos'@'localhost'; + +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `classicplayerbots`.* TO 'mangos'@'localhost'; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba37dd1c3e..52ca754527 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(BUILD_LOGIN_SERVER) add_subdirectory(realmd) endif() + +if(BUILD_IKE3_BOTS) +add_subdirectory(modules) +endif() diff --git a/src/game/BattleGround/BattleGroundWS.h b/src/game/BattleGround/BattleGroundWS.h index 7d389a5092..841fd38104 100644 --- a/src/game/BattleGround/BattleGroundWS.h +++ b/src/game/BattleGround/BattleGroundWS.h @@ -232,7 +232,7 @@ class BattleGroundWS : public BattleGround // Flag interactions void ClearDroppedFlagGuid(Team team) { m_droppedFlagGuid[GetTeamIndexByTeamId(team)].Clear();} - ObjectGuid const& GetDroppedFlagGuid(Team team) const { return m_droppedFlagGuid[GetTeamIndexByTeamId(team)];} + ObjectGuid const& GetDroppedFlagGuid(Team team) const { return m_droppedFlagGuid[GetTeamIndexByTeamId(team)]; } void RespawnFlagAtBase(Team team, bool wasCaptured); void RespawnDroppedFlag(Team team); diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index 3dca10a49c..1f25ef210b 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -51,6 +51,17 @@ if(NOT BUILD_PLAYERBOT) endforeach() endif() +if(NOT BUILD_IKE3_BOTS) + # exclude Ike3 Playerbots folder + set (EXCLUDE_DIR "Bots/") + 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}") @@ -84,6 +95,7 @@ target_link_libraries(${LIBRARY_NAME} ) # include additionals headers +if(NOT BUILD_IKE3_BOTS) set(ADDITIONAL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/vmap @@ -93,6 +105,16 @@ set(ADDITIONAL_INCLUDE_DIRS ${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,68 @@ target_include_directories(${LIBRARY_NAME} PRIVATE ${Boost_INCLUDE_DIRS} ) +if(BUILD_IKE3_BOTS) + include_directories( + ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot + ${CMAKE_SOURCE_DIR}/src/modules/Bots/ahbot + ${CMAKE_SOURCE_DIR}/src/game + ${CMAKE_SOURCE_DIR}/src/game/AI + ${CMAKE_SOURCE_DIR}/src/game/Accounts + ${CMAKE_SOURCE_DIR}/src/game/Addons + ${CMAKE_SOURCE_DIR}/src/game/Arena + ${CMAKE_SOURCE_DIR}/src/game/AuctionHouse + ${CMAKE_SOURCE_DIR}/src/game/BattleGround + ${CMAKE_SOURCE_DIR}/src/game/Chat + ${CMAKE_SOURCE_DIR}/src/game/ChatCommands + ${CMAKE_SOURCE_DIR}/src/game/Combat + ${CMAKE_SOURCE_DIR}/src/game/DBScripts + ${CMAKE_SOURCE_DIR}/src/game/Entities + ${CMAKE_SOURCE_DIR}/src/game/GMTickets + ${CMAKE_SOURCE_DIR}/src/game/GameEvents + ${CMAKE_SOURCE_DIR}/src/game/Globals + ${CMAKE_SOURCE_DIR}/src/game/Grids + ${CMAKE_SOURCE_DIR}/src/game/Groups + ${CMAKE_SOURCE_DIR}/src/game/Guilds + ${CMAKE_SOURCE_DIR}/src/game/LFG + ${CMAKE_SOURCE_DIR}/src/game/Loot + ${CMAKE_SOURCE_DIR}/src/game/Mails + ${CMAKE_SOURCE_DIR}/src/game/Maps + ${CMAKE_SOURCE_DIR}/src/game/MotionGenerators + ${CMAKE_SOURCE_DIR}/src/game/Movement + ${CMAKE_SOURCE_DIR}/src/game/Object + ${CMAKE_SOURCE_DIR}/src/game/OutdoorPvP + ${CMAKE_SOURCE_DIR}/src/game/Pools + ${CMAKE_SOURCE_DIR}/src/game/Quests + ${CMAKE_SOURCE_DIR}/src/game/References + ${CMAKE_SOURCE_DIR}/src/game/Reputation + ${CMAKE_SOURCE_DIR}/src/game/Server + ${CMAKE_SOURCE_DIR}/src/game/Server + ${CMAKE_SOURCE_DIR}/src/game/Skills + ${CMAKE_SOURCE_DIR}/src/game/Social + ${CMAKE_SOURCE_DIR}/src/game/Spells + ${CMAKE_SOURCE_DIR}/src/game/Tools + ${CMAKE_SOURCE_DIR}/src/game/Trade + ${CMAKE_SOURCE_DIR}/src/game/VoiceChat + ${CMAKE_SOURCE_DIR}/src/game/Warden + ${CMAKE_SOURCE_DIR}/src/game/Weather + ${CMAKE_SOURCE_DIR}/src/game/World + ${CMAKE_SOURCE_DIR}/src/game/WorldHandlers + ${CMAKE_SOURCE_DIR}/src/game/movement + ${CMAKE_SOURCE_DIR}/src/game/vmap + ${CMAKE_SOURCE_DIR}/src/shared + ${CMAKE_SOURCE_DIR}/src/shared/Auth + ${CMAKE_SOURCE_DIR}/src/shared/Config + ${CMAKE_SOURCE_DIR}/src/shared/Common + ${CMAKE_SOURCE_DIR}/src/shared/Database + ${CMAKE_SOURCE_DIR}/src/shared/DataStores + ${CMAKE_SOURCE_DIR}/src/shared/Utilities + ${CMAKE_SOURCE_DIR}/src/shared/Log + ${CMAKE_SOURCE_DIR}/src/shared/Threading + ) + target_link_libraries(${LIBRARY_NAME} PUBLIC Bots) + add_dependencies(${LIBRARY_NAME} Bots) +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 +211,11 @@ if (BUILD_PLAYERBOT) add_definitions(-DBUILD_PLAYERBOT) endif() +# Define Ike3 Bots ENABLE_PLAYERBOTS if need +if (BUILD_IKE3_BOTS) + 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 7d2575a8d1..1634e750f4 100644 --- a/src/game/Chat/Chat.cpp +++ b/src/game/Chat/Chat.cpp @@ -35,6 +35,13 @@ #include "Pools/PoolManager.h" #include "GameEvents/GameEventMgr.h" +#ifdef ENABLE_PLAYERBOTS +#include "AhBot.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "GuildTaskMgr.h" +#endif + #include // Supported shift-links (client generated and server side) @@ -893,6 +900,15 @@ 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, "", NULL }, +#endif + { "rndbot", SEC_GAMEMASTER, true, &ChatHandler::HandleRandomPlayerbotCommand, "", NULL }, + { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", NULL }, + { "gtask", SEC_GAMEMASTER, true, &ChatHandler::HandleGuildTaskCommand, "", NULL }, + { "pmon", SEC_GAMEMASTER, true, &ChatHandler::HandlePerfMonCommand, "" }, #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 726d7fec11..9af2142228 100644 --- a/src/game/Chat/Chat.h +++ b/src/game/Chat/Chat.h @@ -101,6 +101,10 @@ class ChatHandler bool HasSentErrorMessage() const { return sentErrorMessage;} +#ifdef ENABLE_PLAYERBOTS + WorldSession* GetSession() { return m_session; } +#endif + /** * \brief Prepare SMSG_GM_MESSAGECHAT/SMSG_MESSAGECHAT * @@ -748,6 +752,14 @@ class ChatHandler bool HandlePlayerbotCommand(char* args); #endif +#ifdef ENABLE_PLAYERBOTS + bool HandlePlayerbotCommand(char* args); + bool HandleRandomPlayerbotCommand(char* args); + bool HandleAhBotCommand(char* args); + bool HandleGuildTaskCommand(char* args); + bool HandlePerfMonCommand(char* args); +#endif + bool HandleMmapPathCommand(char* args); bool HandleMmapLocCommand(char* args); bool HandleMmapLoadedTilesCommand(char* args); diff --git a/src/game/Chat/ChatHandler.cpp b/src/game/Chat/ChatHandler.cpp index 2e5ae4ee82..f490443262 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.h" +#include "RandomPlayerbotMgr.h" +#endif + bool WorldSession::CheckChatMessage(std::string& msg, bool addon/* = false*/) { #ifdef BUILD_PLAYERBOT @@ -260,6 +265,16 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) return; } } + +#ifdef ENABLE_PLAYERBOTS + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + else +#endif GetPlayer()->Whisper(msg, lang, player->GetObjectGuid()); @@ -299,6 +314,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()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + break; } case CHAT_MSG_GUILD: @@ -323,6 +351,19 @@ 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) + { + 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()); + } + } +#endif + break; } case CHAT_MSG_OFFICER: @@ -383,7 +424,22 @@ 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()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; + case CHAT_MSG_RAID_LEADER: { std::string msg; @@ -414,6 +470,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()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; case CHAT_MSG_RAID_WARNING: @@ -444,6 +514,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()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + } break; case CHAT_MSG_BATTLEGROUND: @@ -516,6 +600,20 @@ 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) + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player); + else + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, channel); + + // apply to own bots + if (_player->GetPlayerbotMgr() && chn->GetFlags() & 0x18) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg); + } +#endif } } } break; diff --git a/src/game/Entities/CharacterHandler.cpp b/src/game/Entities/CharacterHandler.cpp index 3b1d753e61..91880504ee 100644 --- a/src/game/Entities/CharacterHandler.cpp +++ b/src/game/Entities/CharacterHandler.cpp @@ -45,6 +45,11 @@ #include "PlayerBot/Base/PlayerbotMgr.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif + // config option SkipCinematics supported values enum CinematicsSkipMode { @@ -66,6 +71,97 @@ 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, 0, LOCALE_enUS, "", 0); + botSession->SetNoAnticheat(); + + 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; + } + PlayerbotMgr *mgr = bot->GetPlayerbotMgr(); + bot->SetPlayerbotMgr(NULL); + delete mgr; + 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); @@ -126,8 +222,28 @@ class CharacterHandler { if (!holder) return; - if (WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId())) - session->HandlePlayerLogin((LoginQueryHolder*)holder); + WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); + if (!session) + { + delete holder; + return; + } +#ifdef ENABLE_PLAYERBOTS + ObjectGuid guid = ((LoginQueryHolder*)holder)->GetGuid(); +#endif + session->HandlePlayerLogin((LoginQueryHolder*)holder); +#ifdef ENABLE_PLAYERBOTS + Player* player = sObjectMgr.GetPlayer(guid, true); + if (player) + { + if (!sRandomPlayerbotMgr.IsRandomBot(player)) + { + player->SetPlayerbotMgr(new PlayerbotMgr(player)); + player->GetPlayerbotMgr()->OnPlayerLogin(player); + } + sRandomPlayerbotMgr.OnPlayerLogin(player); + } +#endif } #ifdef BUILD_PLAYERBOT // This callback is different from the normal HandlePlayerLoginCallback in that it diff --git a/src/game/Entities/PetitionsHandler.cpp b/src/game/Entities/PetitionsHandler.cpp index 91f3946ed4..a8a040c83b 100644 --- a/src/game/Entities/PetitionsHandler.cpp +++ b/src/game/Entities/PetitionsHandler.cpp @@ -373,7 +373,11 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recv_data) // not allow sign another player from already sign player account queryResult = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE player_account = '%u' AND petitionguid = '%u'", GetAccountId(), petitionLowGuid); +#ifdef ENABLE_PLAYERBOTS + if (queryResult && !_player->GetPlayerbotAI()) +#else if (queryResult) +#endif { WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8 + 8 + 4)); data << ObjectGuid(petitionGuid); diff --git a/src/game/Entities/Player.cpp b/src/game/Entities/Player.cpp index 5580892b38..d105c2c3b1 100644 --- a/src/game/Entities/Player.cpp +++ b/src/game/Entities/Player.cpp @@ -68,6 +68,11 @@ #include "Config/Config.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif + #include #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -467,6 +472,10 @@ Player::Player(WorldSession* session): Unit(), m_taxiTracker(*this), m_mover(thi #ifdef BUILD_PLAYERBOT m_playerbotAI = 0; m_playerbotMgr = 0; +#endif +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = 0; + m_playerbotMgr = 0; #endif m_speakTime = 0; m_speakCount = 0; @@ -635,6 +644,11 @@ Player::Player(WorldSession* session): Unit(), m_taxiTracker(*this), m_mover(thi m_isDebuggingAreaTriggers = false; m_fishingSteps = 0; + +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = NULL; + m_playerbotMgr = NULL; +#endif } Player::~Player() @@ -679,6 +693,21 @@ Player::~Player() m_playerbotMgr = 0; } #endif + +#ifdef ENABLE_PLAYERBOTS + if (m_playerbotAI) { + { + delete m_playerbotAI; + } + m_playerbotAI = 0; + } + if (m_playerbotMgr) { + { + delete m_playerbotMgr; + } + m_playerbotMgr = 0; + } +#endif } void Player::CleanupsBeforeDelete() @@ -1586,6 +1615,20 @@ void Player::Update(const uint32 diff) #endif } +#ifdef ENABLE_PLAYERBOTS +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::Heartbeat() { Unit::Heartbeat(); @@ -8176,6 +8219,25 @@ Item* Player::GetItemByGuid(ObjectGuid guid) 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 NULL; +} + Item* Player::GetItemByPos(uint16 pos) const { uint8 bag = pos >> 8; @@ -12374,7 +12436,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()) { @@ -12550,6 +12616,9 @@ 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: @@ -12559,8 +12628,15 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, handled = sScriptDevAIMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest); break; } +#ifdef ENABLE_PLAYERBOTS + } +#endif +#ifdef ENABLE_PLAYERBOTS + if (this != questGiver && !handled && pQuest->GetQuestCompleteScript() != 0) +#else if (!handled && pQuest->GetQuestCompleteScript() != 0) +#endif GetMap()->ScriptsStart(SCRIPT_TYPE_QUEST_END, pQuest->GetQuestCompleteScript(), questGiver, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); // Find spell cast on spell reward if any, then find the appropriate caster and cast it diff --git a/src/game/Entities/Player.h b/src/game/Entities/Player.h index 74b5a72e2d..ebb7cb82cb 100644 --- a/src/game/Entities/Player.h +++ b/src/game/Entities/Player.h @@ -63,6 +63,11 @@ struct FactionTemplateEntry; #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +class PlayerbotAI; +class PlayerbotMgr; +#endif + struct AreaTrigger; typedef std::deque PlayerMails; @@ -914,6 +919,10 @@ class Player : public Unit void Update(const uint32 diff) override; void Heartbeat() override; +#ifdef ENABLE_PLAYERBOTS + void UpdateAI(const uint32 diff, bool minimal = false); +#endif + static bool BuildEnumData(QueryResult* result, WorldPacket& p_data); void SendInitialPacketsBeforeAddToMap(); @@ -1069,6 +1078,7 @@ class Player : public Unit uint8 FindEquipSlot(ItemPrototype const* proto, uint32 slot, bool swap) const; uint32 GetItemCount(uint32 item, bool inBankAlso = false, Item* skipItem = nullptr) const; Item* GetItemByGuid(ObjectGuid guid) const; + Item* GetItemByEntry(uint32 item) const; // only for special cases Item* GetItemByPos(uint16 pos) const; Item* GetItemByPos(uint8 bag, uint8 slot) const; Item* GetWeaponForAttack(WeaponAttackType attackType) const { return GetWeaponForAttack(attackType, false, false); } @@ -1355,6 +1365,10 @@ class Player : public Unit bool LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder); +#ifdef ENABLE_PLAYERBOTS + bool MinimalLoadFromDB(QueryResult *result, uint32 guid); +#endif + static uint32 GetZoneIdFromDB(ObjectGuid guid); static uint32 GetLevelFromDB(ObjectGuid guid); static bool LoadPositionFromDB(ObjectGuid guid, uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight); @@ -1726,6 +1740,7 @@ class Player : public Unit static Team TeamForRace(uint8 race); Team GetTeam() const { return m_team; } + PvpTeamIndex GetTeamId() const { return m_team == ALLIANCE ? TEAM_INDEX_ALLIANCE : TEAM_INDEX_HORDE; } static uint32 getFactionForRace(uint8 race); void setFactionForRace(uint8 race); @@ -2178,6 +2193,16 @@ class Player : public Unit bool IsInDuel() const { return duel && duel->startTime != 0; } #endif +#ifdef ENABLE_PLAYERBOTS + //EquipmentSets& GetEquipmentSets() { return m_EquipmentSets; } + void SetPlayerbotAI(PlayerbotAI* ai) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotAI = ai; } + PlayerbotAI* GetPlayerbotAI() { return m_playerbotAI; } + void SetPlayerbotMgr(PlayerbotMgr* mgr) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotMgr = mgr; } + PlayerbotMgr* GetPlayerbotMgr() { return m_playerbotMgr; } + void SetBotDeathTimer() { m_deathTimer = 0; } + //PlayerTalentMap& GetTalentMap(uint8 spec) { return m_talents[spec]; } +#endif + void SendLootError(ObjectGuid guid, LootError error) const; // cooldown system @@ -2479,6 +2504,11 @@ class Player : public Unit PlayerbotMgr* m_playerbotMgr; #endif +#ifdef ENABLE_PLAYERBOTS + PlayerbotAI* m_playerbotAI; + PlayerbotMgr* m_playerbotMgr; +#endif + // Homebind coordinates uint32 m_homebindMapId; uint16 m_homebindAreaId; diff --git a/src/game/Entities/Unit.cpp b/src/game/Entities/Unit.cpp index b6edded87f..d083ff2a0d 100644 --- a/src/game/Entities/Unit.cpp +++ b/src/game/Entities/Unit.cpp @@ -55,6 +55,11 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "GuildTaskMgr.h" +#endif + #include #include #include @@ -1343,6 +1348,12 @@ void Unit::JustKilledCreature(Unit* killer, Creature* victim, Player* responsibl if (BattleGround* bg = responsiblePlayer->GetBattleGround()) bg->HandleKillUnit(victim, responsiblePlayer); +#ifdef ENABLE_PLAYERBOTS + // Guild Task check + if (responsiblePlayer && sPlayerbotAIConfig.guildTaskEnabled) + sGuildTaskMgr.CheckKillTask(responsiblePlayer, victim); +#endif + // Notify the outdoor pvp script if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(responsiblePlayer ? responsiblePlayer->GetCachedZoneId() : victim->GetZoneId())) outdoorPvP->HandleCreatureDeath(victim); diff --git a/src/game/Groups/Group.h b/src/game/Groups/Group.h index 38e8a1f0a5..8e5d186469 100644 --- a/src/game/Groups/Group.h +++ b/src/game/Groups/Group.h @@ -289,6 +289,10 @@ class Group void CalculateLFGRoles(LFGGroupQueueInfo& data); bool FillPremadeLFG(ObjectGuid const& plrGuid, Classes playerClass, LfgRoles requiredRole, uint32& InitRoles, uint32& DpsCount, std::list& processed); +#ifdef ENABLE_PLAYERBOTS + ObjectGuid GetTargetIcon(int index) { return m_targetIcons[index]; } +#endif + 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 3d10b18b56..df8b3003e4 100644 --- a/src/game/Maps/Map.cpp +++ b/src/game/Maps/Map.cpp @@ -46,6 +46,10 @@ #include +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#endif + Map::~Map() { UnloadAll(true); @@ -155,7 +159,7 @@ Map::Map(uint32 id, time_t expiry, uint32 InstanceId) 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), - m_variableManager(this) + m_variableManager(this), m_activeAreasTimer(0), hasRealPlayers(false) { m_weatherSystem = new WeatherSystem(this); } @@ -728,18 +732,105 @@ void Map::Update(const uint32& t_diff) #endif } + // active areas timer + m_activeAreasTimer += t_diff; + if (m_activeAreasTimer >= 10000) + { + m_activeAreasTimer = 0; + m_activeAreas.clear(); +} + + vector ActiveZones; + if (!m_activeAreasTimer && IsContinent() && HasRealPlayers()) + { + for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) + { + Player* plr = m_mapRefIter->getSource(); + if (plr && plr->IsInWorld()) + { + if (plr->GetPlayerbotAI() && !plr->GetPlayerbotAI()->IsRealPlayer()) + continue; + + if (plr->isAFK()) + continue; + + if (!plr->isGMVisible()) + continue; + + if (find(ActiveZones.begin(), ActiveZones.end(), plr->GetZoneId()) == ActiveZones.end()) + ActiveZones.push_back(plr->GetZoneId()); + + ContinentArea activeArea = sMapMgr.GetContinentInstanceId(GetId(), plr->GetPositionX(), plr->GetPositionY()); + // check active area + if (activeArea != MAP_NO_AREA) + { + if (!HasActiveAreas(activeArea)) + m_activeAreas.push_back(activeArea); + } + } + } + } + + bool hasPlayers = false; + uint32 activeChars = 0; + uint32 maxDiff = sWorld.GetMaxDiff(); + bool updateAI = urand(0, (HasRealPlayers() ? maxDiff : (maxDiff * 3))) < 10; /// 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()) + { + bool isInActiveArea = false; + if (!plr->GetPlayerbotAI() || plr->GetPlayerbotAI()->IsRealPlayer()) + { + isInActiveArea = true; + + hasPlayers = true; + + } + else if (HasRealPlayers()) + { + ContinentArea activeArea = MAP_NO_AREA; + if (IsContinent()) + activeArea = sMapMgr.GetContinentInstanceId(GetId(), plr->GetPositionX(), plr->GetPositionY()); + + isInActiveArea = IsContinent() ? (activeArea == MAP_NO_AREA ? false : HasActiveAreas(activeArea)) : HasRealPlayers(); + + if (isInActiveArea) + { + if (maxDiff > 200 && IsContinent()) + { + if (find(ActiveZones.begin(), ActiveZones.end(), plr->GetZoneId()) == ActiveZones.end()) + isInActiveArea = false; + } + } + } + if (plr->GetPlayerbotAI() && plr->GetPlayerbotAI()->HasRealPlayerMaster()) + isInActiveArea = true; + if (plr->InBattleGroundQueue() || plr->InBattleGround()) + isInActiveArea = true; + + if (isInActiveArea) + activeChars++; + plr->Update(t_diff); + plr->UpdateAI(t_diff, !(isInActiveArea || updateAI || plr->IsInCombat())); + } + } + + hasRealPlayers = hasPlayers; + + if (IsContinent() && HasRealPlayers() && HasActiveAreas() && !m_activeAreasTimer) + { + sLog.outBasic("Map %u: Active Areas:Zones - %u:%u", GetId(), m_activeAreas.size(), ActiveZones.size()); + sLog.outBasic("Map %u: Active Areas Chars - %u of %u", GetId(), activeChars, m_mapRefManager.getSize()); } 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; VisitNearbyCellsOf(player, grid_object_update, world_object_update); @@ -750,6 +841,7 @@ void Map::Update(const uint32& t_diff) } // non-player active objects + bool updateObj = urand(0, (HasRealPlayers() ? maxDiff : (maxDiff * 3))) < 10; if (!m_activeNonPlayers.empty()) { for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end();) @@ -764,6 +856,27 @@ void Map::Update(const uint32& t_diff) if (!obj->IsInWorld() || !obj->IsPositionValid()) continue; + // skip objects if world is laggy + if (IsContinent() && maxDiff > 100) + { + bool isInActiveArea = false; + + ContinentArea activeArea = MAP_NO_AREA; + if (IsContinent()) + activeArea = sMapMgr.GetContinentInstanceId(GetId(), obj->GetPositionX(), obj->GetPositionY()); + + isInActiveArea = IsContinent() ? (activeArea == MAP_NO_AREA ? false : HasActiveAreas(activeArea)) : HasRealPlayers(); + + if (isInActiveArea && IsContinent()) + { + if (maxDiff > 150 && find(ActiveZones.begin(), ActiveZones.end(), obj->GetZoneId()) == ActiveZones.end()) + isInActiveArea = false; + } + + if (!isInActiveArea && !updateObj) + continue; + } + objToUpdate.insert(obj); // lets update mobs/objects in ALL visible cells around player! @@ -875,7 +988,12 @@ void Map::Remove(Player* player, bool remove) SendRemoveTransports(player); UpdateObjectVisibility(player, cell, p); +#ifdef ENABLE_PLAYERBOTS + if (!player->GetPlayerbotAI()) + player->ResetMap(); +#else player->ResetMap(); +#endif if (remove) DeleteFromWorld(player); } diff --git a/src/game/Maps/Map.h b/src/game/Maps/Map.h index 966dcaf7c7..cf1e37d8ac 100644 --- a/src/game/Maps/Map.h +++ b/src/game/Maps/Map.h @@ -63,6 +63,33 @@ class GenericTransport; namespace MaNGOS { struct ObjectUpdater; } class Transport; +enum ContinentArea +{ + MAP_NO_AREA = 0, + + MAP0_TOP_NORTH = 1, + MAP0_MIDDLE_NORTH = 2, + MAP0_IRONFORGE_AREA = 3, + MAP0_MIDDLE = 4, // Burning stepps, Redridge monts, Blasted lands + MAP0_STORMWIND_AREA = 5, // Stormwind, Elwynn forest, Redridge Mts + MAP0_SOUTH = 6, // Southern phase of the continent + + MAP1_TELDRASSIL = 11, // Teldrassil + MAP1_NORTH = 12, // Stonetalon, Ashenvale, Darkshore, Felwood, Moonglade, Winterspring, Azshara, Desolace + MAP1_DUROTAR = 13, // Durotar + MAP1_UPPER_MIDDLE = 14, // Mulgore, Barrens, Dustwallow Marsh + MAP1_LOWER_MIDDLE = 15, // Feralas, 1K needles + MAP1_VALLEY = 16, // Orc and Troll starting area + MAP1_ORGRIMMAR = 17, // Orgrimmar (on its own) + MAP1_SOUTH = 18, // Silithus, Un'goro and Tanaris + MAP1_GMISLAND = 19, // GM island + + MAP0_FIRST = 1, + MAP0_LAST = 6, + MAP1_FIRST = 11, + MAP1_LAST = 19, +}; + // GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some platform #if defined( __GNUC__ ) #pragma pack(1) @@ -132,6 +159,10 @@ class Map : public GridRefManager void VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor &gridVisitor, TypeContainerVisitor &worldVisitor); virtual void Update(const uint32&); +#ifdef ENABLE_PLAYERBOTS + bool HasRealPlayers() { return hasRealPlayers; } +#endif + void MessageBroadcast(Player const*, WorldPacket const&, bool to_self); void MessageBroadcast(WorldObject const*, WorldPacket const&); void MessageDistBroadcast(Player const*, WorldPacket const&, float dist, bool to_self, bool own_team_only = false); @@ -372,6 +403,8 @@ 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 + + bool HasActiveAreas(ContinentArea areaId = MAP_NO_AREA) { if (areaId == MAP_NO_AREA) { return !m_activeAreas.empty(); } else { return !(find(m_activeAreas.begin(), m_activeAreas.end(), areaId) == m_activeAreas.end()); } } private: void LoadMapAndVMap(int gx, int gy); @@ -499,6 +532,11 @@ class Map : public GridRefManager std::shared_ptr m_spellListContainer; WorldStateVariableManager m_variableManager; + + std::vector m_activeAreas; + uint32 m_activeAreasTimer; + + bool hasRealPlayers; }; class WorldMap : public Map diff --git a/src/game/Maps/MapManager.cpp b/src/game/Maps/MapManager.cpp index b42f4f02ba..29df08372a 100644 --- a/src/game/Maps/MapManager.cpp +++ b/src/game/Maps/MapManager.cpp @@ -392,6 +392,270 @@ BattleGroundMap* MapManager::CreateBattleGroundMap(uint32 id, uint32 InstanceId, return map; } +bool IsNorthTo(float x, float y, float const* limits, int count /* last case is limits[2*count - 1] */) +{ + int insideCount = 0; + for (int i = 0; i < count - 1; ++i) + { + if ((limits[2 * i + 1] < y && y < limits[2 * i + 3]) || (limits[2 * i + 1] > y && y > limits[2 * i + 3])) + { + float threshold = limits[2 * i] + (limits[2 * i + 2] - limits[2 * i]) * (y - limits[2 * i + 1]) / (limits[2 * i + 3] - limits[2 * i + 1]); + if (x > threshold) + ++insideCount; + } + } + return insideCount % 2 == 1; +} + +ContinentArea MapManager::GetContinentInstanceId(uint32 mapId, float x, float y, bool* transitionArea) +{ + if (transitionArea) + *transitionArea = false; + + // Y = horizontal axis on wow ... + switch (mapId) + { + case 0: + { + static float const topNorthSouthLimit[] = { + 2032.048340f, -6927.750000f, + 1634.863403f, -6157.505371f, + 1109.519775f, -5181.036133f, + 1315.204712f, -4096.020508f, + 1073.089233f, -3372.571533f, + 825.833191f, -3125.778809f, + 657.343994f, -2314.813232f, + 424.736145f, -1888.283691f, + 744.395813f, -1647.935425f, + 1424.160645f, -654.948181f, + 1447.065308f, -169.751358f, + 1208.715454f, 189.748703f, + 1596.240356f, 998.616699f, + 1577.923706f, 1293.419922f, + 1458.520264f, 1727.373291f, + 1591.916138f, 3728.139404f + }; + static float const ironforgeAreaSouthLimit[] = { + -7491.33f, 3093.74f, + -7472.04f, -391.88f, + -6366.68f, -730.10f, + -6063.96f, -1411.76f, + -6087.62f, -2190.21f, + -6349.54f, -2533.66f, + -6308.63f, -3049.32f, + -6107.82f, -3345.30f, + -6008.49f, -3590.52f, + -5989.37f, -4312.29f, + -5806.26f, -5864.11f + }; + static float const stormwindAreaNorthLimit[] = { + -8004.25f, 3714.11f, + -8075.00f, -179.00f, + -8638.00f, 169.00f, + -9044.00f, 35.00f, + -9068.00f, -125.00f, + -9094.00f, -147.00f, + -9206.00f, -290.00f, + -9097.00f, -510.00f, + -8739.00f, -501.00f, + -8725.50f, -1618.45f, + -9810.40f, -1698.41f, + -10049.60f, -1740.40f, + -10670.61f, -1692.51f, + -10908.48f, -1563.87f, + -13006.40f, -1622.80f, + -12863.23f, -4798.42f + }; + static float const stormwindAreaSouthLimit[] = { + -8725.337891f, 3535.624023f, + -9525.699219f, 910.132568f, + -9796.953125f, 839.069580f, + -9946.341797f, 743.102844f, + -10287.361328f, 760.076477f, + -10083.828125f, 380.389893f, + -10148.072266f, 80.056450f, + -10014.583984f, -161.638519f, + -9978.146484f, -361.638031f, + -9877.489258f, -563.304871f, + -9980.967773f, -1128.510498f, + -9991.717773f, -1428.793213f, + -9887.579102f, -1618.514038f, + -10169.600586f, -1801.582031f, + -9966.274414f, -2227.197754f, + -9861.309570f, -2989.841064f, + -9944.026367f, -3205.886963f, + -9610.209961f, -3648.369385f, + -7949.329590f, -4081.389404f, + -7910.859375f, -5855.578125f + }; + if (IsNorthTo(x, y, topNorthSouthLimit, sizeof(topNorthSouthLimit) / (2 * sizeof(float)))) + return MAP0_TOP_NORTH; + if (x > -2521) + return MAP0_MIDDLE_NORTH; + if (IsNorthTo(x, y, ironforgeAreaSouthLimit, sizeof(ironforgeAreaSouthLimit) / (2 * sizeof(float)))) + return MAP0_IRONFORGE_AREA; + if (IsNorthTo(x, y, stormwindAreaNorthLimit, sizeof(stormwindAreaNorthLimit) / (2 * sizeof(float)))) + return MAP0_MIDDLE; + if (IsNorthTo(x, y, stormwindAreaSouthLimit, sizeof(stormwindAreaNorthLimit) / (2 * sizeof(float)))) + return MAP0_STORMWIND_AREA; + return MAP0_SOUTH; + } + case 1: + { + static float const teldrassilSouthLimit[] = { + 7916.0f, 3475.0f, + 7916.0f, 1000.0f, + 8283.0f, -501.0f, + 8804.0f, -1098.0f + }; + static float const northMiddleLimit[] = { + -2280.00f, 4054.00f, + -2401.00f, 2365.00f, + -2432.00f, 1338.00f, + -2286.00f, 769.00f, + -2137.00f, 662.00f, + -2044.54f, 489.86f, + -1808.52f, 436.39f, + -1754.85f, 504.55f, + -1094.55f, 651.75f, + -747.46f, 647.73f, + -685.55f, 408.43f, + -311.38f, 114.43f, + -358.40f, -587.42f, + -377.92f, -748.70f, + -512.57f, -919.49f, + -280.65f, -1008.87f, + -81.29f, -930.89f, + 284.31f, -1105.39f, + 568.86f, -892.28f, + 1211.09f, -1135.55f, + 879.60f, -2110.18f, + 788.96f, -2276.02f, + 899.68f, -2625.56f, + 1281.54f, -2689.42f, + 1521.82f, -3047.85f, + 1424.22f, -3365.69f, + 1694.11f, -3615.20f, + 2373.78f, -4019.96f, + 2388.13f, -5124.35f, + 2193.79f, -5484.38f, + 1703.57f, -5510.53f, + 1497.59f, -6376.56f, + 1368.00f, -8530.00f + }; + static float const durotarSouthLimit[] = { + 2755.00f, -3766.00f, + 2225.00f, -3596.00f, + 1762.00f, -3746.00f, + 1564.00f, -3943.00f, + 1184.00f, -3915.00f, + 737.00f, -3782.00f, + -75.00f, -3742.00f, + -263.00f, -3836.00f, + -173.00f, -4064.00f, + -81.00f, -4091.00f, + -49.00f, -4089.00f, + -16.00f, -4187.00f, + -5.00f, -4192.00f, + -14.00f, -4551.00f, + -397.00f, -4601.00f, + -522.00f, -4583.00f, + -668.00f, -4539.00f, + -790.00f, -4502.00f, + -1176.00f, -4213.00f, + -1387.00f, -4674.00f, + -2243.00f, -6046.00f + }; + static float const valleyoftrialsSouthLimit[] = { + -324.00f, -3869.00f, + -774.00f, -3992.00f, + -965.00f, -4290.00f, + -932.00f, -4349.00f, + -828.00f, -4414.00f, + -661.00f, -4541.00f, + -521.00f, -4582.00f + }; + static float const middleToSouthLimit[] = { + -2402.01f, 4255.70f, + -2475.933105f, 3199.568359f, // Desolace + -2344.124023f, 1756.164307f, + -2826.438965f, 403.824738f, // Mulgore + -3472.819580f, 182.522476f, // Feralas + -4365.006836f, -1602.575439f, // the Barrens + -4515.219727f, -1681.356079f, + -4543.093750f, -1882.869385f, // Thousand Needles + -4824.16f, -2310.11f, + -5102.913574f, -2647.062744f, + -5248.286621f, -3034.536377f, + -5246.920898f, -3339.139893f, + -5459.449707f, -4920.155273f, // Tanaris + -5437.00f, -5863.00f + }; + + static float const orgrimmarSouthLimit[] = { + 2132.5076f, -3912.2478f, + 1944.4298f, -3855.2583f, + 1735.6906f, -3834.2417f, + 1654.3671f, -3380.9902f, + 1593.9861f, -3975.5413f, + 1439.2548f, -4249.6923f, + 1436.3106f, -4007.8950f, + 1393.3199f, -4196.0625f, + 1445.2428f, -4373.9052f, + 1407.2349f, -4429.4145f, + 1464.7142f, -4545.2875f, + 1584.1331f, -4596.8764f, + 1716.8065f, -4601.1323f, + 1875.8312f, -4788.7187f, + 1979.7647f, -4883.4585f, + 2219.1562f, -4854.3330f + }; + + static float const feralasThousandNeedlesSouthLimit[] = { + -6495.4995f, -4711.981f, + -6674.9995f, -4515.0019f, + -6769.5717f, -4122.4272f, + -6838.2651f, -3874.2792f, + -6851.1314f, -3659.1179f, + -6624.6845f, -3063.3843f, + -6416.9067f, -2570.1301f, + -5959.8466f, -2287.2634f, + -5947.9135f, -1866.5028f, + -5947.9135f, -820.4881f, + -5876.7114f, -3.5138f, + -5876.7114f, 917.6407f, + -6099.3603f, 1153.2884f, + -6021.8989f, 1638.1809f, + -6091.6176f, 2335.8892f, + -6744.9946f, 2393.4855f, + -6973.8608f, 3077.0281f, + -7068.7241f, 4376.2304f, + -7142.1211f, 4808.4331f + }; + + if (IsNorthTo(x, y, teldrassilSouthLimit, sizeof(teldrassilSouthLimit) / (2 * sizeof(float)))) + return MAP1_TELDRASSIL; + if (IsNorthTo(x, y, northMiddleLimit, sizeof(northMiddleLimit) / (2 * sizeof(float)))) + return MAP1_NORTH; + if (IsNorthTo(x, y, orgrimmarSouthLimit, sizeof(orgrimmarSouthLimit) / (2 * sizeof(float)))) + return MAP1_ORGRIMMAR; + if (IsNorthTo(x, y, durotarSouthLimit, sizeof(durotarSouthLimit) / (2 * sizeof(float)))) + return MAP1_DUROTAR; + if (IsNorthTo(x, y, valleyoftrialsSouthLimit, sizeof(valleyoftrialsSouthLimit) / (2 * sizeof(float)))) + return MAP1_VALLEY; + if (IsNorthTo(x, y, middleToSouthLimit, sizeof(middleToSouthLimit) / (2 * sizeof(float)))) + return MAP1_UPPER_MIDDLE; + if (IsNorthTo(x, y, feralasThousandNeedlesSouthLimit, sizeof(feralasThousandNeedlesSouthLimit) / (2 * sizeof(float)))) + return MAP1_LOWER_MIDDLE; + if (y > 15000.0f) + return MAP1_GMISLAND; + else + return MAP1_SOUTH; + } + } + return MAP_NO_AREA; +} + void MapManager::DoForAllMapsWithMapId(uint32 mapId, std::function worker) { MapMapType::const_iterator start = i_maps.lower_bound(MapID(mapId, 0)); diff --git a/src/game/Maps/MapManager.h b/src/game/Maps/MapManager.h index fea2a6aab3..5aa5aee6fa 100644 --- a/src/game/Maps/MapManager.h +++ b/src/game/Maps/MapManager.h @@ -62,6 +62,7 @@ class MapManager : public MaNGOS::Singleton MapMapType; void CreateContinents(); + ContinentArea GetContinentInstanceId(uint32 mapId, float x, float y, bool* transitionArea = nullptr); Map* CreateMap(uint32, const WorldObject* obj); Map* CreateBgMap(uint32 mapid, BattleGround* bg); Map* FindMap(uint32 mapid, uint32 instanceId = 0) const; diff --git a/src/game/MotionGenerators/MovementHandler.cpp b/src/game/MotionGenerators/MovementHandler.cpp index 7d3a215d6a..2a5490df70 100644 --- a/src/game/MotionGenerators/MovementHandler.cpp +++ b/src/game/MotionGenerators/MovementHandler.cpp @@ -276,9 +276,22 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) WorldLocation const& dest = plMover->GetTeleportDest(); + // send MSG_MOVE_TELEPORT to observers around old position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); + plMover->SetDelayedZoneUpdate(false, 0); plMover->SetPosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation, true); + plMover->m_movementInfo.ChangePosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); + +#ifdef ENABLE_PLAYERBOTS + // interrupt moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) + { + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Interrupt(*plMover); + } +#endif plMover->SetFallInformation(0, dest.coord_z); @@ -291,15 +304,26 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) uint32 newzone, newarea; plMover->GetZoneAndAreaId(newzone, newarea); - plMover->UpdateZone(newzone, newarea); // new zone if (old_zone != newzone) + plMover->UpdateZone(newzone, newarea); + + // honorless target + if (plMover->pvpInfo.inPvPEnforcedArea) + plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); + +#ifdef ENABLE_PLAYERBOTS + // reset moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) { - // honorless target - if (plMover->pvpInfo.inPvPEnforcedArea) - plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Reset(*plMover); } +#endif + + // send MSG_MOVE_TELEPORT to observers around new position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); m_anticheat->Teleport({ dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation }); @@ -533,6 +557,17 @@ void WorldSession::SendKnockBack(Unit* who, float angle, float horizontalSpeed, m_anticheat->KnockBack(horizontalSpeed, -verticalSpeed, vcos, vsin); } +void WorldSession::SendTeleportToObservers(float x, float y, float z, float orientation) +{ + WorldPacket data(MSG_MOVE_TELEPORT, 64); + data << _player->GetPackGUID(); + // copy moveinfo 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); +} + 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 5cf6910fb5..1fd4963132 100644 --- a/src/game/MotionGenerators/PathFinder.cpp +++ b/src/game/MotionGenerators/PathFinder.cpp @@ -32,6 +32,27 @@ #include ////////////////// PathFinder ////////////////// +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) +{ + //MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + //m_defaultNavMeshQuery = mmap->GetNavMeshQuery(mapId, instanceId); + + //createFilter(); +} + +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(); +} PathFinder::PathFinder(const Unit* owner, bool ignoreNormalization) : m_type(PATHFIND_BLANK), m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths @@ -52,12 +73,13 @@ PathFinder::PathFinder(const Unit* owner, bool ignoreNormalization) : PathFinder::~PathFinder() { - DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); + if (m_sourceUnit) + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); } 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 +92,15 @@ void PathFinder::SetCurrentNavMesh() m_navMeshQuery = m_defaultNavMeshQuery; } + 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; + if (m_navMeshQuery) m_navMesh = m_navMeshQuery->getAttachedNavMesh(); } @@ -94,13 +125,14 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force return false; #ifdef BUILD_METRICS - metric::duration meas("pathfinder.calculate", { - { "entry", std::to_string(m_sourceUnit->GetEntry()) }, - { "guid", std::to_string(m_sourceUnit->GetGUIDLow()) }, - { "unit_type", std::to_string(m_sourceUnit->GetGUIDHigh()) }, - { "map_id", std::to_string(m_sourceUnit->GetMapId()) }, - { "instance_id", std::to_string(m_sourceUnit->GetInstanceId()) } - }, 1000); + if (m_sourceUnit) + metric::duration meas("pathfinder.calculate", { + { "entry", std::to_string(m_sourceUnit->GetEntry()) }, + { "guid", std::to_string(m_sourceUnit->GetGUIDLow()) }, + { "unit_type", std::to_string(m_sourceUnit->GetGUIDHigh()) }, + { "map_id", std::to_string(m_sourceUnit->GetMapId()) }, + { "instance_id", std::to_string(m_sourceUnit->GetInstanceId()) } + }, 1000); #endif //if (GenericTransport* transport = m_sourceUnit->GetTransport()) @@ -115,11 +147,12 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force SetCurrentNavMesh(); - DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %u \n", m_sourceUnit->GetGUIDLow()); + if (m_sourceUnit) + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %u \n", m_sourceUnit->GetGUIDLow()); // make sure navMesh works - we can run on map w/o mmap // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) - if (!m_navMesh || !m_navMeshQuery || m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING) || + if (!m_navMesh || !m_navMeshQuery || (m_sourceUnit && m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING)) || !HaveTile(start) || !HaveTile(dest)) { BuildShortcut(); @@ -133,7 +166,129 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force return true; } -dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef* polyPath, uint32 polyPathSize, const float* point, float* distance, const float maxDist) const +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); +} + +dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef* polyPath, uint32 polyPathSize, const float* point, float* distance) const { if (!polyPath || !polyPathSize) return INVALID_POLYREF; @@ -216,7 +371,7 @@ dtPolyRef PathFinder::getPolyByLocation(const float* point, float* distance) void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) { // *** getting start/end poly logic *** - if (m_sourceUnit->GetMap()->IsDungeon()) + if (m_sourceUnit && m_sourceUnit->GetMap()->IsDungeon()) { float distance = sqrt((endPos.x - startPos.x) * (endPos.x - startPos.x) + (endPos.y - startPos.y) * (endPos.y - startPos.y) + (endPos.z - startPos.z) * (endPos.z - startPos.z)); if (distance > 300.f) @@ -229,8 +384,8 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) m_pathPolyRefs.resize(m_pointPathLimit); } float distToStartPoly, distToEndPoly; - float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; - float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; + float startPoint[VERTEX_SIZE] = { startPos.y, startPos.z, startPos.x }; + float endPoint[VERTEX_SIZE] = { endPos.y, endPos.z, endPos.x }; dtPolyRef startPoly = getPolyByLocation(startPoint, &distToStartPoly); dtPolyRef endPoly = getPolyByLocation(endPoint, &distToEndPoly); @@ -246,10 +401,10 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) BuildShortcut(); // Check for swimming or flying shortcut - if ((startPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(startPos.x, startPos.y, startPos.z)) || - (endPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(endPos.x, endPos.y, endPos.z))) + if (m_sourceUnit && ((startPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(startPos.x, startPos.y, startPos.z)) || + (endPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(endPos.x, endPos.y, endPos.z)))) m_type = m_sourceUnit->CanSwim() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; - else + else if (m_sourceUnit) { if (m_sourceUnit->GetTypeId() != TYPEID_PLAYER) m_type = m_sourceUnit->CanFly() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; @@ -268,7 +423,7 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) bool buildShotrcut = false; Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; - if (m_sourceUnit->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) + if (m_sourceUnit && m_sourceUnit->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: underWater case\n"); if (m_sourceUnit->CanSwim()) @@ -277,7 +432,7 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) else { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: flying case\n"); - if (m_sourceUnit->CanFly()) + if (m_sourceUnit && m_sourceUnit->CanFly()) buildShotrcut = true; } @@ -330,10 +485,11 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) // here to catch few bugs if (m_pathPolyRefs[pathStartIndex] == INVALID_POLYREF) { - sLog.outError("Invalid poly ref in BuildPolyPath. polyLength: %u, pathStartIndex: %u," - " startPos: %s, endPos: %s, mapId: %u", - m_polyLength, pathStartIndex, startPos.toString().c_str(), endPos.toString().c_str(), - m_sourceUnit->GetMapId()); + if (m_sourceUnit) + sLog.outError("Invalid poly ref in BuildPolyPath. polyLength: %u, pathStartIndex: %u," + " startPos: %s, endPos: %s, mapId: %u", + m_polyLength, pathStartIndex, startPos.toString().c_str(), endPos.toString().c_str(), + m_sourceUnit->GetMapId()); break; } @@ -470,30 +626,30 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) if (!m_straightLine) { dtResult = m_navMeshQuery->findPath( - startPoly, // start polygon - endPoly, // end polygon - startPoint, // start position - endPoint, // end position - &m_filter, // polygon search filter - m_pathPolyRefs.data(), // [out] path - (int*)&m_polyLength, - m_pointPathLimit); // max number of polygons in output path + startPoly, // start polygon + endPoly, // end polygon + startPoint, // start position + endPoint, // end position + &m_filter, // polygon search filter + m_pathPolyRefs.data(), // [out] path + (int*)&m_polyLength, + m_pointPathLimit / 2); // max number of polygons in output path } else { float hit = 0.0f; - float hitNormal[3] = {0.0f, 0.0f, 0.0f}; + float hitNormal[3] = { 0.0f, 0.0f, 0.0f }; dtResult = m_navMeshQuery->raycast( - startPoly, - startPoint, - endPoint, - &m_filter, - &hit, - hitNormal, - m_pathPolyRefs.data(), - (int*)&m_polyLength, - m_pointPathLimit); + startPoly, + startPoint, + endPoint, + &m_filter, + &hit, + hitNormal, + m_pathPolyRefs.data(), + (int*)&m_polyLength, + m_pointPathLimit / 2); // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != FLT_MAX) @@ -542,7 +698,8 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) if (!m_polyLength || dtStatusFailed(dtResult)) { // 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()); + if (m_sourceUnit) + sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); BuildShortcut(); m_type = PATHFIND_NOPATH; return; @@ -730,7 +887,7 @@ void PathFinder::BuildPointPath(const float* startPoint, const float* endPoint) // force the given destination, if needed if (m_forceDestination && - (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) + (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath if (dist3DSqr(getActualEndPosition(), getEndPosition()) < 0.3f * dist3DSqr(getStartPosition(), getEndPosition())) @@ -754,10 +911,12 @@ 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(); + GenericTransport* transport; + if (m_sourceUnit) + transport = m_sourceUnit->GetTransport(); for (auto& m_pathPoint : m_pathPoints) { @@ -792,21 +951,34 @@ void PathFinder::createFilter() uint16 includeFlags = 0; uint16 excludeFlags = 0; - if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) + 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 agro + } + 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; // walk + 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_PLAYER) - { - // perfect support not possible, just stay 'safe' - includeFlags |= (NAV_GROUND | NAV_WATER); - } m_filter.setIncludeFlags(includeFlags); m_filter.setExcludeFlags(excludeFlags); @@ -818,12 +990,12 @@ 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(), - m_sourceUnit->GetPositionY(), - m_sourceUnit->GetPositionZ()); + m_sourceUnit->GetPositionY(), + m_sourceUnit->GetPositionZ()); m_filter.setIncludeFlags(includedFlags); } @@ -832,7 +1004,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) @@ -850,11 +1022,11 @@ 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; - float point[VERTEX_SIZE] = {p.y, p.z, p.x}; + float point[VERTEX_SIZE] = { p.y, p.z, p.x }; m_navMesh->calcTileLoc(point, &tx, &ty); @@ -916,8 +1088,8 @@ uint32 PathFinder::fixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, } bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, - float minTargetDist, const dtPolyRef* path, uint32 pathSize, - float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) const + float minTargetDist, const dtPolyRef* path, uint32 pathSize, + float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) const { // Find steer target. static const uint32 MAX_STEER_POINTS = 3; @@ -926,7 +1098,7 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, dtPolyRef steerPathPolys[MAX_STEER_POINTS]; uint32 nsteerPath = 0; dtStatus dtResult = m_navMeshQuery->findStraightPath(startPos, endPos, path, pathSize, - steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); + steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); if (!nsteerPath || dtStatusFailed(dtResult)) return false; @@ -936,7 +1108,7 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || - !inRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) + !inRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) break; ++ns; } @@ -953,8 +1125,8 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, } dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, - const dtPolyRef* polyPath, uint32 polyPathSize, - float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) + const dtPolyRef* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) { *smoothPathSize = 0; uint32 nsmoothPath = 0; @@ -1074,7 +1246,7 @@ dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, *smoothPathSize = nsmoothPath; // this is most likely a loop - return nsmoothPath < m_pointPathLimit ? DT_SUCCESS : DT_FAILURE; + return nsmoothPath <= m_pointPathLimit ? DT_SUCCESS : DT_FAILURE; } void PathFinder::ComputePathToRandomPoint(Vector3 const& startPoint, float maxRange) diff --git a/src/game/MotionGenerators/PathFinder.h b/src/game/MotionGenerators/PathFinder.h index 8f1074b9d1..fddaaac26b 100644 --- a/src/game/MotionGenerators/PathFinder.h +++ b/src/game/MotionGenerators/PathFinder.h @@ -16,6 +16,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#ifndef IKE_PATHFINDER +#define IKE_PATHFINDER +#endif + #ifndef MANGOS_PATH_FINDER_H #define MANGOS_PATH_FINDER_H @@ -34,8 +38,8 @@ 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 -#define MAX_PATH_LENGTH 74 -#define MAX_POINT_PATH_LENGTH 74 +#define MAX_PATH_LENGTH 148 //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_POINT_PATH_LENGTH 148 #define SMOOTH_PATH_STEP_SIZE 4.0f #define SMOOTH_PATH_SLOP 0.3f @@ -65,6 +69,8 @@ enum PathType class PathFinder { public: + PathFinder(); + PathFinder(uint32 mapId, uint32 instanceId = 0); PathFinder(Unit const* owner, bool ignoreNormalization = false); ~PathFinder(); @@ -88,6 +94,11 @@ class PathFinder PointsArray& getPath() { return m_pathPoints; } PathType getPathType() const { return m_type; } + 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); private: PointsArray m_pathPoints; // our actual (x,y,z) path to the target diff --git a/src/game/Movement/typedefs.h b/src/game/Movement/typedefs.h index 67e5fe550c..e91b04fbf8 100644 --- a/src/game/Movement/typedefs.h +++ b/src/game/Movement/typedefs.h @@ -67,6 +67,8 @@ namespace Movement }; typedef counter UInt32Counter; + + extern float computeFallElevation(float t_passed, bool isSafeFall, float start_velocity); } #endif // MANGOSSERVER_TYPEDEFS_H diff --git a/src/game/Server/DBCStores.cpp b/src/game/Server/DBCStores.cpp index f20953c36c..a43fca9d1a 100644 --- a/src/game/Server/DBCStores.cpp +++ b/src/game/Server/DBCStores.cpp @@ -83,6 +83,12 @@ DBCStorage sDurabilityCostsStore(DurabilityCostsfmt); DBCStorage sEmotesStore(EmotesEntryfmt); DBCStorage sEmotesTextStore(EmotesTextEntryfmt); +#ifdef ENABLE_PLAYERBOTS +typedef std::tuple EmotesTextSoundKey; +static std::map sEmotesTextSoundMap; +DBCStorage sEmotesTextSoundStore(EmotesTextSoundEntryfmt); +#endif + typedef std::map FactionTeamMap; static FactionTeamMap sFactionTeamMap; DBCStorage sFactionStore(FactionEntryfmt); @@ -300,6 +306,13 @@ void LoadDBCStores(const std::string& dataPath) } } +#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"); @@ -909,3 +922,11 @@ DBCStorage const* GetFactionStore() { return &sFacti 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 diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index 2161a3fb0b..e5aef1f810 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -66,11 +66,22 @@ bool IsPointInAreaTriggerZone(AreaTriggerEntry const* atEntry, uint32 mapid, flo uint32 GetCreatureModelRace(uint32 model_id); +#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 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 +#endif extern DBCStorage sCharStartOutfitStore; extern DBCStorage sChatChannelsStore; extern DBCStorage sCharacterFacialHairStylesStore; diff --git a/src/game/Server/DBCStructure.h b/src/game/Server/DBCStructure.h index 0ff009bedb..1a7611eacd 100644 --- a/src/game/Server/DBCStructure.h +++ b/src/game/Server/DBCStructure.h @@ -341,6 +341,21 @@ struct EmotesTextEntry // m_emoteText }; +#ifdef ENABLE_PLAYERBOTS +/** +* \struct EmotesTextSoundEntry +* \brief Entry repsenting 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 diff --git a/src/game/Server/DBCfmt.h b/src/game/Server/DBCfmt.h index f5b4f9ae20..2bdf12c2b2 100644 --- a/src/game/Server/DBCfmt.h +++ b/src/game/Server/DBCfmt.h @@ -41,6 +41,11 @@ 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[] = "niiiiiiiiiiiiiiiiiissssssssxxxxxxxxxx"; const char FactionTemplateEntryfmt[] = "niiiiiiiiiiiii"; const char GameObjectArtKitfmt[] = "nxxxxxxx"; diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index 3dadb769e9..3b6fbccb52 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -52,6 +52,10 @@ #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#endif + // select opcodes appropriate for processing in Map::Update context for current session state static bool MapSessionFilterHelper(WorldSession* session, OpcodeHandler const& opHandle) { @@ -204,6 +208,15 @@ void WorldSession::SendPacket(WorldPacket const& packet, bool forcedSend /*= fal } #endif +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer()) { + if (GetPlayer()->GetPlayerbotAI()) + GetPlayer()->GetPlayerbotAI()->HandleBotOutgoingPacket(packet); + else if (GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->HandleMasterOutgoingPacket(packet); + } +#endif + if (!m_Socket || (m_sessionState != WORLD_SESSION_STATE_READY && !forcedSend)) { //sLog.outDebug("Refused to send %s to %s", packet.GetOpcodeName(), _player ? _player->GetName() : "UKNOWN"); @@ -388,6 +401,10 @@ bool WorldSession::Update(uint32 diff) #ifdef BUILD_PLAYERBOT if (_player && _player->GetPlayerbotMgr()) _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); +#endif +#ifdef ENABLE_PLAYERBOTS + if (_player && _player->GetPlayerbotMgr()) + _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); #endif break; case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT: @@ -464,6 +481,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) @@ -568,6 +590,19 @@ void WorldSession::UpdateMap(uint32 diff) } } +#ifdef ENABLE_PLAYERBOTS +void WorldSession::HandleBotPackets() +{ + while (!m_recvQueue.empty()) + { + auto const packet = std::move(m_recvQueue.front()); + m_recvQueue.pop_front(); + OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()]; + (this->*opHandle.handler)(*packet); + } +} +#endif + /// %Log the player out void WorldSession::LogoutPlayer() { @@ -589,7 +624,17 @@ 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); +#endif + +#ifdef ENABLE_PLAYERBOTS + 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); @@ -711,6 +756,11 @@ void WorldSession::LogoutPlayer() uint32 guid = _player->GetGUIDLow(); #endif +#ifdef ENABLE_PLAYERBOTS + // Remember player GUID for update SQL below + uint32 guid = _player->GetGUIDLow(); +#endif + ///- Remove the player from the world // the player may not be in the world when logging out // e.g if he got disconnected during a transfer to another map @@ -743,11 +793,18 @@ void WorldSession::LogoutPlayer() // Different characters can be alive as bots SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); stmt.PExecute(guid); +#else +#ifdef ENABLE_PLAYERBOTS + // Set for only character instead of accountid + // Different characters can be alive as bots + 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 // No SQL injection as AccountId is uint32 stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE account = ?"); stmt.PExecute(GetAccountId()); +#endif #endif DEBUG_LOG("SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); @@ -1262,6 +1319,15 @@ void WorldSession::SetNoAnticheat() #endif +#ifdef ENABLE_PLAYERBOTS + +void WorldSession::SetNoAnticheat() +{ + m_anticheat.reset(new NullSessionAnticheat(this)); +} + +#endif + void WorldSession::HandleWardenDataOpcode(WorldPacket& recv_data) { m_anticheat->WardenPacket(recv_data); diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 716fcc460e..6f93c53448 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -240,8 +240,13 @@ class WorldSession #ifdef BUILD_PLAYERBOT // Players connected without socket are bot const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected/bot"; } +#else +#ifdef ENABLE_PLAYERBOTS + // Players connected without socket are bot + const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected/bot"; } #else const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected"; } +#endif #endif const std::string& GetLocalAddress() const { return m_localAddress; } @@ -256,6 +261,10 @@ class WorldSession void SetNoAnticheat(); #endif +#ifdef ENABLE_PLAYERBOTS + void SetNoAnticheat(); +#endif + /// Session in auth.queue currently void SetInQueue(bool state) { m_inQueue = state; } @@ -408,6 +417,7 @@ class WorldSession // Misc void SendKnockBack(Unit* who, float angle, float horizontalSpeed, float verticalSpeed); void SendPlaySpellVisual(ObjectGuid guid, uint32 spellArtKit) const; + void SendTeleportToObservers(float x, float y, float z, float orientation); void SendAuthOk() const; void SendAuthQueued() const; @@ -796,6 +806,11 @@ class WorldSession std::deque GetOutOpcodeHistory(); std::deque GetIncOpcodeHistory(); +#ifdef ENABLE_PLAYERBOTS + // Playerbots + void HandleBotPackets(); +#endif + Messager& GetMessager() { return m_messager; } void SetPacketLogging(bool state); diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index 48ec6a173e..b609e3dcb5 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -77,6 +77,12 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "AhBot.h" +#include "PlayerbotAIConfig.h" +#include "RandomPlayerbotMgr.h" +#endif + #include #include #include @@ -100,6 +106,10 @@ uint32 World::m_relocation_ai_notify_delay = 1000u; uint32 World::m_currentMSTime = 0; TimePoint World::m_currentTime = TimePoint(); uint32 World::m_currentDiff = 0; +uint32 World::m_currentDiffSum = 0; +uint32 World::m_currentDiffSumIndex = 0; +uint32 World::m_averageDiff = 0; +uint32 World::m_maxDiff = 0; /// World constructor World::World(): mail_timer(0), mail_timer_expires(0), m_NextWeeklyQuestReset(0), m_opcodeCounters(NUM_MSG_TYPES) @@ -1388,6 +1398,11 @@ void World::SetInitialWorldSettings() #ifdef BUILD_PLAYERBOT PlayerbotMgr::SetInitialWorldSettings(); #endif + +#ifdef ENABLE_PLAYERBOTS + sPlayerbotAIConfig.Initialize(); +#endif + sLog.outString("---------------------------------------"); sLog.outString(" CMANGOS: World initialized "); sLog.outString("---------------------------------------"); @@ -1450,6 +1465,42 @@ void World::Update(uint32 diff) m_currentMSTime = WorldTimer::getMSTime(); m_currentTime = std::chrono::time_point_cast(Clock::now()); m_currentDiff = diff; + m_currentDiffSum += diff; + m_currentDiffSumIndex++; + if (m_currentDiffSumIndex && m_currentDiffSumIndex % 600 == 0) + { + m_averageDiff = (uint32)(m_currentDiffSum / m_currentDiffSumIndex); + if (m_maxDiff < m_averageDiff) + m_maxDiff = m_averageDiff; + sLog.outBasic("Avg Diff: %u. Sessions online: %u.", m_averageDiff, (uint32)GetActiveSessionCount()); + sLog.outBasic("Max Diff (last 5 min): %u.", m_maxDiff); + } + if (m_currentDiffSum > 300000) + { + m_currentDiffSum = 0; + m_currentDiffSumIndex = 0; + if (m_maxDiff > m_averageDiff) + { + m_maxDiff = m_averageDiff; + sLog.outBasic("Max Diff reset to: %u.", m_maxDiff); + } + } + if (GetActiveSessionCount()) + { + if (m_currentDiffSumIndex && (m_currentDiffSumIndex % 5 == 0)) + { + uint32 tempDiff = (uint32)(m_currentDiffSum / m_currentDiffSumIndex); + if (tempDiff > m_averageDiff) + { + m_averageDiff = tempDiff; + } + if (m_maxDiff < tempDiff) + { + m_maxDiff = tempDiff; + sLog.outBasic("Max Diff Increased: %u.", m_maxDiff); + } + } + } ///- Update the different timers for (auto& m_timer : m_timers) @@ -1498,6 +1549,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()); @@ -2008,6 +2072,10 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode) m_ShutdownTimer = time; ShutdownMsg(true); } + +#ifdef ENABLE_PLAYERBOTS + sRandomPlayerbotMgr.LogoutAllBots(); +#endif } /// Display a shutdown message to the user(s) @@ -2156,6 +2224,7 @@ void World::UpdateResultQueue() CharacterDatabase.ProcessResultQueue(); WorldDatabase.ProcessResultQueue(); LoginDatabase.ProcessResultQueue(); + PlayerbotDatabase.ProcessResultQueue(); } void World::UpdateRealmCharCount(uint32 accountId) diff --git a/src/game/World/World.h b/src/game/World/World.h index 49f0d5aab1..72885e9333 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -609,6 +609,8 @@ class World static uint32 GetCurrentMSTime() { return m_currentMSTime; } static TimePoint GetCurrentClockTime() { return m_currentTime; } static uint32 GetCurrentDiff() { return m_currentDiff; } + static uint32 GetAverageDiff() { return m_averageDiff; } + static uint32 GetMaxDiff() { return m_maxDiff; } template void ExecuteForAllSessions(T executor) const @@ -729,6 +731,10 @@ class World static uint32 m_currentMSTime; static TimePoint m_currentTime; static uint32 m_currentDiff; + static uint32 m_currentDiffSum; + static uint32 m_currentDiffSumIndex; + static uint32 m_averageDiff; + static uint32 m_maxDiff; Messager m_messager; diff --git a/src/mangosd/Main.cpp b/src/mangosd/Main.cpp index c5a564a92f..0073a79369 100644 --- a/src/mangosd/Main.cpp +++ b/src/mangosd/Main.cpp @@ -62,6 +62,7 @@ DatabaseType WorldDatabase; ///< Accessor to the DatabaseType CharacterDatabase; ///< Accessor to the character database DatabaseType LoginDatabase; ///< Accessor to the realm/login database DatabaseType LogsDatabase; ///< Accessor to the logs database +DatabaseType PlayerbotDatabase; ///< Accessor to the playerbot database uint32 realmID; ///< Id of the realm diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index c8b4e45887..fcbbd3d629 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -132,6 +132,7 @@ int Master::Run() WorldDatabase.AllowAsyncTransactions(); LoginDatabase.AllowAsyncTransactions(); LogsDatabase.AllowAsyncTransactions(); + PlayerbotDatabase.AllowAsyncTransactions(); ///- Catch termination signals _HookSignals(); @@ -363,6 +364,31 @@ bool Master::_StartDB() return false; } + /// Playerbot Database + dbstring = sConfig.GetStringDefault("PlayerbotDatabaseInfo", ""); + nConnections = sConfig.GetIntDefault("PlayerbotDatabaseConnections", 1); + if (dbstring.empty()) + { + sLog.outError("Playerbot Database not specified in configuration file"); + + ///- Wait for already started DB delay threads to end + WorldDatabase.HaltDelayThread(); + CharacterDatabase.HaltDelayThread(); + return false; + } + sLog.outString("Playerbot Database total connections: %i", nConnections + 1); + + ///- Initialise the Playerbot database + if (!PlayerbotDatabase.Initialize(dbstring.c_str(), nConnections)) + { + sLog.outError("Can not connect to Playerbot database %s", dbstring.c_str()); + + ///- Wait for already started DB delay threads to end + CharacterDatabase.HaltDelayThread(); + WorldDatabase.HaltDelayThread(); + return false; + } + ///- Get login database info from configuration file dbstring = sConfig.GetStringDefault("LoginDatabaseInfo"); nConnections = sConfig.GetIntDefault("LoginDatabaseConnections", 1); @@ -373,6 +399,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); return false; } @@ -385,6 +412,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); return false; } @@ -393,6 +421,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } @@ -444,6 +473,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index e3d57c7b46..5650de1c8a 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -71,10 +71,12 @@ LoginDatabaseInfo = "127.0.0.1;3306;mangos;mangos;classicrealmd" WorldDatabaseInfo = "127.0.0.1;3306;mangos;mangos;classicmangos" CharacterDatabaseInfo = "127.0.0.1;3306;mangos;mangos;classiccharacters" LogsDatabaseInfo = "127.0.0.1;3306;mangos;mangos;classiclogs" +PlayerbotDatabaseInfo = "127.0.0.1;3306;mangos;mangos;classicplayerbots" LoginDatabaseConnections = 1 WorldDatabaseConnections = 1 CharacterDatabaseConnections = 1 LogsDatabaseConnections = 1 +PlayerbotDatabaseConnections = 1 MaxPingTime = 30 WorldServerPort = 8085 BindIP = "0.0.0.0" diff --git a/src/modules/Bots b/src/modules/Bots new file mode 160000 index 0000000000..4e2ac8a576 --- /dev/null +++ b/src/modules/Bots @@ -0,0 +1 @@ +Subproject commit 4e2ac8a576e5500dc076ba325c0856681c3dc786 diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt new file mode 100644 index 0000000000..10efa42e94 --- /dev/null +++ b/src/modules/CMakeLists.txt @@ -0,0 +1,24 @@ +# MaNGOS is a full featured server for World of Warcraft, supporting +# the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 +# +# Copyright (C) 2005-2018 MaNGOS project +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +# Include Player Bots if enabled +if(BUILD_IKE3_BOTS) + add_subdirectory(Bots) +endif() \ No newline at end of file diff --git a/src/shared/Database/DatabaseEnv.h b/src/shared/Database/DatabaseEnv.h index ea7b4658ef..7581c282b2 100644 --- a/src/shared/Database/DatabaseEnv.h +++ b/src/shared/Database/DatabaseEnv.h @@ -50,5 +50,6 @@ extern DatabaseType WorldDatabase; extern DatabaseType CharacterDatabase; extern DatabaseType LoginDatabase; extern DatabaseType LogsDatabase; +extern DatabaseType PlayerbotDatabase; #endif