diff --git a/src/server/database/Database/DatabaseEnv.cpp b/src/server/database/Database/DatabaseEnv.cpp index e6bf6b082..835454568 100644 --- a/src/server/database/Database/DatabaseEnv.cpp +++ b/src/server/database/Database/DatabaseEnv.cpp @@ -20,3 +20,7 @@ DatabaseWorkerPool WorldDatabase; DatabaseWorkerPool CharacterDatabase; DatabaseWorkerPool LoginDatabase; + +#ifdef PLAYERBOTS +DatabaseWorkerPool PlayerbotDatabase; +#endif diff --git a/src/server/database/Database/DatabaseEnv.h b/src/server/database/Database/DatabaseEnv.h index eec79f004..79876c9dc 100644 --- a/src/server/database/Database/DatabaseEnv.h +++ b/src/server/database/Database/DatabaseEnv.h @@ -25,6 +25,10 @@ #include "Implementation/LoginDatabase.h" #include "Implementation/WorldDatabase.h" +#ifdef PLAYERBOTS +#include "PlayerbotDatabase.h" +#endif + #include "Field.h" #include "PreparedStatement.h" #include "QueryCallback.h" @@ -38,4 +42,9 @@ AC_DATABASE_API extern DatabaseWorkerPool Character /// Accessor to the realm/login database AC_DATABASE_API extern DatabaseWorkerPool LoginDatabase; +#ifdef PLAYERBOTS +/// Accessor to the playerbot database +AC_DATABASE_API extern DatabaseWorkerPool PlayerbotDatabase; +#endif + #endif diff --git a/src/server/database/Database/DatabaseEnvFwd.h b/src/server/database/Database/DatabaseEnvFwd.h index 34a590c45..fe835183c 100644 --- a/src/server/database/Database/DatabaseEnvFwd.h +++ b/src/server/database/Database/DatabaseEnvFwd.h @@ -33,6 +33,10 @@ class CharacterDatabaseConnection; class LoginDatabaseConnection; class WorldDatabaseConnection; +#ifdef PLAYERBOTS +class PlayerbotDatabaseConnection; +#endif + class PreparedStatementBase; template @@ -42,6 +46,10 @@ using CharacterDatabasePreparedStatement = PreparedStatement; using WorldDatabasePreparedStatement = PreparedStatement; +#ifdef PLAYERBOTS +using PlayerbotDatabasePreparedStatement = PreparedStatement; +#endif + class PreparedResultSet; using PreparedQueryResult = std::shared_ptr; using PreparedQueryResultFuture = std::future; @@ -71,6 +79,10 @@ using CharacterDatabaseTransaction = SQLTransaction using LoginDatabaseTransaction = SQLTransaction; using WorldDatabaseTransaction = SQLTransaction; +#ifdef PLAYERBOTS +using PlayerbotDatabaseTransaction = SQLTransaction; +#endif + class SQLQueryHolderBase; using QueryResultHolderFuture = std::future; using QueryResultHolderPromise = std::promise; @@ -82,6 +94,10 @@ using CharacterDatabaseQueryHolder = SQLQueryHolder using LoginDatabaseQueryHolder = SQLQueryHolder; using WorldDatabaseQueryHolder = SQLQueryHolder; +#ifdef PLAYERBOTS +using PlayerbotDatabaseQueryHolder = SQLQueryHolder; +#endif + class SQLQueryHolderCallback; // mysql diff --git a/src/server/database/Database/DatabaseLoader.cpp b/src/server/database/Database/DatabaseLoader.cpp index 41e3bacae..2876714ff 100644 --- a/src/server/database/Database/DatabaseLoader.cpp +++ b/src/server/database/Database/DatabaseLoader.cpp @@ -204,3 +204,8 @@ template AC_DATABASE_API DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); template AC_DATABASE_API DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); + +#ifdef PLAYERBOTS +template AC_DATABASE_API +DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool&, std::string const&); +#endif diff --git a/src/server/database/Database/DatabaseLoader.h b/src/server/database/Database/DatabaseLoader.h index 44aaca199..df5942342 100644 --- a/src/server/database/Database/DatabaseLoader.h +++ b/src/server/database/Database/DatabaseLoader.h @@ -48,8 +48,13 @@ public: DATABASE_LOGIN = 1, DATABASE_CHARACTER = 2, DATABASE_WORLD = 4, +#ifdef PLAYERBOTS + DATABASE_PLAYERBOT = 8, + DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD | DATABASE_PLAYERBOT +#else DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD +#endif }; [[nodiscard]] uint32 GetUpdateFlags() const @@ -57,6 +62,11 @@ public: return _updateFlags; } + void SetUpdateFlags(uint32 newUpdateFlags) + { + _updateFlags |= newUpdateFlags; + } + private: bool OpenDatabases(); bool PopulateDatabases(); @@ -73,7 +83,7 @@ private: std::string const _logger; std::string_view _modulesList; bool const _autoSetup; - uint32 const _updateFlags; + uint32 _updateFlags; std::queue _open, _populate, _update, _prepare; std::stack _close; diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp index ea74da357..618e885e6 100644 --- a/src/server/database/Database/DatabaseWorkerPool.cpp +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -39,6 +39,10 @@ #include #endif +#ifdef PLAYERBOTS +#include "PlayerbotDatabase.h" +#endif + #if MARIADB_VERSION_ID >= 100600 #define MIN_MYSQL_SERVER_VERSION 100200u #define MIN_MYSQL_CLIENT_VERSION 30203u @@ -530,3 +534,7 @@ void DatabaseWorkerPool::ExecuteOrAppend(SQLTransaction& trans, PreparedSt template class AC_DATABASE_API DatabaseWorkerPool; template class AC_DATABASE_API DatabaseWorkerPool; template class AC_DATABASE_API DatabaseWorkerPool; + +#ifdef PLAYERBOTS +template class AC_DATABASE_API DatabaseWorkerPool; +#endif diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index 924a0b495..0c29dade5 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -89,7 +89,7 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_UPD_MUTE_TIME_LOGIN, "UPDATE account SET mutetime = ? WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LAST_IP, "UPDATE account SET last_ip = ? WHERE username = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_LAST_ATTEMPT_IP, "UPDATE account SET last_attempt_ip = ? WHERE username = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = ? WHERE id = ?", CONNECTION_ASYNC); + PrepareStatement(LOGIN_UPD_ACCOUNT_ONLINE, "UPDATE account SET online = 1 WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_UPD_UPTIME_PLAYERS, "UPDATE uptime SET uptime = ?, maxplayers = ? WHERE realmid = ? AND starttime = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_OLD_LOGS, "DELETE FROM logs WHERE (time + ?) < ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_DEL_ACCOUNT_ACCESS, "DELETE FROM account_access WHERE id = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index b2f121de4..649483a6b 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -160,6 +160,40 @@ std::string DBUpdater::GetDBModuleName() return "db-characters"; } +#ifdef PLAYERBOTS +// Playerbot Database +template<> +std::string DBUpdater::GetConfigEntry() +{ + return "Updates.Playerbot"; +} + +template<> +std::string DBUpdater::GetTableName() +{ + return "Playerbot"; +} + +template<> +std::string DBUpdater::GetBaseFilesDirectory() +{ + return BuiltInConfig::GetSourceDirectory() + "/modules/mod-playerbots/sql/base/db_playerbot/"; +} + +template<> +bool DBUpdater::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_PLAYERBOT) ? true : false; +} + +template<> +std::string DBUpdater::GetDBModuleName() +{ + return "db-playerbot"; +} +#endif + // All template BaseLocation DBUpdater::GetBaseLocationType() @@ -514,3 +548,7 @@ void DBUpdater::ApplyFile(DatabaseWorkerPool& pool, std::string const& hos template class AC_DATABASE_API DBUpdater; template class AC_DATABASE_API DBUpdater; template class AC_DATABASE_API DBUpdater; + +#ifdef PLAYERBOTS +template class AC_DATABASE_API DBUpdater; +#endif diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 6afc5f746..645c5644e 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -36,6 +36,8 @@ typedef std::map AreaFlagByMapID; typedef std::tuple WMOAreaTableKey; typedef std::map WMOAreaInfoByTripple; +typedef std::multimap CharSectionsMap; + DBCStorage sAreaTableStore(AreaTableEntryfmt); DBCStorage sAreaGroupStore(AreaGroupEntryfmt); DBCStorage sAreaPOIStore(AreaPOIEntryfmt); @@ -51,6 +53,10 @@ DBCStorage sBattlemasterListStore(BattlemasterListEntryf DBCStorage sBarberShopStyleStore(BarberShopStyleEntryfmt); DBCStorage sCharStartOutfitStore(CharStartOutfitEntryfmt); std::map sCharStartOutfitMap; + +DBCStorage sCharSectionsStore(CharSectionsEntryfmt); +CharSectionsMap sCharSectionMap; + DBCStorage sCharTitlesStore(CharTitlesEntryfmt); DBCStorage sChatChannelsStore(ChatChannelsEntryfmt); DBCStorage sChrClassesStore(ChrClassesEntryfmt); @@ -72,6 +78,10 @@ DBCStorage sDurabilityCostsStore(DurabilityCostsfmt); DBCStorage sEmotesStore(EmotesEntryfmt); DBCStorage sEmotesTextStore(EmotesTextEntryfmt); +typedef std::tuple EmotesTextSoundKey; +static std::map sEmotesTextSoundMap; +DBCStorage sEmotesTextSoundStore(EmotesTextSoundEntryfmt); + typedef std::map FactionTeamMap; static FactionTeamMap sFactionTeamMap; DBCStorage sFactionStore(FactionEntryfmt); @@ -275,6 +285,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sBattlemasterListStore, "BattlemasterList.dbc", "battlemasterlist_dbc"); LOAD_DBC(sBarberShopStyleStore, "BarberShopStyle.dbc", "barbershopstyle_dbc"); LOAD_DBC(sCharStartOutfitStore, "CharStartOutfit.dbc", "charstartoutfit_dbc"); + LOAD_DBC(sCharSectionsStore, "CharSections.dbc", "charsections_dbc"); LOAD_DBC(sCharTitlesStore, "CharTitles.dbc", "chartitles_dbc"); LOAD_DBC(sChatChannelsStore, "ChatChannels.dbc", "chatchannels_dbc"); LOAD_DBC(sChrClassesStore, "ChrClasses.dbc", "chrclasses_dbc"); @@ -293,6 +304,7 @@ void LoadDBCStores(const std::string& dataPath) LOAD_DBC(sDurabilityQualityStore, "DurabilityQuality.dbc", "durabilityquality_dbc"); LOAD_DBC(sEmotesStore, "Emotes.dbc", "emotes_dbc"); LOAD_DBC(sEmotesTextStore, "EmotesText.dbc", "emotestext_dbc"); + LOAD_DBC(sEmotesTextSoundStore, "EmotesTextSound.dbc", "emotetextsound_dbc"); LOAD_DBC(sFactionStore, "Faction.dbc", "faction_dbc"); LOAD_DBC(sFactionTemplateStore, "FactionTemplate.dbc", "factiontemplate_dbc"); LOAD_DBC(sGameObjectDisplayInfoStore, "GameObjectDisplayInfo.dbc", "gameobjectdisplayinfo_dbc"); @@ -377,6 +389,10 @@ void LoadDBCStores(const std::string& dataPath) for (CharStartOutfitEntry const* outfit : sCharStartOutfitStore) sCharStartOutfitMap[outfit->Race | (outfit->Class << 8) | (outfit->Gender << 16)] = outfit; + for (CharSectionsEntry const* charSection : sCharSectionsStore) + if (charSection->Race && ((1 << (charSection->Race - 1)) & RACEMASK_ALL_PLAYABLE) != 0) //ignore Nonplayable races + sCharSectionMap.insert({ charSection->GenType | (charSection->Gender << 8) | (charSection->Race << 16), charSection }); + for (FactionEntry const* faction : sFactionStore) { if (faction->team) @@ -398,6 +414,9 @@ void LoadDBCStores(const std::string& dataPath) std::swap(*(float*)(&info->maxZ), *(float*)(&info->minZ)); } + for (EmotesTextSoundEntry const* emoteTextSound : sEmotesTextSoundStore) + sEmotesTextSoundMap[EmotesTextSoundKey(emoteTextSound->EmotesTextId, emoteTextSound->RaceId, emoteTextSound->SexId)] = emoteTextSound; + // fill data for (MapDifficultyEntry const* entry : sMapDifficultyStore) sMapDifficultyMap[MAKE_PAIR32(entry->MapId, entry->Difficulty)] = MapDifficulty(entry->resetTime, entry->maxPlayers, entry->areaTriggerText[0] != '\0'); @@ -1089,6 +1108,18 @@ CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, ui return itr->second; } +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color) +{ + std::pair eqr = sCharSectionMap.equal_range(uint32(genType) | uint32(gender << 8) | uint32(race << 16)); + for (CharSectionsMap::const_iterator itr = eqr.first; itr != eqr.second; ++itr) + { + if (itr->second->Type == type && itr->second->Color == color) + return itr->second; + } + + return nullptr; +} + /// Returns LFGDungeonEntry for a specific map and difficulty. Will return first found entry if multiple dungeons use the same map (such as Scarlet Monastery) LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty) { @@ -1134,3 +1165,9 @@ SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, u return nullptr; } + +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender) +{ + auto itr = sEmotesTextSoundMap.find(EmotesTextSoundKey(emote, race, gender)); + return itr != sEmotesTextSoundMap.end() ? itr->second : nullptr; +} diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index 2ff299c99..95d11c19f 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -63,6 +63,8 @@ PvPDifficultyEntry const* GetBattlegroundBracketById(uint32 mapid, BattlegroundB CharStartOutfitEntry const* GetCharStartOutfitEntry(uint8 race, uint8 class_, uint8 gender); +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color); + LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty); uint32 GetDefaultMapLight(uint32 mapId); @@ -70,6 +72,8 @@ typedef std::unordered_multimap SkillRac typedef std::pair SkillRaceClassInfoBounds; SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_); +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender); + extern DBCStorage sAchievementStore; extern DBCStorage sAchievementCriteriaStore; extern DBCStorage sAchievementCategoryStore; @@ -82,6 +86,7 @@ extern DBCStorage sBarberShopStyleStore; extern DBCStorage sBattlemasterListStore; extern DBCStorage sChatChannelsStore; extern DBCStorage sCharStartOutfitStore; +extern DBCStorage sCharSectionsStore; extern DBCStorage sCharTitlesStore; extern DBCStorage sChrClassesStore; extern DBCStorage sChrRacesStore; @@ -99,6 +104,7 @@ extern DBCStorage sDurabilityCostsStore; extern DBCStorage sDurabilityQualityStore; extern DBCStorage sEmotesStore; extern DBCStorage sEmotesTextStore; +extern DBCStorage sEmotesTextSoundStore; extern DBCStorage sFactionStore; extern DBCStorage sFactionTemplateStore; extern DBCStorage sGameObjectDisplayInfoStore; diff --git a/src/server/game/DungeonFinding/LFGQueue.cpp b/src/server/game/DungeonFinding/LFGQueue.cpp index 3ca1e1187..f39d09375 100644 --- a/src/server/game/DungeonFinding/LFGQueue.cpp +++ b/src/server/game/DungeonFinding/LFGQueue.cpp @@ -401,6 +401,22 @@ namespace lfg return LFG_COMPATIBLES_WITH_LESS_PLAYERS; } +#ifdef PLAYERBOTS + bool nonBotFound = false; + for (uint8 i = 0; i < 5 && check.guids[i]; ++i) + { + ObjectGuid guid = check.guids[i]; + Player* player = ObjectAccessor::FindPlayer(guid); + if (guid.IsGroup() || (player && !player->GetPlayerbotAI())) + { + nonBotFound = true; + break; + } + } + if (!nonBotFound) + return LFG_INCOMPATIBLES_HAS_IGNORES; +#endif + proposal.queues = strGuids; proposal.isNew = numLfgGroups != 1 || sLFGMgr->GetOldState(proposal.group) != LFG_STATE_DUNGEON; diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 9777846a8..5a4f7c3e5 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -1038,6 +1038,11 @@ void Item::SendUpdateSockets() // time. void Item::SendTimeUpdate(Player* owner) { +#ifdef PLAYERBOTS + if (!owner || !owner->IsInWorld() || owner->GetPlayerbotAI()) + return; +#endif + uint32 duration = GetUInt32Value(ITEM_FIELD_DURATION); if (!duration) return; diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index 56da3f837..b2720d554 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -260,7 +260,7 @@ enum SocketColor #define SOCKET_COLOR_ALL (SOCKET_COLOR_META | SOCKET_COLOR_RED | SOCKET_COLOR_YELLOW | SOCKET_COLOR_BLUE) -enum InventoryType +enum InventoryType : uint32 { INVTYPE_NON_EQUIP = 0, INVTYPE_HEAD = 1, diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 820e4b346..0f16f3f17 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -92,6 +92,10 @@ #include "WorldPacket.h" #include "WorldSession.h" +#ifdef PLAYERBOTS +#include "Playerbot.h" +#endif + enum CharacterFlags { CHARACTER_FLAG_NONE = 0x00000000, @@ -410,6 +414,11 @@ Player::Player(WorldSession* session): Unit(true), m_mover(this) m_isInstantFlightOn = true; +#ifdef PLAYERBOTS + _playerbotAI = nullptr; + _playerbotMgr = nullptr; +#endif + sScriptMgr->OnConstructPlayer(this); } @@ -461,6 +470,14 @@ Player::~Player() u->RemovePlayerFromVision(this); } while (!m_isInSharedVisionOf.empty()); } + +#ifdef PLAYERBOTS + delete _playerbotAI; + _playerbotAI = nullptr; + + delete _playerbotMgr; + _playerbotMgr = nullptr; +#endif } void Player::CleanupsBeforeDelete(bool finalCleanup) @@ -15435,3 +15452,34 @@ std::string Player::GetPlayerName() return "|Hplayer:" + name + "|h" + color + name + "|h|r"; } + +#ifdef PLAYERBOTS +void Player::SetPlayerbotAI(PlayerbotAI* ai) +{ + ASSERT(!_playerbotAI && !_playerbotMgr); + + _playerbotAI = ai; +} + +PlayerbotAI* Player::GetPlayerbotAI() +{ + return _playerbotAI; +} + +void Player::SetPlayerbotMgr(PlayerbotMgr* mgr) +{ + ASSERT(!_playerbotAI && !_playerbotMgr); + + _playerbotMgr = mgr; +} + +PlayerbotMgr* Player::GetPlayerbotMgr() +{ + return _playerbotMgr; +} + +void Player::SetBotDeathTimer() +{ + m_deathTimer = 0; +} +#endif diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 34c5faa1a..e73ba1ad7 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -60,6 +60,9 @@ class PlayerSocial; class SpellCastTargets; class UpdateMask; +class PlayerbotAI; +class PlayerbotMgr; + typedef std::deque PlayerMails; typedef void(*bgZoneRef)(Battleground*, WorldPacket&); @@ -660,7 +663,7 @@ enum PlayerSlots #define INVENTORY_SLOT_BAG_0 255 -enum EquipmentSlots // 19 slots +enum EquipmentSlots : uint32 // 19 slots { EQUIPMENT_SLOT_START = 0, EQUIPMENT_SLOT_HEAD = 0, @@ -1247,7 +1250,7 @@ public: InventoryResult CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading = true) const; InventoryResult CanUseItem(Item* pItem, bool not_loading = true) const; [[nodiscard]] bool HasItemTotemCategory(uint32 TotemCategory) const; - bool IsTotemCategoryCompatiableWith(const ItemTemplate* pProto, uint32 requiredTotemCategoryId) const; + bool IsTotemCategoryCompatiableWith(ItemTemplate const* pProto, uint32 requiredTotemCategoryId) const; InventoryResult CanUseItem(ItemTemplate const* pItem) const; [[nodiscard]] InventoryResult CanUseAmmo(uint32 item) const; InventoryResult CanRollForItemInLFG(ItemTemplate const* item, WorldObject const* lootedObject) const; @@ -1274,7 +1277,7 @@ public: void SetAmmo(uint32 item); void RemoveAmmo(); [[nodiscard]] float GetAmmoDPS() const { return m_ammoDPS; } - bool CheckAmmoCompatibility(const ItemTemplate* ammo_proto) const; + bool CheckAmmoCompatibility(ItemTemplate const* ammo_proto) const; void QuickEquipItem(uint16 pos, Item* pItem); void VisualizeItem(uint8 slot, Item* pItem); void SetVisibleItemSlot(uint8 slot, Item* pItem); @@ -2589,15 +2592,16 @@ public: std::string GetMapAreaAndZoneString(); std::string GetCoordsMapAreaAndZoneString(); - void SetFarSightDistance(float radius); - void ResetFarSightDistance(); - Optional GetFarSightDistance() const; + // Playerbot mod + // A Player can either have a playerbotMgr (to manage its bots), or have playerbotAI (if it is a bot), or + // neither. Code that enables bots must create the playerbotMgr and set it using SetPlayerbotMgr. + void SetPlayerbotAI(PlayerbotAI* ai); + PlayerbotAI* GetPlayerbotAI(); + void SetPlayerbotMgr(PlayerbotMgr* mgr); + PlayerbotMgr* GetPlayerbotMgr(); + void SetBotDeathTimer(); - float GetSightRange(const WorldObject* target = nullptr) const override; - - std::string GetPlayerName(); - - protected: +protected: // Gamemaster whisper whitelist WhisperListContainer WhisperList; @@ -2952,6 +2956,10 @@ private: WorldLocation _corpseLocation; Optional _farSightDistance = { }; + + // Playerbot mod + PlayerbotAI* _playerbotAI; + PlayerbotMgr* _playerbotMgr; }; void AddItemsSetItem(Player* player, Item* item); diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index ae2a0f8ce..8839fa73a 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -38,6 +38,10 @@ #include "Vehicle.h" #include "WeatherMgr.h" +#ifdef PLAYERBOTS +#include "Playerbot.h" +#endif + // Zone Interval should be 1 second constexpr auto ZONE_UPDATE_INTERVAL = 1000; @@ -434,6 +438,18 @@ void Player::Update(uint32 p_time) m_delayed_unit_relocation_timer = 0; RemoveFromNotify(NOTIFY_VISIBILITY_CHANGED); } + +#ifdef PLAYERBOTS + if (_playerbotAI) + { + _playerbotAI->UpdateAI(p_time); + } + + if (_playerbotMgr) + { + _playerbotMgr->UpdateAI(p_time); + } +#endif } void Player::UpdateMirrorTimers() diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 37f827852..985a47cda 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -250,10 +250,12 @@ public: GroupJoinBattlegroundResult CanJoinBattlegroundQueue(Battleground const* bgTemplate, BattlegroundQueueTypeId bgQueueTypeId, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot); void ChangeMembersGroup(ObjectGuid guid, uint8 group); - void SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid); void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag); void RemoveUniqueGroupMemberFlag(GroupMemberFlags flag); + void SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid); + ObjectGuid const GetTargetIcon(uint8 id) const { return m_targetIcons[i]; } + Difficulty GetDifficulty(bool isRaid) const; Difficulty GetDungeonDifficulty() const; Difficulty GetRaidDifficulty() const; @@ -292,6 +294,8 @@ public: bool CountRollVote(ObjectGuid playerGUID, ObjectGuid Guid, uint8 Choise); void EndRoll(Loot* loot, Map* allowedMap); + Rolls GetRolls() const { return RollId; } + // related to disenchant rolls void ResetMaxEnchantingLevel(); diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index d092c3aea..fe7257d82 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -56,6 +56,10 @@ #include "WorldPacket.h" #include "WorldSession.h" +#ifdef PLAYERBOTS +#include "Playerbot.h" +#endif + class LoginQueryHolder : public CharacterDatabaseQueryHolder { private: @@ -208,6 +212,22 @@ bool LoginQueryHolder::Initialize() return res; } +#ifdef PLAYERBOTS +class PlayerbotLoginQueryHolder : public LoginQueryHolder +{ + private: + uint32 masterAccountId; + PlayerbotHolder* playerbotHolder; + + public: + PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid) + : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) { } + + uint32 GetMasterAccountId() const { return masterAccountId; } + PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; } +}; +#endif + void WorldSession::HandleCharEnum(PreparedQueryResult result) { WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size @@ -901,8 +921,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) CharacterDatabase.Execute(stmt); LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_ONLINE); - loginStmt->setUInt32(0, realm.Id.Realm); - loginStmt->setUInt32(1, GetAccountId()); + loginStmt->setUInt32(0, GetAccountId()); LoginDatabase.Execute(loginStmt); pCurrChar->SetInGameTime(World::GetGameTimeMS()); @@ -1106,6 +1125,14 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) } } +#ifdef PLAYERBOTS + if (!pCurrChar->GetPlayerbotAI()) + { + pCurrChar->SetPlayerbotMgr(new PlayerbotMgr(pCurrChar)); + sRandomPlayerbotMgr->OnPlayerLogin(pCurrChar); + } +#endif + sScriptMgr->OnPlayerLogin(pCurrChar); if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST)) @@ -2546,3 +2573,65 @@ void WorldSession::SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, O data << guid; SendPacket(&data); } + +#ifdef PLAYERBOTS +void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) +{ + // has bot already been added? + if (ObjectAccessor::FindConnectedPlayer(playerGuid)) + { + return; + } + + uint32 accountId = sObjectMgr->GetPlayerAccountIdByGUID(playerGuid.GetCounter()); + if (!accountId) + { + return; + } + + PlayerbotLoginQueryHolder* holder = new PlayerbotLoginQueryHolder(this, masterAccountId, accountId, playerGuid); + if (!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + return; + } + + QueryResultHolderFuture future = CharacterDatabase.DelayQueryHolder(holder); + SQLQueryHolder* param; + future.get(param); + + PlayerbotHolder* playerbotHolder = holder->GetPlayerbotHolder(); + uint32 masterAccount = holder->GetMasterAccountId(); + WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : NULL; + + // The bot's WorldSession is owned by the bot's Player object + // The bot's WorldSession is deleted by PlayerbotMgr::LogoutPlayerBot + uint32 botAccountId = holder->GetAccountId(); + WorldSession* botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0), LOCALE_enUS, false, false, false, 0); + botSession->SetAddress("bot"); + botSession->HandlePlayerLoginFromDB(holder); // will delete lqh + + Player* bot = botSession->GetPlayer(); + if (!bot) + { + return; + } + + 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) + playerbotHolder->OnBotLogin(bot); + else if (masterSession) + { + ChatHandler ch(masterSession); + ch.PSendSysMessage("You are not allowed to control bot %s...", bot->GetName()); + playerbotHolder->LogoutPlayerBot(bot->GetGUID()); + } +} +#endif diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 58f61d0c9..79665b522 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -39,6 +39,10 @@ #include "WorldPacket.h" #include "WorldSession.h" +#ifdef PLAYERBOTS +#include "Playerbot.h" +#endif + inline bool isNasty(uint8 c) { if (c == '\t') @@ -409,7 +413,16 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) if (!senderIsPlayer && !sender->isAcceptWhispers() && !sender->IsInWhisperWhiteList(receiver->GetGUID())) sender->AddWhisperWhiteList(receiver->GetGUID()); - GetPlayer()->Whisper(msg, Language(lang), receiver); +#ifdef PLAYERBOTS + if (receiver->GetPlayerbotAI()) + { + receiver->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + else +#endif + GetPlayer()->Whisper(msg, Language(lang), receiver); } break; case CHAT_MSG_PARTY: @@ -432,6 +445,21 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } +#ifdef PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* player = itr->GetSource()) + { + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } + } +#endif + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group); WorldPacket data; @@ -454,6 +482,22 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) guild->BroadcastToGuild(this, false, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); } + +#ifdef PLAYERBOTS + if (PlayerbotMgr* mgr = GetPlayer()->GetPlayerbotMgr()) + { + for (PlayerBotMap::const_iterator it = mgr->GetPlayerBotsBegin(); it != mgr->GetPlayerBotsEnd(); ++it) + { + if (Player* const bot = it->second) + { + if (bot->GetGuildId() == GetPlayer()->GetGuildId()) + { + bot->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + } + } + } + } +#endif } } break; @@ -491,6 +535,21 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } +#ifdef PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* player = itr->GetSource()) + { + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } + } +#endif + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group); WorldPacket data; @@ -514,6 +573,21 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } +#ifdef PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* player = itr->GetSource()) + { + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } + } +#endif + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group); WorldPacket data; @@ -532,6 +606,21 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } +#ifdef PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + if (Player* player = itr->GetSource()) + { + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, GetPlayer()); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } + } +#endif + sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group); // In battleground, raid warning is sent only to players in battleground - code is ok @@ -598,6 +687,14 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } +#ifdef PLAYERBOTS + if (_player->GetPlayerbotMgr() && chn->GetFlags() & 0x18) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg); + } + + sRandomPlayerbotMgr->HandleCommand(type, msg, _player); +#endif sScriptMgr->OnPlayerChat(sender, type, lang, msg, chn); chn->Say(sender->GetGUID(), msg.c_str(), lang); diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index dade8e205..91b1f2db0 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -51,6 +51,10 @@ #include "WorldSocket.h" #include +#ifdef PLAYERBOTS +#include "Playerbot.h" +#endif + namespace { std::string const DefaultPlayerName = ""; @@ -211,6 +215,20 @@ void WorldSession::SendPacket(WorldPacket const* packet) return; } +#ifdef PLAYERBOTS + if (Player* player = GetPlayer()) + { + if (PlayerbotAI* playerbotAI = player->GetPlayerbotAI()) + { + playerbotAI->HandleBotOutgoingPacket(*packet); + } + else if (PlayerbotMgr* playerbotMgr = GetPlayer()->GetPlayerbotMgr()) + { + playerbotMgr->HandleMasterOutgoingPacket(*packet); + } + } +#endif + if (!m_Socket) return; @@ -287,6 +305,11 @@ void WorldSession::LogUnprocessedTail(WorldPacket* packet) /// Update the WorldSession (triggered by World update) bool WorldSession::Update(uint32 diff, PacketFilter& updater) { +#ifdef PLAYERBOTS + if (GetPlayer() && GetPlayer()->GetPlayerbotAI()) + return true; +#endif + ///- Before we process anything: /// If necessary, kick the player because the client didn't send anything for too long /// (or they've been idling in character select) @@ -347,6 +370,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) opHandle->Call(this, *packet); LogUnprocessedTail(packet); + +#ifdef PLAYERBOTS + if (_player && _player->GetPlayerbotMgr()) + _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); +#endif } break; case STATUS_TRANSFER: @@ -457,6 +485,11 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) ProcessQueryCallbacks(); +#ifdef 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!!! if (updater.ProcessUnsafe()) @@ -613,6 +646,7 @@ void WorldSession::LogoutPlayer(bool save) // there are some positive auras from boss encounters that can be kept by logging out and logging in after boss is dead, and may be used on next bosses _player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP); +#ifndef PLAYERBOTS ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. _player->UninviteFromGroup(); @@ -621,6 +655,7 @@ void WorldSession::LogoutPlayer(bool save) if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && !_player->GetGroup()->isLFGGroup() && m_Socket) _player->RemoveFromGroup(); +#endif // pussywizard: checked second time after being removed from a group if (!_player->IsBeingTeleportedFar() && !_player->m_InstanceValid && !_player->IsGameMaster()) _player->RepopAtGraveyard(); @@ -1614,3 +1649,17 @@ void WorldSession::SendTimeSync() _timeSyncTimer = _timeSyncNextCounter == 0 ? 5000 : 10000; _timeSyncNextCounter++; } + +#ifdef PLAYERBOTS +void WorldSession::HandleBotPackets() +{ + WorldPacket* packet; + while (_recvQueue.next(packet)) + { + OpcodeClient opcode = static_cast(packet->GetOpcode()); + ClientOpcodeHandler const* opHandle = opcodeTable[opcode]; + opHandle->Call(this, *packet); + delete packet; + } +} +#endif diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 1b3250a01..12eadf550 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -442,6 +442,11 @@ public: // Time Synchronisation void ResetTimeSync(); void SendTimeSync(); + +#ifdef PLAYERBOTS + void HandleBotPackets(); +#endif + public: // opcodes handlers void Handle_NULL(WorldPacket& null); // not used void Handle_EarlyProccess(WorldPacket& recvPacket); // just mark packets processed in WorldSocket::OnRead @@ -984,6 +989,8 @@ public: // opcodes handlers bool GetShouldSetOfflineInDB() const { return _shouldSetOfflineInDB; } bool IsSocketClosed() const; + void SetAddress(std::string const& address) { m_Address = address; } + /* * CALLBACKS */ diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index c79390c02..d422ab5b8 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -92,6 +92,10 @@ #include #include +#ifdef PLAYERBOTS +#include "RandomPlayerbotMgr.h" +#endif + std::atomic_long World::m_stopEvent = false; uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE; uint32 World::m_worldLoopCounter = 0; @@ -2274,6 +2278,11 @@ void World::Update(uint32 diff) ResetGuildCap(); } +#ifdef PLAYERBOTS + sRandomPlayerbotMgr->UpdateAI(diff); + sRandomPlayerbotMgr->UpdateSessions(diff); +#endif + // pussywizard: // acquire mutex now, this is kind of waiting for listing thread to finish it's work (since it can't process next packet) // so we don't have to do it in every packet that modifies auctions @@ -2432,6 +2441,9 @@ void World::Update(uint32 diff) CharacterDatabase.KeepAlive(); LoginDatabase.KeepAlive(); WorldDatabase.KeepAlive(); +#ifdef PLAYERBOTS + PlayerbotDatabase.KeepAlive(); +#endif } { @@ -2719,6 +2731,10 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std: ShutdownMsg(true, nullptr, reason); } +#ifdef PLAYERBOTS + sRandomPlayerbotMgr->LogoutAllBots(); +#endif + sScriptMgr->OnShutdownInitiate(ShutdownExitCode(exitcode), ShutdownMask(options)); } diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 0a8d7dbe3..2d02c6fe3 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -627,6 +627,33 @@ struct CharStartOutfitEntry //int32 ItemInventorySlot[MAX_OUTFIT_ITEMS]; // 53-76 not required at server side }; +enum CharSectionFlags +{ + SECTION_FLAG_PLAYER = 0x01, + SECTION_FLAG_DEATH_KNIGHT = 0x04 +}; + +enum CharSectionType +{ + SECTION_TYPE_SKIN = 0, + SECTION_TYPE_FACE = 1, + SECTION_TYPE_FACIAL_HAIR = 2, + SECTION_TYPE_HAIR = 3, + SECTION_TYPE_UNDERWEAR = 4 +}; + +struct CharSectionsEntry +{ + //uint32 Id; + uint32 Race; + uint32 Gender; + uint32 GenType; + //char* TexturePath[3]; + uint32 Flags; + uint32 Type; + uint32 Color; +}; + struct CharTitlesEntry { uint32 ID; // 0, title ids, for example in Quest::GetCharTitleId() @@ -864,6 +891,15 @@ struct EmotesTextEntry uint32 textid; }; +struct EmotesTextSoundEntry +{ + uint32 Id; // 0 + uint32 EmotesTextId; // 1 + uint32 RaceId; // 2 + uint32 SexId; // 3, 0 male / 1 female + uint32 SoundId; // 4 +}; + struct FactionEntry { uint32 ID; // 0 m_ID diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index 857c94c46..b8e46f57c 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -29,6 +29,7 @@ char constexpr BankBagSlotPricesEntryfmt[] = "ni"; char constexpr BarberShopStyleEntryfmt[] = "nixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxiii"; char constexpr BattlemasterListEntryfmt[] = "niiiiiiiiixssssssssssssssssxiixx"; char constexpr CharStartOutfitEntryfmt[] = "dbbbXiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; +char constexpr CharSectionsEntryfmt[] = "diiixxxiii"; char constexpr CharTitlesEntryfmt[] = "nxssssssssssssssssxssssssssssssssssxi"; char constexpr ChatChannelsEntryfmt[] = "nixssssssssssssssssxxxxxxxxxxxxxxxxxx"; // ChatChannelsEntryfmt, index not used (more compact store) char constexpr ChrClassesEntryfmt[] = "nxixssssssssssssssssxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxixii"; @@ -47,6 +48,7 @@ char constexpr DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii"; char constexpr DurabilityQualityfmt[] = "nf"; char constexpr EmotesEntryfmt[] = "nxxiiix"; char constexpr EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx"; +char constexpr EmotesTextSoundEntryfmt[] = "niiii"; char constexpr FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx"; char constexpr FactionTemplateEntryfmt[] = "niiiiiiiiiiiii"; char constexpr GameObjectDisplayInfofmt[] = "nsxxxxxxxxxxffffffx"; diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 6c4b5bb39..5632598a2 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -3116,7 +3116,7 @@ enum WeatherType #define MAX_WEATHER_TYPE 4 // EnumUtils: DESCRIBE THIS -enum ChatMsg +enum ChatMsg : uint32 { CHAT_MSG_ADDON = 0xFFFFFFFF, CHAT_MSG_SYSTEM = 0x00, diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp index b7398caf1..07d51b17d 100644 --- a/src/server/worldserver/Main.cpp +++ b/src/server/worldserver/Main.cpp @@ -456,6 +456,14 @@ bool StartDB() if (!loader.Load()) return false; +#ifdef PLAYERBOTS + DatabaseLoader playerbotLoader("server.playerbot"); + playerbotLoader.SetUpdateFlags(sConfigMgr->GetOption("Playerbot.Updates.EnableDatabases", true) ? DatabaseLoader::DATABASE_PLAYERBOT : 0); + playerbotLoader.AddDatabase(PlayerbotDatabase, "Playerbot"); + if (!playerbotLoader.Load()) + return false; +#endif + ///- Get the realm Id from the configuration file realm.Id.Realm = sConfigMgr->GetIntDefault("RealmID", 0); if (!realm.Id.Realm) @@ -499,6 +507,10 @@ void StopDB() WorldDatabase.Close(); LoginDatabase.Close(); +#ifdef PLAYERBOTS + PlayerbotDatabase.Close(); +#endif + MySQL::Library_End(); }