diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e956498e..004abea3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ option(BUILD_TOOLS "Build the map/vmap/mmap extractors" ON) option(USE_STORMLIB "Use StormLib for reading MPQs" ON) option(SCRIPT_LIB_ELUNA "Compile with support for Eluna scripts" ON) option(SCRIPT_LIB_SD3 "Compile with support for ScriptDev3 scripts" ON) -option(PLAYERBOTS "Enable Player Bots" OFF) +option(PLAYERBOTS "Enable Player Bots" ON) option(SOAP "Enable remote access via SOAP" OFF) option(PCH "Enable precompiled headers" ON) option(DEBUG "Enable debug build (only on non IDEs)" OFF) diff --git a/dockercontainer/DockerFile-mangosd b/dockercontainer/DockerFile-mangosd index b3acdcd6c..f7487723a 100644 --- a/dockercontainer/DockerFile-mangosd +++ b/dockercontainer/DockerFile-mangosd @@ -14,7 +14,7 @@ RUN mkdir /mangoserver/build WORKDIR /mangoserver/build #Install mangos -RUN cmake .. -DCMAKE_INSTALL_PREFIX=/mangos -DBUILD_MANGOSD=1 -DBUILD_REALMD=0 -DBUILD_TOOLS=0 +RUN cmake .. -DCMAKE_INSTALL_PREFIX=/mangos -DBUILD_MANGOSD=1 -DBUILD_REALMD=0 -DBUILD_TOOLS=0 -DPLAYERBOTS=0 RUN make -j4 RUN make install @@ -25,7 +25,7 @@ RUN apt-get -y update && apt-get -y upgrade RUN apt-get -y install libmysqlclient20 openssl COPY --from=build-step /mangos /mangos -COPY --from=build-step /etc/mangosd.conf.dist ../etc/mangosd.conf.dist +COPY --from=build-step /etc/*.conf.dist /mangos/etc/ WORKDIR /mangos/bin RUN chmod +x mangosd diff --git a/src/game/AuctionHouseBot/AuctionHouseBot.cpp b/src/game/AuctionHouseBot/AuctionHouseBot.cpp index b8ed4cfa9..9f9616e79 100644 --- a/src/game/AuctionHouseBot/AuctionHouseBot.cpp +++ b/src/game/AuctionHouseBot/AuctionHouseBot.cpp @@ -821,7 +821,6 @@ void AuctionBotBuyer::PrepareListOfEntry(AHB_Buyer_Config& config) bool AuctionBotBuyer::IsBuyableEntry(uint32 buyoutPrice, double InGame_BuyPrice, double MaxBuyablePrice, uint32 MinBuyPrice, uint32 MaxChance, uint32 ChanceRatio) { - double ratio = 0; uint32 Chance = 0; if (buyoutPrice <= MinBuyPrice) diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index e69093c30..6d79ffcde 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -92,94 +92,6 @@ if(SCRIPT_LIB_ELUNA) source_group("Eluna" FILES ${SRC_GRP_ELUNA}) endif() -if(PLAYERBOTS) - -#Base files -file(GLOB Playerbot_Source ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/*.h) -source_group("Player Bot" FILES ${Playerbot_Source}) -file(GLOB AHbot_Source ${CMAKE_SOURCE_DIR}/src/modules/Bots/ahbot/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/ahbot/*.h) -source_group("AH Bot" FILES ${AHbot_Source}) - -set(SRC_GRP_BOTS - ${Playerbot_Source} - ${AHbot_Source} -) -#Strategy files -file(GLOB Playerbot_Strategy ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/*.h) -source_group("Player Bot\\Strategies" FILES ${Playerbot_Strategy}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Strategy}) - -#Action files -file(GLOB Playerbot_Actions ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/actions/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/actions/*.h) -source_group("Player Bot\\Strategies\\Actions" FILES ${Playerbot_Actions}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Actions}) - -#Generic files -file(GLOB Playerbot_Generic ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/generic/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/generic/*.h) -source_group("Player Bot\\Strategies\\Generic" FILES ${Playerbot_Generic}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Generic}) - -#Trigger files -file(GLOB Playerbot_Triggers ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/triggers/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/triggers/*.h) -source_group("Player Bot\\Strategies\\Triggers" FILES ${Playerbot_Triggers}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Triggers}) - - -#Value files -file(GLOB Playerbot_Values ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/values/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/values/*.h) -source_group("Player Bot\\Strategies\\Values" FILES ${Playerbot_Values}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Values}) - -## Class files -#Druid AI -file(GLOB Playerbot_Druid ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/druid/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/druid/*.h) -source_group("Player Bot\\Strategies\\Druid" FILES ${Playerbot_Druid}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Druid}) - -#Hunter AI -file(GLOB Playerbot_Hunter ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/hunter/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/hunter/*.h) -source_group("Player Bot\\Strategies\\Hunter" FILES ${Playerbot_Hunter}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Hunter}) - -#Mage AI -file(GLOB Playerbot_Mage ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/mage/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/mage/*.h) -source_group("Player Bot\\Strategies\\Mage" FILES ${Playerbot_Mage}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Mage}) - -#Paladin AI -file(GLOB Playerbot_Paladin ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/paladin/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/paladin/*.h) -source_group("Player Bot\\Strategies\\Paladin" FILES ${Playerbot_Paladin}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Paladin}) - -#Priest AI -file(GLOB Playerbot_Priest ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/priest/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/priest/*.h) -source_group("Player Bot\\Strategies\\Priest" FILES ${Playerbot_Priest}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Priest}) - -#Rogue AI -file(GLOB Playerbot_Rogue ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/rogue/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/rogue/*.h) -source_group("Player Bot\\Strategies\\Rogue" FILES ${Playerbot_Rogue}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Rogue}) - -#Shaman AI -file(GLOB Playerbot_Shaman ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/shaman/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/shaman/*.h) -source_group("Player Bot\\Strategies\\Shaman" FILES ${Playerbot_Shaman}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Shaman}) - -#Warlock AI -file(GLOB Playerbot_Warlock ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/warlock/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/warlock/*.h) -source_group("Player Bot\\Strategies\\Warlock" FILES ${Playerbot_Warlock}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Warlock}) - -#Warrior AI -file(GLOB Playerbot_Warrior ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/warrior/*.cpp ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/strategy/warrior/*.h) -source_group("Player Bot\\Strategies\\Warrior" FILES ${Playerbot_Warrior}) -LIST(APPEND SRC_GRP_BOTS ${Playerbot_Warrior}) - -configure_file(${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist) - -endif() - configure_file(AuctionHouseBot/ahbot.conf.dist.in ${CMAKE_CURRENT_BINARY_DIR}/AuctionHouseBot/ahbot.conf.dist) if(BUILD_TOOLS) @@ -264,11 +176,6 @@ target_include_directories(game ${CMAKE_SOURCE_DIR}/src/modules/Eluna ${CMAKE_SOURCE_DIR}/src/modules/Eluna/Mangos > - $<$: - ${CMAKE_SOURCE_DIR}/src/modules/Bots - ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot - ${CMAKE_SOURCE_DIR}/src/modules/Bots/ahbot - > ) target_compile_definitions(game @@ -287,6 +194,7 @@ target_link_libraries(game g3dlite $<$:mangosscript> $<$:lualib> + $<$:bots> ) # Generate precompiled header @@ -304,9 +212,4 @@ install( DIRECTORY ${CMAKE_SOURCE_DIR}/src/modules/Eluna/extensions DESTINATION ${BIN_DIR}/lua_scripts ) -endif() - -if(PLAYERBOTS) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist - DESTINATION ${CONF_INSTALL_DIR}) endif() \ No newline at end of file diff --git a/src/game/ChatCommands/RACommands.cpp b/src/game/ChatCommands/RACommands.cpp index 734d78270..4840e1978 100644 --- a/src/game/ChatCommands/RACommands.cpp +++ b/src/game/ChatCommands/RACommands.cpp @@ -35,4 +35,4 @@ bool ChatHandler::HandleQuitCommand(char* /*args*/) // processed in RASocket SendSysMessage(LANG_QUIT_WRONG_USE_ERROR); return true; -} +} \ No newline at end of file diff --git a/src/game/Object/Item.cpp b/src/game/Object/Item.cpp index 1e6b28442..51f2b710f 100644 --- a/src/game/Object/Item.cpp +++ b/src/game/Object/Item.cpp @@ -1263,6 +1263,13 @@ bool Item::IsLimitedToAnotherMapOrZone(uint32 cur_mapId, uint32 cur_zoneId) cons // time. void Item::SendTimeUpdate(Player* owner) { +#ifdef ENABLE_PLAYERBOTS + if (!owner || !owner->IsInWorld() || owner->GetPlayerbotAI()) + { + return; + } +#endif + uint32 duration = GetUInt32Value(ITEM_FIELD_DURATION); if (!duration) { diff --git a/src/game/Object/Player.cpp b/src/game/Object/Player.cpp index 234a68bc6..f2f172391 100644 --- a/src/game/Object/Player.cpp +++ b/src/game/Object/Player.cpp @@ -79,6 +79,10 @@ #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#endif + #include #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -438,6 +442,11 @@ UpdateMask Player::updateVisualBits; Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_achievementMgr(this), m_reputationMgr(this) { +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = 0; + m_playerbotMgr = 0; +#endif + m_transport = 0; m_speakTime = 0; @@ -629,6 +638,10 @@ Player::Player(WorldSession* session): Unit(), m_mover(this), m_camera(this), m_ m_lastFallTime = 0; m_lastFallZ = 0; +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = NULL; + m_playerbotMgr = NULL; +#endif m_cachedGS = 0; } @@ -670,6 +683,21 @@ Player::~Player() delete ItemSetEff[x]; } +#ifdef ENABLE_PLAYERBOTS + if (m_playerbotAI) { + { + delete m_playerbotAI; + } + m_playerbotAI = 0; + } + if (m_playerbotMgr) { + { + delete m_playerbotMgr; + } + m_playerbotMgr = 0; + } +#endif + // clean up player-instance binds, may unload some instance saves for (uint8 i = 0; i < MAX_DIFFICULTY; ++i) for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) @@ -1628,6 +1656,18 @@ void Player::Update(uint32 update_diff, uint32 p_time) { TeleportTo(m_teleport_dest, m_teleport_options); } + +#ifdef ENABLE_PLAYERBOTS + if (m_playerbotAI) + { + m_playerbotAI->UpdateAI(p_time); + } + if (m_playerbotMgr) + { + m_playerbotMgr->UpdateAI(p_time); + } +#endif + } void Player::SetDeathState(DeathState s) diff --git a/src/game/Object/Player.h b/src/game/Object/Player.h index 30ff874ac..4a93c5045 100644 --- a/src/game/Object/Player.h +++ b/src/game/Object/Player.h @@ -65,6 +65,11 @@ class Item; struct AreaTrigger; +#ifdef ENABLE_PLAYERBOTS +class PlayerbotAI; +class PlayerbotMgr; +#endif + typedef std::deque PlayerMails; #define PLAYER_MAX_SKILLS 127 @@ -1626,6 +1631,9 @@ 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); @@ -2628,6 +2636,17 @@ class Player : public Unit void SetTitle(CharTitlesEntry const* title, bool lost = false); bool canSeeSpellClickOn(Creature const* creature) const; + +#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 + protected: uint32 m_contestedPvPTimer; @@ -2908,6 +2927,10 @@ class Player : public Unit GridReference m_gridRef; MapReference m_mapRef; +#ifdef ENABLE_PLAYERBOTS + PlayerbotAI* m_playerbotAI; + PlayerbotMgr* m_playerbotMgr; +#endif // Homebind coordinates uint32 m_homebindMapId; uint16 m_homebindAreaId; diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index d06709dac..9e11264b0 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -49,6 +49,9 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" #endif /*ENABLE_ELUNA*/ +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#endif // Warden #include "WardenWin.h" @@ -162,6 +165,19 @@ char const* WorldSession::GetPlayerName() const /// Send a packet to the client void WorldSession::SendPacket(WorldPacket const* packet) { +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer()) { + if (GetPlayer()->GetPlayerbotAI()) + { + GetPlayer()->GetPlayerbotAI()->HandleBotOutgoingPacket(*packet); + } + else if (GetPlayer()->GetPlayerbotMgr()) + { + GetPlayer()->GetPlayerbotMgr()->HandleMasterOutgoingPacket(*packet); + } + } +#endif + if (!m_Socket) { return; @@ -267,6 +283,12 @@ bool WorldSession::Update(PacketFilter& updater) } // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer +#ifdef ENABLE_PLAYERBOTS + if (_player && _player->GetPlayerbotMgr()) + { + _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); + } +#endif break; case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT: if (!_player && !m_playerRecentlyLogout) @@ -349,6 +371,13 @@ bool WorldSession::Update(PacketFilter& updater) delete packet; } +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer() && GetPlayer()->GetPlayerbotMgr()) + { + GetPlayer()->GetPlayerbotMgr()->UpdateSessions(0); + } +#endif + ///- Cleanup socket pointer if need if (m_Socket && m_Socket->IsClosed()) { @@ -388,6 +417,19 @@ bool WorldSession::Update(PacketFilter& updater) return true; } +#ifdef ENABLE_PLAYERBOTS +void WorldSession::HandleBotPackets() +{ + WorldPacket* packet; + while (_recvQueue.next(packet)) + { + OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()]; + ExecuteOpcode(opHandle, packet); + delete packet; + } +} +#endif + /// %Log the player out void WorldSession::LogoutPlayer(bool Save) { @@ -402,6 +444,13 @@ void WorldSession::LogoutPlayer(bool Save) if (_player) { +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer()->GetPlayerbotMgr()) + { + GetPlayer()->GetPlayerbotMgr()->LogoutAllBots(); + } +#endif + sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), GetRemoteAddress().c_str(), _player->GetName() , _player->GetGUIDLow()); if (ObjectGuid lootGuid = GetPlayer()->GetLootGuid()) @@ -409,6 +458,14 @@ void WorldSession::LogoutPlayer(bool Save) DoLootRelease(lootGuid); } +#ifdef ENABLE_PLAYERBOTS + if (_player->GetPlayerbotMgr()) + { + _player->GetPlayerbotMgr()->LogoutAllBots(); + } + sRandomPlayerbotMgr.OnPlayerLogout(_player); +#endif + ///- If the player just died before logging out, make him appear as a ghost // FIXME: logout must be delayed in case lost connection with client in time of combat if (_player->GetDeathTimer()) @@ -502,11 +559,23 @@ void WorldSession::LogoutPlayer(bool Save) ///- Reset the online field in the account table // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage // No SQL injection as AccountID is uint32 +#ifdef ENABLE_PLAYERBOTS + if (!GetPlayer()->GetPlayerbotAI()) + { + static SqlStatementID id; + // playerbot mod + if (!_player->GetPlayerbotAI()) + { + SqlStatement stmt = LoginDatabase.CreateStatement(id, "UPDATE `account` SET `active_realm_id` = ? WHERE `id` = ?"); + stmt.PExecute(uint32(0), GetAccountId()); + } + } +#else static SqlStatementID id; SqlStatement stmt = LoginDatabase.CreateStatement(id, "UPDATE `account` SET `active_realm_id` = ? WHERE `id` = ?"); stmt.PExecute(uint32(0), GetAccountId()); - +#endif ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members if (Guild* guild = sGuildMgr.GetGuildById(_player->GetGuildId())) { @@ -531,7 +600,7 @@ void WorldSession::LogoutPlayer(bool Save) ///- Leave all channels before player delete... _player->CleanupChannels(); - +#ifndef ENABLE_PLAYERBOTS ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. _player->UninviteFromGroup(); @@ -541,7 +610,7 @@ void WorldSession::LogoutPlayer(bool Save) { _player->RemoveFromGroup(); } - +#endif ///- Send update to group if (_player->GetGroup()) { @@ -582,8 +651,11 @@ void WorldSession::LogoutPlayer(bool Save) // No SQL injection as AccountId is uint32 static SqlStatementID updChars; - +#ifdef ENABLE_PLAYERBOTS + SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE `characters` SET `online` = 0 WHERE `account` = ?"); +#else stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE `characters` SET `online` = 0 WHERE `account` = ?"); +#endif stmt.PExecute(GetAccountId()); DEBUG_LOG("SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); diff --git a/src/game/WorldHandlers/CharacterHandler.cpp b/src/game/WorldHandlers/CharacterHandler.cpp index 9a1cbbc84..25b344165 100644 --- a/src/game/WorldHandlers/CharacterHandler.cpp +++ b/src/game/WorldHandlers/CharacterHandler.cpp @@ -51,6 +51,10 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif // config option SkipCinematics supported values enum CinematicsSkipMode @@ -73,6 +77,23 @@ 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, ObjectGuid guid) + : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) { } + +public: + uint32 GetMasterAccountId() const { return masterAccountId; } + PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; } +}; +#endif + bool LoginQueryHolder::Initialize() { SetSize(MAX_PLAYER_LOGIN_QUERY); @@ -151,10 +172,103 @@ class CharacterHandler delete holder; return; } +#ifdef ENABLE_PLAYERBOTS + ObjectGuid guid = static_cast(holder)->GetGuid(); +#endif session->HandlePlayerLogin((LoginQueryHolder*)holder); +#ifdef ENABLE_PLAYERBOTS + Player* player = sObjectMgr.GetPlayer(guid, true); + if (player && !player->GetPlayerbotAI()) + { + player->SetPlayerbotMgr(new PlayerbotMgr(player)); + sRandomPlayerbotMgr.OnPlayerLogin(player); + } +#endif + } +#ifdef ENABLE_PLAYERBOTS + void HandlePlayerBotLoginCallback(QueryResult * dummy, SqlQueryHolder * holder) + { + if (!holder) + { + return; + } + + PlayerbotLoginQueryHolder* lqh = static_cast(holder); + if (sObjectMgr.GetPlayer(lqh->GetGuid())) + { + delete holder; + return; + } + + PlayerbotHolder* playerbotHolder = lqh->GetPlayerbotHolder(); + uint32 masterAccount = lqh->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 = lqh->GetAccountId(); + WorldSession *botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, 2, 0, LOCALE_enUS); + botSession->m_Address = "bot"; + botSession->HandlePlayerLogin(lqh); // 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->GetObjectGuid().GetRawValue()); + } } +#endif } chrHandler; +#ifdef ENABLE_PLAYERBOTS +void PlayerbotHolder::AddPlayerBot(uint64 playerGuid, uint32 masterAccountId) +{ + // has bot already been added? + if (sObjectMgr.GetPlayer(ObjectGuid(playerGuid))) + { + return; + } + + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(ObjectGuid(playerGuid)); + if (accountId == 0) + { + return; + } + + PlayerbotLoginQueryHolder *holder = new PlayerbotLoginQueryHolder(this, masterAccountId, accountId, ObjectGuid(playerGuid)); + if (!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + return; + } + CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerBotLoginCallback, holder); +} +#endif + void WorldSession::HandleCharEnum(QueryResult* result) { WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size @@ -845,8 +959,15 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE `characters` SET `online` = 1 WHERE `guid` = ?"); stmt.PExecute(pCurrChar->GetGUIDLow()); - stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE `account` SET `active_realm_id` = ? WHERE `id` = ?"); - stmt.PExecute(realmID, GetAccountId()); +#ifdef ENABLE_PLAYERBOTS + if (pCurrChar->GetSession()->GetRemoteAddress() != "bot") + { +#endif + stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE `account` SET `active_realm_id` = ? WHERE `id` = ?"); + stmt.PExecute(realmID, GetAccountId()); +#ifdef ENABLE_PLAYERBOTS + } +#endif /* Sync player's in-game time with server time */ pCurrChar->SetInGameTime(GameTime::GetGameTimeMS()); diff --git a/src/game/WorldHandlers/Chat.cpp b/src/game/WorldHandlers/Chat.cpp index 440ec68b2..36e44beb6 100644 --- a/src/game/WorldHandlers/Chat.cpp +++ b/src/game/WorldHandlers/Chat.cpp @@ -858,6 +858,12 @@ ChatCommand* ChatHandler::getCommandTable() { "quit", SEC_CONSOLE, true, &ChatHandler::HandleQuitCommand, "", NULL }, { "gearscore", SEC_ADMINISTRATOR, false, &ChatHandler::HandleShowGearScoreCommand, "", NULL }, { "mmap", SEC_GAMEMASTER, false, NULL, "", mmapCommandTable }, +#ifdef ENABLE_PLAYERBOTS + { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", NULL }, + { "rndbot", SEC_GAMEMASTER, true, &ChatHandler::HandleRandomPlayerbotCommand, "", NULL }, + { "ahbot", SEC_GAMEMASTER, true, &ChatHandler::HandleAhBotCommand, "", NULL }, + { "gtask", SEC_GAMEMASTER, true, &ChatHandler::HandleGuildTaskCommand, "", NULL }, +#endif { NULL, 0, false, NULL, "", NULL } }; diff --git a/src/game/WorldHandlers/Chat.h b/src/game/WorldHandlers/Chat.h index 32a0e57e7..c1f87ec78 100644 --- a/src/game/WorldHandlers/Chat.h +++ b/src/game/WorldHandlers/Chat.h @@ -131,6 +131,10 @@ class ChatHandler bool isValidChatMessage(const char* msg); bool HasSentErrorMessage() { return sentErrorMessage;} +#ifdef ENABLE_PLAYERBOTS + WorldSession* GetSession() { return m_session; } +#endif + /** * \brief Prepare SMSG_GM_MESSAGECHAT/SMSG_MESSAGECHAT * @@ -680,6 +684,13 @@ class ChatHandler bool HandleFreezePlayerCommand(char* args); bool HandleUnfreezePlayerCommand(char* args); +#ifdef ENABLE_PLAYERBOTS + bool HandlePlayerbotCommand(char* args); + bool HandleRandomPlayerbotCommand(char* args); + bool HandleAhBotCommand(char* args); + bool HandleGuildTaskCommand(char* args); +#endif + //! Development Commands bool HandleSaveAllCommand(char* args); diff --git a/src/game/WorldHandlers/ChatHandler.cpp b/src/game/WorldHandlers/ChatHandler.cpp index 2121b20d2..8cdd4578b 100644 --- a/src/game/WorldHandlers/ChatHandler.cpp +++ b/src/game/WorldHandlers/ChatHandler.cpp @@ -44,6 +44,10 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "RandomPlayerbotMgr.h" +#endif bool WorldSession::processChatmessageFurtherAfterSecurityChecks(std::string& msg, uint32 lang) { @@ -298,7 +302,16 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) return; } #endif /* ENABLE_ELUNA */ - GetPlayer()->Whisper(msg, lang, player->GetObjectGuid()); +#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()); } break; case CHAT_MSG_PARTY: @@ -351,6 +364,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) } #endif /* ENABLE_ELUNA */ +#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 + WorldPacket 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())); @@ -396,6 +422,21 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) 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: @@ -483,6 +524,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) } #endif /* ENABLE_ELUNA */ +#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 + WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(&data, false); @@ -531,6 +585,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) } #endif /* ENABLE_ELUNA */ +#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 + WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_LEADER, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(&data, false); @@ -566,6 +633,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) } #endif /* ENABLE_ELUNA */ +#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 + WorldPacket data; // in battleground, raid warning is sent only to players in battleground - code is ok ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_WARNING, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); @@ -669,7 +749,13 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) return; } #endif /* ENABLE_ELUNA */ - +#ifdef ENABLE_PLAYERBOTS + if (_player->GetPlayerbotMgr() && chn->GetFlags() & 0x18) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg); + } + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player); +#endif /* ENABLE_PLAYERBOTS */ chn->Say(_player, msg.c_str(), lang); } } diff --git a/src/game/WorldHandlers/Group.h b/src/game/WorldHandlers/Group.h index afa23302b..4000d9d8a 100644 --- a/src/game/WorldHandlers/Group.h +++ b/src/game/WorldHandlers/Group.h @@ -462,6 +462,11 @@ class Group InstanceGroupBind* GetBoundInstance(Map* aMap, Difficulty difficulty); BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } +#ifdef ENABLE_PLAYERBOTS + ObjectGuid GetTargetIcon(int index) { return m_targetIcons[index]; } + Rolls GetRolls() { return RollId; } +#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/WorldHandlers/Map.cpp b/src/game/WorldHandlers/Map.cpp index f2fc9906b..9ddaf8ca8 100644 --- a/src/game/WorldHandlers/Map.cpp +++ b/src/game/WorldHandlers/Map.cpp @@ -722,6 +722,9 @@ void Map::Remove(Player* player, bool remove) SendRemoveTransports(player); UpdateObjectVisibility(player, cell, p); +#ifdef ENABLE_PLAYERBOTS + if (!player->GetPlayerbotAI()) +#endif player->ResetMap(); if (remove) { diff --git a/src/game/WorldHandlers/Spell.cpp b/src/game/WorldHandlers/Spell.cpp index 8d09d5234..4c6915154 100644 --- a/src/game/WorldHandlers/Spell.cpp +++ b/src/game/WorldHandlers/Spell.cpp @@ -4392,6 +4392,13 @@ void Spell::finish(bool ok) { ((DungeonMap*)map)->GetPersistanceState()->UpdateEncounterState(ENCOUNTER_CREDIT_CAST_SPELL, m_spellInfo->Id); } + +#ifdef ENABLE_PLAYERBOTS + if(!m_caster->GetMapId()) + { + return; + } +#endif } void Spell::SendCastResult(SpellCastResult result) diff --git a/src/game/WorldHandlers/World.cpp b/src/game/WorldHandlers/World.cpp index c9e00f512..e5e8a9710 100644 --- a/src/game/WorldHandlers/World.cpp +++ b/src/game/WorldHandlers/World.cpp @@ -86,6 +86,12 @@ #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ +#ifdef ENABLE_PLAYERBOTS + +#include "PlayerbotAIConfig.h" +#include "RandomPlayerbotMgr.h" +#endif + // WARDEN #include "WardenCheckMgr.h" @@ -1616,20 +1622,19 @@ void World::showFooter() modules_.insert(" ScriptDev3 (SD3) : Enabled"); #endif - // PLAYERBOTS can be included or excluded but also disabled via mangos.conf + // PLAYERBOTS can be included or excluded but also disabled via aiplayerbot.conf #ifdef ENABLE_PLAYERBOTS - bool playerBotActive = sConfig.GetBoolDefault("PlayerbotAI.DisableBots", true); - if (playerBotActive) + if (sPlayerbotAIConfig.enabled) { - modules_.insert(" PlayerBots : Disabled"); + modules_.insert(" PlayerBots : Enabled"); } else { - modules_.insert(" PlayerBots : Enabled"); + modules_.insert(" PlayerBots : Disabled"); } #endif - // Remote Access can be activated / deactivated via mangos.conf + // Remote Access can be activated / deactivated via mangosd.conf bool raActive = sConfig.GetBoolDefault("Ra.Enable", false); if (raActive) { @@ -1640,7 +1645,7 @@ void World::showFooter() modules_.insert(" Remote Access (RA) : Disabled"); } - // SOAP can be included or excluded but also disabled via mangos.conf + // SOAP can be included or excluded but also disabled via mangosd.conf #ifdef ENABLE_SOAP bool soapActive = sConfig.GetBoolDefault("SOAP.Enabled", false); if (soapActive) @@ -1653,7 +1658,7 @@ void World::showFooter() } #endif - // Warden is always included, set active or disabled via mangos.conf + // Warden is always included, set active or disabled via mangosd.conf bool wardenActive = (sWorld.getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED) || sWorld.getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED)); if (wardenActive) { @@ -1828,6 +1833,11 @@ void World::Update(uint32 diff) m_timers[WUPDATE_AHBOT].Reset(); } +#ifdef ENABLE_PLAYERBOTS + sRandomPlayerbotMgr.UpdateAI(diff); + sRandomPlayerbotMgr.UpdateSessions(diff); +#endif + ///
  • Update Dungeon Finder if (m_timers[WUPDATE_LFGMGR].Passed()) { @@ -2243,6 +2253,10 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode) ShutdownMsg(true); } +#ifdef ENABLE_PLAYERBOTS + sRandomPlayerbotMgr.LogoutAllBots(); +#endif + ///- Used by Eluna #ifdef ENABLE_ELUNA sEluna->OnShutdownInitiate(ShutdownExitCode(exitcode), ShutdownMask(options)); diff --git a/src/game/WorldHandlers/World.h b/src/game/WorldHandlers/World.h index 2580b5ca9..394304ad3 100644 --- a/src/game/WorldHandlers/World.h +++ b/src/game/WorldHandlers/World.h @@ -210,7 +210,6 @@ enum eConfigUInt32Values CONFIG_UINT32_WARDEN_NUM_MEM_CHECKS, CONFIG_UINT32_WARDEN_NUM_OTHER_CHECKS, CONFIG_UINT32_WARDEN_DB_LOGLEVEL, - CONFIG_UINT32_AUTOBROADCAST_INTERVAL, CONFIG_UINT32_VALUE_COUNT }; @@ -370,7 +369,6 @@ enum eConfigBoolValues CONFIG_BOOL_ELUNA_ENABLED, CONFIG_BOOL_PLAYER_COMMANDS, CONFIG_BOOL_ENABLE_QUEST_TRACKER, - // Warden CONFIG_BOOL_WARDEN_WIN_ENABLED, CONFIG_BOOL_WARDEN_OSX_ENABLED, diff --git a/src/modules/Bots/CMakeLists.txt b/src/modules/Bots/CMakeLists.txt new file mode 100644 index 000000000..1168970b7 --- /dev/null +++ b/src/modules/Bots/CMakeLists.txt @@ -0,0 +1,126 @@ +# +# Copyright (C) 2005-2022 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 +# + +#Base files +file(GLOB Playerbot_Source ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/*.h) +source_group("Player Bot" FILES ${Playerbot_Source}) +file(GLOB AHbot_Source ${CMAKE_CURRENT_SOURCE_DIR}/ahbot/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ahbot/*.h) +source_group("AH Bot" FILES ${AHbot_Source}) + +set(SRC_GRP_BOTS + ${Playerbot_Source} + ${AHbot_Source} + ) +#Strategy files +file(GLOB Playerbot_Strategy ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/*.h) +source_group("Player Bot\\Strategies" FILES ${Playerbot_Strategy}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Strategy}) + +#Action files +file(GLOB Playerbot_Actions ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/actions/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/actions/*.h) +source_group("Player Bot\\Strategies\\Actions" FILES ${Playerbot_Actions}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Actions}) + +#Generic files +file(GLOB Playerbot_Generic ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/generic/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/generic/*.h) +source_group("Player Bot\\Strategies\\Generic" FILES ${Playerbot_Generic}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Generic}) + +#Trigger files +file(GLOB Playerbot_Triggers ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/triggers/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/triggers/*.h) +source_group("Player Bot\\Strategies\\Triggers" FILES ${Playerbot_Triggers}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Triggers}) + + +#Value files +file(GLOB Playerbot_Values ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/values/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/values/*.h) +source_group("Player Bot\\Strategies\\Values" FILES ${Playerbot_Values}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Values}) + +## Class files +#Deathknight AI +file(GLOB Playerbot_Deathknight ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/deathknight/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/deathknight/*.h) +source_group("Player Bot\\Strategies\\Deathknight" FILES ${Playerbot_Deathknight}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Deathknight}) + +#Druid AI +file(GLOB Playerbot_Druid ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/druid/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/druid/*.h) +source_group("Player Bot\\Strategies\\Druid" FILES ${Playerbot_Druid}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Druid}) + +#Hunter AI +file(GLOB Playerbot_Hunter ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/hunter/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/hunter/*.h) +source_group("Player Bot\\Strategies\\Hunter" FILES ${Playerbot_Hunter}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Hunter}) + +#Mage AI +file(GLOB Playerbot_Mage ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/mage/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/mage/*.h) +source_group("Player Bot\\Strategies\\Mage" FILES ${Playerbot_Mage}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Mage}) + +#Paladin AI +file(GLOB Playerbot_Paladin ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/paladin/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/paladin/*.h) +source_group("Player Bot\\Strategies\\Paladin" FILES ${Playerbot_Paladin}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Paladin}) + +#Priest AI +file(GLOB Playerbot_Priest ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/priest/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/priest/*.h) +source_group("Player Bot\\Strategies\\Priest" FILES ${Playerbot_Priest}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Priest}) + +#Rogue AI +file(GLOB Playerbot_Rogue ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/rogue/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/rogue/*.h) +source_group("Player Bot\\Strategies\\Rogue" FILES ${Playerbot_Rogue}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Rogue}) + +#Shaman AI +file(GLOB Playerbot_Shaman ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/shaman/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/shaman/*.h) +source_group("Player Bot\\Strategies\\Shaman" FILES ${Playerbot_Shaman}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Shaman}) + +#Warlock AI +file(GLOB Playerbot_Warlock ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/warlock/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/warlock/*.h) +source_group("Player Bot\\Strategies\\Warlock" FILES ${Playerbot_Warlock}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Warlock}) + +#Warrior AI +file(GLOB Playerbot_Warrior ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/warrior/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/playerbot/strategy/warrior/*.h) +source_group("Player Bot\\Strategies\\Warrior" FILES ${Playerbot_Warrior}) +LIST(APPEND SRC_GRP_BOTS ${Playerbot_Warrior}) + +# Install config files +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/playerbot/aiplayerbot.conf.dist.in ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist DESTINATION ${CONF_INSTALL_DIR}) + +add_library(bots STATIC ${SRC_GRP_BOTS}) + +target_include_directories(bots + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/playerbot + ${CMAKE_CURRENT_SOURCE_DIR}/ahbot + ) + +target_link_libraries(bots + PUBLIC + game) + +# Generate PCH +if(PCH) + ADD_CXX_PCH(bots ${CMAKE_CURRENT_SOURCE_DIR}/botpch.h ${CMAKE_CURRENT_SOURCE_DIR}/botpch.cpp) +endif() \ No newline at end of file diff --git a/src/modules/Bots/ahbot/AhBot.cpp b/src/modules/Bots/ahbot/AhBot.cpp new file mode 100644 index 000000000..8e63616c1 --- /dev/null +++ b/src/modules/Bots/ahbot/AhBot.cpp @@ -0,0 +1,1295 @@ +#include "../botpch.h" +#include "Category.h" +#include "ItemBag.h" +#include "AhBot.h" +#include "World.h" +#include "Config.h" +#include "Chat.h" +#include "AhBotConfig.h" +#include "AuctionHouseMgr.h" +#include "WorldSession.h" +#include "Player.h" +#include "ObjectAccessor.h" +#include "ObjectGuid.h" +#include "ObjectMgr.h" +#include "playerbot/PlayerbotAIConfig.h" +#include "AccountMgr.h" +#include "playerbot/playerbot.h" +#include "Player.h" +#include "Mail.h" + +using namespace ahbot; + +bool Player::MinimalLoadFromDB( QueryResult *result, uint32 guid ) +{ + bool delete_result = true; + if (!result) + { + // 0 1 2 3 4 5 6 7 + result = CharacterDatabase.PQuery("SELECT name, position_x, position_y, position_z, map, totaltime, leveltime, at_login FROM characters WHERE guid = '%u'",guid); + if (!result) + { + return false; + } + } + else + { + delete_result = false; + } + + Field *fields = result->Fetch(); + + // overwrite possible wrong/corrupted guid + Object::_Create( guid, 0, HIGHGUID_PLAYER ); + + m_name = fields[0].GetString(); + + Relocate(fields[1].GetFloat(),fields[2].GetFloat(),fields[3].GetFloat()); + SetLocationMapId(fields[4].GetUInt32()); + + m_Played_time[PLAYED_TIME_TOTAL] = fields[5].GetUInt32(); + m_Played_time[PLAYED_TIME_LEVEL] = fields[6].GetUInt32(); + + m_atLoginFlags = fields[7].GetUInt32(); + + if (delete_result) + { + delete result; + } + + for (int i = 0; i < PLAYER_SLOTS_COUNT; ++i) + { + m_items[i] = NULL; + } + + if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + { + m_deathState = DEAD; + } + + return true; +} + +bool AhBot::HandleAhBotCommand(ChatHandler* handler, char const* args) +{ + auctionbot.HandleCommand(args); + return true; +} + +uint32 AhBot::auctionIds[MAX_AUCTIONS] = {1,6,7}; +uint32 AhBot::auctioneers[MAX_AUCTIONS] = {79707,4656,23442}; +map AhBot::factions; + +void AhBot::Init() +{ + sLog.outString("Initializing AhBot by ike3"); + + if (!sAhBotConfig.Initialize()) + { + return; + } + + factions[1] = 1; + factions[2] = 1; + factions[3] = 1; + factions[4] = 2; + factions[5] = 2; + factions[6] = 2; + factions[7] = 3; + + availableItems.Init(); + + sLog.outString("AhBot configuration loaded"); +} + +AhBot::~AhBot() +{ +} + +ObjectGuid AhBot::GetAHBplayerGUID() +{ + return ObjectGuid(sAhBotConfig.guid); +} + +class AhbotThread: public ACE_Task +{ +private: + AhBot* bot; +public: + explicit AhbotThread(AhBot* bot) : bot(bot) {} + int svc(void) { bot->ForceUpdate(); return 0; } +}; + +void AhBot::Update() +{ + time_t now = time(0); + + if (now < nextAICheckTime) + { + return; + } + + if (updating) + { + return; + } + + nextAICheckTime = time(0) + sAhBotConfig.updateInterval; + + AhbotThread *thread = new AhbotThread(this); + thread->activate(); +} + +void AhBot::ForceUpdate() +{ + if (!sAhBotConfig.enabled) + { + return; + } + + if (updating) + { + return; + } + + string msg = "AhBot is now checking auctions in the background"; + sLog.outString(msg.c_str()); + sWorld.SendWorldText(3, msg.c_str()); + updating = true; + + if (!allBidders.size()) + { + LoadRandomBots(); + } + + if (!allBidders.size()) + { + sLog.outError( "Ahbot is enabled but there are no bidders available"); + return; + } + + CheckCategoryMultipliers(); + + int answered = 0, added = 0; + for (int i = 0; i < MAX_AUCTIONS; i++) + { + InAuctionItemsBag inAuctionItems(auctionIds[i]); + inAuctionItems.Init(true); + + for (int j = 0; j < CategoryList::instance.size(); j++) + { + Category* category = CategoryList::instance[j]; + answered += Answer(i, category, &inAuctionItems); + added += AddAuctions(i, category, &inAuctionItems); + } + } + + CleanupHistory(); + + sLog.outString("AhBot auction check finished. %d auctions answered, %d new auctions added. Next check in %d seconds", + answered, added, sAhBotConfig.updateInterval); + ostringstream out; out << "AhBot auction check finished. Next check in " << sAhBotConfig.updateInterval << " seconds"; + sWorld.SendWorldText(3, out.str().c_str()); + updating = false; +} + +struct SortByPricePredicate +{ + bool operator()(AuctionEntry* const & a, AuctionEntry* const & b) const + { + if (a->startbid == b->startbid) + { + return a->buyout < b->buyout; + } + + return a->startbid < b->startbid; + } +}; + +vector AhBot::LoadAuctions(const AuctionHouseObject::AuctionEntryMap& auctionEntryMap, + Category*& category, int& auction) +{ + vector entries; + for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionEntryMap.begin(); itr != auctionEntryMap.end(); ++itr) + { + AuctionEntry *entry = itr->second; + if (IsBotAuction(entry->owner) || IsBotAuction(entry->bidder)) + { + continue; + } + + Item *item = sAuctionMgr.GetAItem(entry->itemGuidLow); + if (!item) + { + continue; + } + + if (!category->Contains(item->GetProto())) + { + continue; + } + + uint32 price = category->GetPricingStrategy()->GetBuyPrice(item->GetProto(), auctionIds[auction]); + if (!price || !item->GetCount()) + { + sLog.outDetail("%s (x%d) in auction %d: price cannot be determined", + item->GetProto()->Name1, item->GetCount(), auctionIds[auction]); + continue; + } + + entries.push_back(entry); + } + sort(entries.begin(), entries.end(), SortByPricePredicate()); + return entries; +} + +void AhBot::FindMinPrice(const AuctionHouseObject::AuctionEntryMap& auctionEntryMap, AuctionEntry*& entry, Item*& item, uint32* minBid, + uint32* minBuyout) +{ + *minBid = 0; + *minBuyout = 0; + for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionEntryMap.begin(); itr != auctionEntryMap.end(); ++itr) + { + AuctionEntry *other = itr->second; + if (other->owner == entry->owner) + { + continue; + } + + Item *otherItem = sAuctionMgr.GetAItem(other->itemGuidLow); + if (!otherItem || !otherItem->GetCount() || otherItem->GetProto()->ItemId != item->GetProto()->ItemId) + { + continue; + } + + uint32 startbid = other->startbid / otherItem->GetCount() * item->GetCount(); + uint32 bid = other->bid / otherItem->GetCount() * item->GetCount(); + uint32 buyout = other->buyout / otherItem->GetCount() * item->GetCount(); + + if (!bid && startbid && (!*minBid || *minBid > startbid)) + { + *minBid = startbid; + } + + if (bid && (*minBid || *minBid > bid)) + { + *minBid = bid; + } + + if (buyout && (!*minBuyout || *minBuyout > buyout)) + { + *minBuyout = buyout; + } + } +} + +int AhBot::Answer(int auction, Category* category, ItemBag* inAuctionItems) +{ + const AuctionHouseEntry* ahEntry = sAuctionHouseStore.LookupEntry(auctionIds[auction]); + if (!ahEntry) + { + return 0; + } + + int answered = 0; + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + const AuctionHouseObject::AuctionEntryMap& auctionEntryMap = auctionHouse->GetAuctions(); + int64 availableMoney = GetAvailableMoney(auctionIds[auction]); + + vector entries = LoadAuctions(auctionEntryMap, category, auction); + for (vector::iterator itr = entries.begin(); itr != entries.end(); ++itr) + { + AuctionEntry *entry = *itr; + + Item *item = sAuctionMgr.GetAItem(entry->itemGuidLow); + if (!item || !item->GetCount()) + { + continue; + } + + const ItemPrototype* proto = item->GetProto(); + vector items = availableItems.Get(category); + if (find(items.begin(), items.end(), proto->ItemId) == items.end()) + { + sLog.outDetail( "%s (x%d) in auction %d: unavailable item", + item->GetProto()->Name1, item->GetCount(), auctionIds[auction]); + continue; + } + + uint32 answerCount = GetAnswerCount(proto->ItemId, auctionIds[auction], sAhBotConfig.itemBuyMaxInterval); + uint32 maxAnswerCount = category->GetMaxAllowedItemAuctionCount(proto); + if (maxAnswerCount && answerCount > maxAnswerCount) + { + sLog.outDetail( "%s (x%d) in auction %d: answer count %d > %d (max)", + item->GetProto()->Name1, item->GetCount(), auctionIds[auction], answerCount, maxAnswerCount); + continue; + } + + if (proto->RequiredLevel > sAhBotConfig.maxRequiredLevel || proto->ItemLevel > sAhBotConfig.maxItemLevel) + { + sLog.outDetail( "%s (x%d) in auction %d: above max required or item level", + item->GetProto()->Name1, item->GetCount(), auctionIds[auction]); + continue; + } + + uint32 price = category->GetPricingStrategy()->GetBuyPrice(proto, auctionIds[auction]); + if (!price) + { + sLog.outDetail( "%s (x%d) in auction %d: cannot determine price", + item->GetProto()->Name1, item->GetCount(), auctionIds[auction]); + continue; + } + + uint32 bidPrice = item->GetCount() * price; + uint32 buyoutPrice = item->GetCount() * urand(price, 4 * price / 3); + + uint32 curPrice = entry->bid; + if (!curPrice) curPrice = entry->startbid; + { + if (!curPrice) curPrice = entry->buyout; + } + + uint32 bidder = GetRandomBidder(auctionIds[auction]); + if (!bidder) + { + sLog.outError( "No bidders for auction %d", auctionIds[auction]); + break; + } + + if (curPrice > buyoutPrice) + { + sLog.outDetail( "%s (x%d) in auction %d: price %d > %d (buyout price)", + proto->Name1, item->GetCount(), auctionIds[auction], curPrice, buyoutPrice); + CheckSendMail(bidder, buyoutPrice, entry); + continue; + } + + if (availableMoney < curPrice) + { + sLog.outDetail( "%s (x%d) in auction %d: price %d > %d (available money)", + proto->Name1, item->GetCount(), auctionIds[auction], curPrice, availableMoney); + continue; + } + + uint32 minBid = 0, minBuyout = 0; + FindMinPrice(auctionEntryMap, entry, item, &minBid, &minBuyout); + + if (minBid && entry->bid && minBid < entry->bid) + { + sLog.outDetail( "%s (x%d) in auction %d: %d (bid) > %d (minBid)", + proto->Name1, item->GetCount(), auctionIds[auction], entry->bid, minBid); + continue; + } + + if (minBid && entry->startbid && minBid < entry->startbid) + { + sLog.outDetail( "%s (x%d) in auction %d: %d (startbid) > %d (minBid)", + proto->Name1, item->GetCount(), auctionIds[auction], entry->startbid, minBid); + CheckSendMail(bidder, minBid, entry); + continue; + } + + double priceLevel = (double)curPrice / (double)buyoutPrice; + uint32 buytime = GetBuyTime(entry->Id, proto->ItemId, auctionIds[auction], category, priceLevel); + if (time(0) < buytime) + { + sLog.outDetail( "%s (x%d) in auction %d: will buy/bid in %d seconds", + proto->Name1, item->GetCount(), auctionIds[auction], buytime - time(0)); + continue; + } + + entry->bidder = bidder; + entry->bid = curPrice + urand(1, 1 + bidPrice / 10); + availableMoney -= curPrice; + + updateMarketPrice(item->GetProto()->ItemId, entry->buyout / item->GetCount(), auctionIds[auction]); + + if ((entry->buyout && (entry->bid >= entry->buyout || 100 * (entry->buyout - entry->bid) / price < 25)) && + !(minBuyout && entry->buyout && minBuyout < entry->buyout)) + { + sLog.outDetail( "AhBot %d won %s (x%d) in auction %d for %d", + bidder, item->GetProto()->Name1, item->GetCount(), auctionIds[auction], entry->buyout); + + entry->bid = entry->buyout; + entry->AuctionBidWinning(NULL); + } + else + { + sLog.outDetail( "AhBot %d placed bid %d for %s (x%d) in auction %d", + bidder, entry->bid, item->GetProto()->Name1, item->GetCount(), auctionIds[auction]); + + CharacterDatabase.PExecute("UPDATE auction SET buyguid = '%u',lastbid = '%u' WHERE id = '%u'", + entry->bidder, entry->bid, entry->Id); + AddToHistory(entry, AHBOT_WON_BID); + } + + CharacterDatabase.PExecute("DELETE FROM ahbot_history WHERE item = '%u' AND won = 4 AND auction_house = '%u' ", + proto->ItemId, factions[auctionIds[auction]]); + + answered++; + } + + return answered; +} + +uint32 AhBot::GetTime(string category, uint32 id, uint32 auctionHouse, uint32 type) +{ + QueryResult* results = CharacterDatabase.PQuery("SELECT MAX(buytime) FROM ahbot_history WHERE item = '%u' AND won = '%u' AND auction_house = '%u' AND category = '%s'", + id, type, factions[auctionHouse], category.c_str()); + + if (!results) + { + return 0; + } + + Field* fields = results->Fetch(); + uint32 result = fields[0].GetUInt32(); + delete results; + + return result; +} + +void AhBot::SetTime(string category, uint32 id, uint32 auctionHouse, uint32 type, uint32 value) +{ + CharacterDatabase.PExecute("DELETE FROM ahbot_history WHERE item = '%u' AND won = '%u' AND auction_house = '%u' AND category = '%s'", + id, type, factions[auctionHouse], category.c_str()); + + CharacterDatabase.PExecute("INSERT INTO ahbot_history (buytime, item, bid, buyout, category, won, auction_house) " + "VALUES ('%u', '%u', '%u', '%u', '%s', '%u', '%u')", + value, id, 0, 0, + category.c_str(), type, factions[auctionHouse]); +} + +uint32 AhBot::GetBuyTime(uint32 entry, uint32 itemId, uint32 auctionHouse, Category*& category, double priceLevel) +{ + uint32 entryTime = GetTime("entry", entry, auctionHouse, AHBOT_WON_DELAY); + if (entryTime > time(0)) + { + return entryTime; + } + + uint32 result = entryTime; + + string categoryName = category->GetName(); + uint32 categoryTime = GetTime(categoryName, 0, auctionHouse, AHBOT_WON_DELAY); + uint32 itemTime = GetTime("item", itemId, auctionHouse, AHBOT_WON_DELAY); + + if (categoryTime < time(0)) categoryTime = time(0); + { + if (itemTime < time(0)) itemTime = time(0); + } + + double rarity = category->GetPricingStrategy()->GetRarityPriceMultiplier(itemId); + categoryTime += urand(sAhBotConfig.itemBuyMinInterval, sAhBotConfig.itemBuyMaxInterval) * priceLevel; + itemTime += urand(sAhBotConfig.itemBuyMinInterval, sAhBotConfig.itemBuyMaxInterval) * priceLevel / rarity; + entryTime = max(categoryTime, itemTime); + + SetTime(categoryName, 0, auctionHouse, AHBOT_WON_DELAY, categoryTime); + SetTime("item", itemId, auctionHouse, AHBOT_WON_DELAY, itemTime); + SetTime("entry", entry, auctionHouse, AHBOT_WON_DELAY, entryTime); + + return result ? result : entryTime; +} + +uint32 AhBot::GetSellTime(uint32 itemId, uint32 auctionHouse, Category*& category) +{ + uint32 itemSellTime = GetTime("item", itemId, auctionHouse, AHBOT_SELL_DELAY); + uint32 itemBuyTime = GetTime("item", itemId, auctionHouse, AHBOT_WON_DELAY); + uint32 itemTime = max(itemSellTime, itemBuyTime); + + if (itemTime > time(0)) + { + return itemTime; + } + + uint32 result = itemTime; + + string categoryName = category->GetName(); + uint32 categorySellTime = GetTime(categoryName, 0, auctionHouse, AHBOT_SELL_DELAY); + uint32 categoryBuyTime = GetTime(categoryName, 0, auctionHouse, AHBOT_WON_DELAY); + uint32 categoryTime = max(categorySellTime, categoryBuyTime); + + if (categoryTime < time(0)) categoryTime = time(0); + { + if (itemTime < time(0)) itemTime = time(0); + } + + double rarity = category->GetPricingStrategy()->GetRarityPriceMultiplier(itemId); + categoryTime += urand(sAhBotConfig.itemSellMinInterval, sAhBotConfig.itemSellMaxInterval); + itemTime += urand(sAhBotConfig.itemSellMinInterval, sAhBotConfig.itemSellMaxInterval) * rarity; + itemTime = max(itemTime, categoryTime); + + SetTime(categoryName, 0, auctionHouse, AHBOT_SELL_DELAY, categoryTime); + SetTime("item", itemId, auctionHouse, AHBOT_SELL_DELAY, itemTime); + + return result ? result : itemTime; +} + +int AhBot::AddAuctions(int auction, Category* category, ItemBag* inAuctionItems) +{ + vector& inAuction = inAuctionItems->Get(category); + + int32 maxAllowedAuctionCount = categoryMaxAuctionCount[category->GetName()]; + if (inAuctionItems->GetCount(category) >= maxAllowedAuctionCount) + { + return 0; + } + + int added = 0; + vector available = availableItems.Get(category); + for (int32 i = 0; i <= maxAllowedAuctionCount && available.size() > 0 && inAuctionItems->GetCount(category) < maxAllowedAuctionCount; ++i) + { + uint32 index = urand(0, available.size() - 1); + uint32 itemId = available[index]; + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + int32 maxAllowedItems = category->GetMaxAllowedItemAuctionCount(proto); + if (maxAllowedItems && inAuctionItems->GetCount(category, proto->ItemId) >= maxAllowedItems) + { + continue; + } + + uint32 sellTime = GetSellTime(proto->ItemId, auctionIds[auction], category); + if (time(0) < sellTime) + { + sLog.outDetail( "%s in auction %d: will add in %d seconds", + proto->Name1, auctionIds[auction], sellTime - time(0)); + continue; + } + else if (time(0) - sellTime > sAhBotConfig.maxSellInterval) + { + sLog.outDetail( "%s in auction %d: too old (%d secs)", + proto->Name1, auctionIds[auction], time(0) - sellTime); + continue; + } + + inAuctionItems->Add(proto); + added += AddAuction(auction, category, proto); + } + + return added; +} + +int AhBot::AddAuction(int auction, Category* category, ItemPrototype const* proto) +{ + uint32 owner = GetRandomBidder(auctionIds[auction]); + if (!owner) + { + sLog.outError( "No bidders for auction %d", auctionIds[auction]); + return 0; + } + + + string name; + if (!sObjectMgr.GetPlayerNameByGUID((ObjectGuid)(uint64)owner, name)) + { + return 0; + } + + uint32 price = category->GetPricingStrategy()->GetSellPrice(proto, auctionIds[auction]); + + sLog.outDetail( "AddAuction: market price adjust"); + updateMarketPrice(proto->ItemId, price, auctionIds[auction]); + + price = category->GetPricingStrategy()->GetSellPrice(proto, auctionIds[auction]); + + uint32 stackCount = category->GetStackCount(proto); + if (!price || !stackCount) + { + return 0; + } + + if (price > sAhBotConfig.stackReducePrice) + { + stackCount /= (price / sAhBotConfig.stackReducePrice); + } + + if (!stackCount) + { + stackCount = 1; + } + + if (urand(0, 100) <= sAhBotConfig.underPriceProbability * 100) + { + price = price * 100 / urand(100, 200); + } + + uint32 bidPrice = PricingStrategy::RoundPrice(stackCount * price); + uint32 buyoutPrice = PricingStrategy::RoundPrice(stackCount * urand(price, 4 * price / 3)); + + Item* item = Item::CreateItem(proto->ItemId, stackCount); + if (!item) + { + return 0; + } + + uint32 randomPropertyId = Item::GenerateItemRandomPropertyId(proto->ItemId); + if (randomPropertyId) + { + item->SetItemRandomProperties(randomPropertyId); + } + + AuctionHouseEntry const* ahEntry = sAuctionHouseStore.LookupEntry(auctionIds[auction]); + if(!ahEntry) + { + return 0; + } + + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + + AuctionEntry* auctionEntry = new AuctionEntry; + auctionEntry->Id = sObjectMgr.GenerateAuctionID(); + auctionEntry->itemGuidLow = item->GetGUIDLow(); + auctionEntry->itemTemplate = item->GetEntry(); + auctionEntry->owner = owner; + auctionEntry->startbid = bidPrice; + auctionEntry->buyout = buyoutPrice; + auctionEntry->bidder = 0; + auctionEntry->bid = 0; + auctionEntry->deposit = 0; + auctionEntry->expireTime = (time_t) (urand(8, 24) * 60 * 60 + time(NULL)); + auctionEntry->auctionHouseEntry = ahEntry; + + item->SaveToDB(); + + sAuctionMgr.AddAItem(item); + + + auctionHouse->AddAuction(auctionEntry); + + auctionHouse->AddAuction(auctionEntry); + auctionEntry->SaveToDB(); + + sLog.outDetail( "AhBot %d added %d of %s to auction %d for %d..%d", owner, stackCount, proto->Name1, auctionIds[auction], bidPrice, buyoutPrice); + return 1; +} + +void AhBot::HandleCommand(string command) +{ + if (!sAhBotConfig.enabled) + { + return; + } + + if (command == "expire") + { + for (int i = 0; i < MAX_AUCTIONS; i++) + { + Expire(i); + } + + return; + } + + if (command == "stats") + { + for (int i = 0; i < MAX_AUCTIONS; i++) + { + PrintStats(i); + } + + return; + } + + if (command == "update") + { + AhbotThread *thread = new AhbotThread(this); + thread->activate(); + return; + } + + uint32 itemId = atoi(command.c_str()); + if (!itemId) + { + sLog.outString("ahbot stats - show short summary"); + sLog.outString("ahbot expire - expire all auctions"); + sLog.outString("ahbot update - update all auctions"); + sLog.outString("ahbot - show item price"); + return; + } + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return; + } + + for (int i=0; iContains(proto)) + { + vector items = availableItems.Get(category); + if (find(items.begin(), items.end(), proto->ItemId) == items.end()) + { + continue; + } + + ostringstream out; + out << proto->Name1 << " (" << category->GetDisplayName() << "), " + << category->GetMaxAllowedAuctionCount() << "x" << category->GetMaxAllowedItemAuctionCount(proto) + << "x" << category->GetStackCount(proto) << " max" + << "\n"; + for (int auction = 0; auction < MAX_AUCTIONS; auction++) + { + const AuctionHouseEntry* ahEntry = sAuctionHouseStore.LookupEntry(auctionIds[auction]); + out << "--- auction house " << auctionIds[auction] << "(faction: " << factions[auctionIds[auction]] << ", money: " + << GetAvailableMoney(auctionIds[auction]) + << ") ---\n"; + + out << "sell: " << ChatHelper::formatMoney(category->GetPricingStrategy()->GetSellPrice(proto, auctionIds[auction], true)) + << " (" << category->GetPricingStrategy()->ExplainSellPrice(proto, auctionIds[auction]) << ")" + << "\n"; + + out << "buy: " << ChatHelper::formatMoney(category->GetPricingStrategy()->GetBuyPrice(proto, auctionIds[auction])) + << " (" << category->GetPricingStrategy()->ExplainBuyPrice(proto, auctionIds[auction]) << ")" + << "\n"; + + out << "market: " << ChatHelper::formatMoney(category->GetPricingStrategy()->GetMarketPrice(proto->ItemId, auctionIds[auction])) + << "\n"; + } + sLog.outString(out.str().c_str()); + } + } +} + +void AhBot::Expire(int auction) +{ + if (!sAhBotConfig.enabled) + { + return; + } + + AuctionHouseEntry const* ahEntry = sAuctionHouseStore.LookupEntry(auctionIds[auction]); + if(!ahEntry) + { + return; + } + + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + + AuctionHouseObject::AuctionEntryMap const& auctions = auctionHouse->GetAuctions(); + AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctions.begin(); + + int count = 0; + while (itr != auctions.end()) + { + if (IsBotAuction(itr->second->owner)) + { + itr->second->expireTime = sWorld.GetGameTime(); + count++; + } + + ++itr; + } + + CharacterDatabase.PExecute("DELETE FROM ahbot_category"); + sLog.outString("%d auctions marked as expired in auction %d", count, auctionIds[auction]); +} + +void AhBot::PrintStats(int auction) +{ + if (!sAhBotConfig.enabled) + { + return; + } + + AuctionHouseEntry const* ahEntry = sAuctionHouseStore.LookupEntry(auctionIds[auction]); + if(!ahEntry) + { + return; + } + + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + AuctionHouseObject::AuctionEntryMap const& auctions = auctionHouse->GetAuctions(); + + sLog.outString("%d auctions available on auction house %d", auctions.size(), auctionIds[auction]); +} + +void AhBot::AddToHistory(AuctionEntry* entry, uint32 won) +{ + if (!sAhBotConfig.enabled || !entry) + { + return; + } + + if (!IsBotAuction(entry->owner) && !IsBotAuction(entry->bidder)) + { + return; + } + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(entry->itemTemplate); + if (!proto) + { + return; + } + + string category = ""; + for (int i = 0; i < CategoryList::instance.size(); i++) + { + if (CategoryList::instance[i]->Contains(proto)) + { + category = CategoryList::instance[i]->GetName(); + break; + } + } + + if (!won) + { + won = AHBOT_WON_PLAYER; + if (IsBotAuction(entry->bidder)) + { + won = AHBOT_WON_SELF; + } + } + + sLog.outDetail( "AddToHistory: market price adjust"); + int count = entry->itemCount ? entry->itemCount : 1; + updateMarketPrice(proto->ItemId, entry->buyout / count, entry->auctionHouseEntry->houseId); + + uint32 now = time(0); + CharacterDatabase.PExecute("INSERT INTO ahbot_history (buytime, item, bid, buyout, category, won, auction_house) " + "VALUES ('%u', '%u', '%u', '%u', '%s', '%u', '%u')", + now, entry->itemTemplate, entry->bid ? entry->bid : entry->startbid, entry->buyout, + category.c_str(), won, factions[entry->auctionHouseEntry->houseId]); +} + +uint32 AhBot::GetAnswerCount(uint32 itemId, uint32 auctionHouse, uint32 withinTime) +{ + uint32 count = 0; + + QueryResult* results = CharacterDatabase.PQuery("SELECT COUNT(*) FROM ahbot_history WHERE " + "item = '%u' AND won in (2, 3) AND auction_house = '%u' AND buytime > '%u'", + itemId, factions[auctionHouse], time(0) - withinTime); + if (results) + { + do + { + Field* fields = results->Fetch(); + count = fields[0].GetUInt32(); + } while (results->NextRow()); + + delete results; + } + + return count; +} + +void AhBot::CleanupHistory() +{ + uint32 when = time(0) - 3600 * 24 * sAhBotConfig.historyDays; + CharacterDatabase.PExecute("DELETE FROM ahbot_history WHERE buytime < '%u'", when); +} + +uint32 AhBot::GetAvailableMoney(uint32 auctionHouse) +{ + int64 result = sAhBotConfig.alwaysAvailableMoney; + + map data; + data[AHBOT_WON_PLAYER] = 0; + data[AHBOT_WON_SELF] = 0; + + const AuctionHouseEntry* ahEntry = sAuctionHouseStore.LookupEntry(auctionHouse); + QueryResult* results = CharacterDatabase.PQuery( + "SELECT won, SUM(bid) FROM ahbot_history WHERE auction_house = '%u' GROUP BY won HAVING won > 0 ORDER BY won", + factions[auctionHouse]); + if (results) + { + do + { + Field* fields = results->Fetch(); + data[fields[0].GetUInt32()] = fields[1].GetUInt32(); + + } while (results->NextRow()); + + delete results; + } + + results = CharacterDatabase.PQuery( + "SELECT max(buytime) FROM ahbot_history WHERE auction_house = '%u' AND won = '2'", + factions[auctionHouse]); + if (results) + { + Field* fields = results->Fetch(); + uint32 lastBuyTime = fields[0].GetUInt32(); + uint32 now = time(0); + if (lastBuyTime && now > lastBuyTime) + { + result += (now - lastBuyTime) / 3600 / 24 * sAhBotConfig.alwaysAvailableMoney; + } + + delete results; + } + + AuctionHouseObject::AuctionEntryMap const& auctionEntryMap = sAuctionMgr.GetAuctionsMap(ahEntry)->GetAuctions(); + for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionEntryMap.begin(); itr != auctionEntryMap.end(); ++itr) + { + AuctionEntry *entry = itr->second; + if (!IsBotAuction(entry->bidder)) + { + continue; + } + + result -= entry->bid; + } + + result += (data[AHBOT_WON_PLAYER] - data[AHBOT_WON_SELF]); + return result < 0 ? 0 : (uint32)result; +} + +void AhBot::CheckCategoryMultipliers() +{ + QueryResult* results = CharacterDatabase.PQuery("SELECT category, multiplier, max_auction_count, expire_time FROM ahbot_category"); + if (results) + { + do + { + Field* fields = results->Fetch(); + categoryMultipliers[fields[0].GetString()] = fields[1].GetFloat(); + categoryMaxAuctionCount[fields[0].GetString()] = fields[2].GetInt32(); + categoryMultiplierExpireTimes[fields[0].GetString()] = fields[3].GetUInt64(); + + } while (results->NextRow()); + + delete results; + } + + CharacterDatabase.PExecute("DELETE FROM ahbot_category"); + + set tmp; + for (int i = 0; i < CategoryList::instance.size(); i++) + { + string name = CategoryList::instance[i]->GetName(); + if (tmp.find(name) != tmp.end()) + { + continue; + } + + tmp.insert(name); + if (categoryMultiplierExpireTimes[name] <= time(0) || categoryMultipliers[name] <= 0) + { + uint32 k = urand(1, 100); + double m = 1.0; + double r = (double)urand(100, 200) / 100.0; + if (k < 50) + { + m = r; // 1..2 + } + else if (k < 80) + { + m = 1 + r; // 2..3 + } + else if (k < 90) + { + m = 2 + r; // 3..4 + } + else + { + m = 3 + r; // 4..5 + } + categoryMultipliers[name] = m; + uint32 maxAllowedAuctionCount = CategoryList::instance[i]->GetMaxAllowedAuctionCount(); + categoryMaxAuctionCount[name] = urand(maxAllowedAuctionCount / 2, maxAllowedAuctionCount); + categoryMultiplierExpireTimes[name] = time(0) + urand(4, 7) * 3600 * 24; + } + + CharacterDatabase.PExecute("INSERT INTO ahbot_category (category, multiplier, max_auction_count, expire_time) " + "VALUES ('%s', '%f', '%u', '%u')", + name.c_str(), categoryMultipliers[name], categoryMaxAuctionCount[name], categoryMultiplierExpireTimes[name]); + } +} + + +void AhBot::updateMarketPrice(uint32 itemId, double price, uint32 auctionHouse) +{ + double marketPrice = 0; + + QueryResult* results = CharacterDatabase.PQuery("SELECT price FROM ahbot_price WHERE item = '%u' AND auction_house = '%u'", itemId, auctionHouse); + if (results) + { + marketPrice = results->Fetch()[0].GetFloat(); + delete results; + } + + if (marketPrice > 0) + { + marketPrice = (marketPrice + price) / 2; + } + else + { + marketPrice = price; + } + + CharacterDatabase.PExecute("DELETE FROM ahbot_price WHERE item = '%u' AND auction_house = '%u'", itemId, auctionHouse); + CharacterDatabase.PExecute("INSERT INTO ahbot_price (item, price, auction_house) VALUES ('%u', '%lf', '%u')", itemId, marketPrice, auctionHouse); +} + +bool AhBot::IsBotAuction(uint32 bidder) +{ + return allBidders.find(bidder) != allBidders.end(); +} + +uint32 AhBot::GetRandomBidder(uint32 auctionHouse) +{ + vector guids = bidders[factions[auctionHouse]]; + if (guids.empty()) + { + return 0; + } + + vector online; + for (vector::iterator i = guids.begin(); i != guids.end(); ++i) + { + uint32 guid = *i; + string name; + if (!sObjectMgr.GetPlayerNameByGUID((ObjectGuid)(uint64)guid, name)) + { + continue; + } + + online.push_back(guid); + } + + if (online.empty()) + { + return 0; + } + + int index = urand(0, online.size() - 1); + return online[index]; +} + +void AhBot::LoadRandomBots() +{ + for (list::iterator i = sPlayerbotAIConfig.randomBotAccounts.begin(); i != sPlayerbotAIConfig.randomBotAccounts.end(); i++) + { + uint32 accountId = *i; + if (!sAccountMgr.GetCharactersCount(accountId)) + { + continue; + } + + QueryResult *result = CharacterDatabase.PQuery("SELECT guid, race FROM characters WHERE account = '%u'", accountId); + if (!result) + { + continue; + } + + do + { + Field* fields = result->Fetch(); + uint32 guid = fields[0].GetUInt32(); + uint8 race = fields[1].GetUInt8(); + uint32 auctionHouse = PlayerbotAI::IsOpposing(race, RACE_HUMAN) ? 2 : 1; + bidders[auctionHouse].push_back(guid); + bidders[3].push_back(guid); + allBidders.insert(guid); + } while (result->NextRow()); + delete result; + } + + + if (allBidders.empty() && sAhBotConfig.guid) + { + uint32 guid = sAhBotConfig.guid; + allBidders.insert(guid); + for (int i = 1; i <= 3; i++) + { + bidders[i].push_back(guid); + } + } + + sLog.outDetail( "{A=%d,H=%d,N=%d} bidders loaded", bidders[1].size(), bidders[2].size(), bidders[3].size()); +} + +int32 AhBot::GetSellPrice(ItemPrototype const* proto) +{ + if (!sAhBotConfig.enabled) + { + return 0; + } + + int32 maxPrice = 0; + for (int i=0; iContains(proto)) + { + continue; + } + + vector items = availableItems.Get(category); + if (find(items.begin(), items.end(), proto->ItemId) == items.end()) + { + continue; + } + + for (int auction = 0; auction < MAX_AUCTIONS; auction++) + { + int32 price = (int32)category->GetPricingStrategy()->GetSellPrice(proto, auctionIds[auction]); + if (!price) + { + price = (int32)category->GetPricingStrategy()->GetBuyPrice(proto, auctionIds[auction]); + } + + if (price > maxPrice) + { + maxPrice = price; + } + } + } + + return maxPrice; +} + +int32 AhBot::GetBuyPrice(ItemPrototype const* proto) +{ + if (!sAhBotConfig.enabled) + { + return 0; + } + + int32 maxPrice = 0; + for (int i=0; iContains(proto)) + { + continue; + } + + vector items = availableItems.Get(category); + if (find(items.begin(), items.end(), proto->ItemId) == items.end()) + { + continue; + } + + for (int auction = 0; auction < MAX_AUCTIONS; auction++) + { + int32 price = (int32)category->GetPricingStrategy()->GetBuyPrice(proto, auctionIds[auction]); + if (!price) + { + continue; + } + + if (price > maxPrice) + { + maxPrice = price; + } + } + } + + return maxPrice; +} + +double AhBot::GetRarityPriceMultiplier(const ItemPrototype* proto) +{ + if (!sAhBotConfig.enabled) + { + return 1.0; + } + + for (int i=0; iContains(proto)) + { + continue; + } + + return category->GetPricingStrategy()->GetRarityPriceMultiplier(proto->ItemId); + } + + return 1.0; + +} + +bool AhBot::IsUsedBySkill(const ItemPrototype* proto, uint32 skillId) +{ + if (!sAhBotConfig.enabled) + { + return false; + } + + for (int i=0; iGetSkillId() == skillId && category->Contains(proto)) + { + return true; + } + } + + return false; +} + +void AhBot::CheckSendMail(uint32 bidder, uint32 price, AuctionEntry *entry) +{ + if (!sAhBotConfig.sendmail) + { + return; + } + + time_t entryTime = GetTime("entry", entry->Id, entry->auctionHouseEntry->houseId, AHBOT_SENDMAIL); + if (entryTime > time(0)) + { + return; + } + + const AuctionHouseEntry* ahEntry = sAuctionHouseStore.LookupEntry(entry->auctionHouseEntry->houseId); + if (!ahEntry) + { + return; + } + + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + const AuctionHouseObject::AuctionEntryMap& auctionEntryMap = auctionHouse->GetAuctions(); + for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionEntryMap.begin(); itr != auctionEntryMap.end(); ++itr) + { + AuctionEntry *otherEntry = itr->second; + if (otherEntry->owner == entry->owner && otherEntry->Id != entry->Id && otherEntry->itemTemplate == entry->itemTemplate) + { + time_t otherEntryTime = GetTime("entry", otherEntry->Id, entry->auctionHouseEntry->houseId, AHBOT_SENDMAIL); + if (otherEntryTime > time(0)) + { + return; + } + } + } + + ostringstream body; + body << "Hello,\n"; + body << "\n"; + Item *item = sAuctionMgr.GetAItem(entry->itemGuidLow); + if (!item) + { + return; + } + body << "I see you posted " << ChatHelper::formatItem(item->GetProto(), item->GetCount()); + body << " to the AH and I really need that at the moment. Could you lower your price at least to "; + body << ChatHelper::formatMoney(PricingStrategy::RoundPrice(price)) << "? I'll buy it then.\n"; + body << "\n"; + body << "Regards,\n"; + + string name; + if (!sObjectMgr.GetPlayerNameByGUID((ObjectGuid)(uint64)bidder, name)) + { + return; + } + + body << name << "\n"; + + ostringstream title; title << "AH Proposition: " << item->GetProto()->Name1; + MailDraft draft(title.str(), body.str()); + ObjectGuid receiverGuid(HIGHGUID_PLAYER, entry->owner); + draft.SendMailTo(MailReceiver(receiverGuid), MailSender(MAIL_NORMAL, bidder)); + + SetTime("entry", entry->Id, entry->auctionHouseEntry->houseId, AHBOT_SENDMAIL, entry->expireTime); +} + +INSTANTIATE_SINGLETON_1( ahbot::AhBot ); diff --git a/src/modules/Bots/ahbot/AhBot.h b/src/modules/Bots/ahbot/AhBot.h new file mode 100644 index 000000000..eb8845f10 --- /dev/null +++ b/src/modules/Bots/ahbot/AhBot.h @@ -0,0 +1,100 @@ +#ifndef AHBOT_H +#define AHBOT_H + +#include "Category.h" +#include "ItemBag.h" +#include "PlayerbotAIBase.h" +#include "AuctionHouseMgr.h" +#include "ObjectGuid.h" +#include "WorldSession.h" +#include "../botpch.h" + +#define MAX_AUCTIONS 3 +#define AHBOT_WON_EXPIRE 0 +#define AHBOT_WON_PLAYER 1 +#define AHBOT_WON_SELF 2 +#define AHBOT_WON_BID 3 +#define AHBOT_WON_DELAY 4 +#define AHBOT_SELL_DELAY 5 +#define AHBOT_SENDMAIL 6 + +namespace ahbot +{ + using namespace std; + + class AhBot + { + public: + AhBot() : nextAICheckTime(0), updating(false) {} + virtual ~AhBot(); + static AhBot& instance() + { + static AhBot instance; + return instance; + } + + public: + static bool HandleAhBotCommand(ChatHandler* handler, char const* args); + ObjectGuid GetAHBplayerGUID(); + void Init(); + void Update(); + void ForceUpdate(); + void HandleCommand(string command); + void Won(AuctionEntry* entry) { AddToHistory(entry); } + void Expired(AuctionEntry* entry) {} + + double GetCategoryMultiplier(string category) + { + return categoryMultipliers[category]; + } + + int32 GetSellPrice(const ItemPrototype* proto); + int32 GetBuyPrice(const ItemPrototype* proto); + double GetRarityPriceMultiplier(const ItemPrototype* proto); + bool IsUsedBySkill(const ItemPrototype* proto, uint32 skillId); + + private: + int Answer(int auction, Category* category, ItemBag* inAuctionItems); + int AddAuctions(int auction, Category* category, ItemBag* inAuctionItems); + int AddAuction(int auction, Category* category, const ItemPrototype* proto); + void Expire(int auction); + void PrintStats(int auction); + void AddToHistory(AuctionEntry* entry, uint32 won = 0); + void CleanupHistory(); + uint32 GetAvailableMoney(uint32 auctionHouse); + void CheckCategoryMultipliers(); + void updateMarketPrice(uint32 itemId, double price, uint32 auctionHouse); + bool IsBotAuction(uint32 bidder); + uint32 GetRandomBidder(uint32 auctionHouse); + void LoadRandomBots(); + uint32 GetAnswerCount(uint32 itemId, uint32 auctionHouse, uint32 withinTime); + vector LoadAuctions(const AuctionHouseObject::AuctionEntryMap& auctionEntryMap, Category*& category, + int& auction); + void FindMinPrice(const AuctionHouseObject::AuctionEntryMap& auctionEntryMap, AuctionEntry*& entry, Item*& item, uint32* minBid, + uint32* minBuyout); + uint32 GetBuyTime(uint32 entry, uint32 itemId, uint32 auctionHouse, Category*& category, double priceLevel); + uint32 GetTime(string category, uint32 id, uint32 auctionHouse, uint32 type); + void SetTime(string category, uint32 id, uint32 auctionHouse, uint32 type, uint32 value); + uint32 GetSellTime(uint32 itemId, uint32 auctionHouse, Category*& category); + void CheckSendMail(uint32 bidder, uint32 price, AuctionEntry *entry); + + public: + static uint32 auctionIds[MAX_AUCTIONS]; + static uint32 auctioneers[MAX_AUCTIONS]; + static map factions; + + private: + AvailableItemsBag availableItems; + time_t nextAICheckTime; + map categoryMultipliers; + map categoryMaxAuctionCount; + map categoryMultiplierExpireTimes; + map > bidders; + set allBidders; + bool updating; + }; +}; + +#define auctionbot MaNGOS::Singleton::Instance() + +#endif diff --git a/src/modules/Bots/ahbot/AhBotConfig.cpp b/src/modules/Bots/ahbot/AhBotConfig.cpp new file mode 100644 index 000000000..6123d1f3a --- /dev/null +++ b/src/modules/Bots/ahbot/AhBotConfig.cpp @@ -0,0 +1,62 @@ +#include "../botpch.h" +#include "AhBotConfig.h" +#include "SystemConfig.h" +std::vector split(const std::string &s, char delim); + +using namespace std; + +INSTANTIATE_SINGLETON_1(AhBotConfig); + +template +void LoadSet(const string& value, T &res) +{ + vector ids = split(value, ','); + for (vector::iterator i = ids.begin(); i != ids.end(); i++) + { + uint32 id = atoi((*i).c_str()); + if (!id) + { + continue; + } + + res.insert(id); + } +} + +bool AhBotConfig::Initialize() +{ + if (!config.SetSource(AUCTIONHOUSEBOT_CONFIG_NAME)) + { + sLog.outString("AhBot is Disabled. Unable to open configuration file ahbot.conf"); + return false; + } + + enabled = config.GetBoolDefault("AhBot.Enabled", true); + + if (!enabled) + { + sLog.outString("AhBot is Disabled in ahbot.conf"); + } + + guid = (uint64)config.GetIntDefault("AhBot.GUID", 0); + updateInterval = config.GetIntDefault("AhBot.UpdateIntervalInSeconds", 300); + historyDays = config.GetIntDefault("AhBot.History.Days", 30); + itemBuyMinInterval = config.GetIntDefault("AhBot.ItemBuyMinInterval", 600); + itemBuyMaxInterval = config.GetIntDefault("AhBot.ItemBuyMaxInterval", 7200); + itemSellMinInterval = config.GetIntDefault("AhBot.ItemSellMinInterval", 600); + itemSellMaxInterval = config.GetIntDefault("AhBot.ItemSellMaxInterval", 7200); + maxSellInterval = config.GetIntDefault("AhBot.MaxSellInterval", 3600 * 8); + alwaysAvailableMoney = config.GetIntDefault("AhBot.AlwaysAvailableMoney", 200000); + priceMultiplier = config.GetFloatDefault("AhBot.PriceMultiplier", 1.0f); + defaultMinPrice = config.GetIntDefault("AhBot.DefaultMinPrice", 20); + maxItemLevel = config.GetIntDefault("AhBot.MaxItemLevel", 199); + maxRequiredLevel = config.GetIntDefault("AhBot.MaxRequiredLevel", 80); + stackReducePrice = config.GetIntDefault("AhBot.StackReducePrice", 1000000); + priceQualityMultiplier = config.GetFloatDefault("AhBot.PriceQualityMultiplier", 1.0f); + underPriceProbability = config.GetFloatDefault("AhBot.UnderPriceProbability", 0.05f); + LoadSet >(config.GetStringDefault("AhBot.IgnoreItemIds", "49283,52200,8494,6345,6891,2460,37164,34835"), ignoreItemIds); + sendmail = config.GetBoolDefault("AhBot.SendMail", true); + + + return enabled; +} diff --git a/src/modules/Bots/ahbot/AhBotConfig.h b/src/modules/Bots/ahbot/AhBotConfig.h new file mode 100644 index 000000000..c620b3d72 --- /dev/null +++ b/src/modules/Bots/ahbot/AhBotConfig.h @@ -0,0 +1,96 @@ +#ifndef AHBOT_CONFIG_H +#define AHBOT_CONFIG_H + +#include "../../shared/Config/Config.h" + +using namespace std; + +class AhBotConfig +{ +public: + static AhBotConfig& instance() + { + static AhBotConfig instance; + return instance; + } + +public: + bool Initialize(); + + bool enabled; + uint64 guid; + uint32 updateInterval; + uint32 historyDays, maxSellInterval; + uint32 itemBuyMinInterval, itemBuyMaxInterval; + uint32 itemSellMinInterval, itemSellMaxInterval; + uint32 alwaysAvailableMoney; + float priceMultiplier, priceQualityMultiplier; + uint32 defaultMinPrice, stackReducePrice; + uint32 maxItemLevel, maxRequiredLevel; + float underPriceProbability; + std::set ignoreItemIds; + bool sendmail; + + float GetSellPriceMultiplier(string category) + { + return GetCategoryParameter(sellPriceMultipliers, "PriceMultiplier.Sell", category, 1.0f); + } + + float GetBuyPriceMultiplier(string category) + { + return GetCategoryParameter(buyPriceMultipliers, "PriceMultiplier.Buy", category, 1.0f); + } + + float GetItemPriceMultiplier(string name) + { + return GetCategoryParameter(itemPriceMultipliers, "PriceMultiplier.Item", name, 1.0f); + } + + int32 GetMaxAllowedAuctionCount(string category) + { + return (int32)GetCategoryParameter(maxAuctionCount, "MaxAuctionCount", category, 5); + } + + std::string GetStringDefault(const char* name, const char* def) + { + return config.GetStringDefault(name, def); + } + + bool GetBoolDefault(const char* name, const bool def = false) + { + return config.GetBoolDefault(name, def); + } + + int32 GetIntDefault(const char* name, const int32 def) + { + return config.GetIntDefault(name, def); + } + + float GetFloatDefault(const char* name, const float def) + { + return config.GetFloatDefault(name, def); + } + +private: + float GetCategoryParameter(map& cache, string type, string category, float defaultValue) + { + if (cache.find(category) == cache.end()) + { + ostringstream out; out << "AhBot."<< type << "." << category; + cache[category] = config.GetFloatDefault(out.str().c_str(), defaultValue); + } + + return cache[category]; + } + +private: + Config config; + map sellPriceMultipliers; + map buyPriceMultipliers; + map itemPriceMultipliers; + map maxAuctionCount; +}; + +#define sAhBotConfig MaNGOS::Singleton::Instance() + +#endif diff --git a/src/modules/Bots/ahbot/Category.cpp b/src/modules/Bots/ahbot/Category.cpp new file mode 100644 index 000000000..cf443e47b --- /dev/null +++ b/src/modules/Bots/ahbot/Category.cpp @@ -0,0 +1,296 @@ +#include "../botpch.h" +#include "Category.h" +#include "ItemBag.h" +#include "AhBotConfig.h" +#include "PricingStrategy.h" + +using namespace ahbot; + +uint32 Category::GetStackCount(ItemPrototype const* proto) +{ + if (proto->Quality > ITEM_QUALITY_UNCOMMON) + { + return 1; + } + + return urand(1, proto->GetMaxStackSize()); +} + +uint32 Category::GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) +{ + return 0; +} + +uint32 Category::GetMaxAllowedAuctionCount() +{ + return sAhBotConfig.GetMaxAllowedAuctionCount(GetName()); +} + +PricingStrategy* Category::GetPricingStrategy() +{ + if (pricingStrategy) + { + return pricingStrategy; + } + + ostringstream out; out << "AhBot.PricingStrategy." << GetName(); + string name = sAhBotConfig.GetStringDefault(out.str().c_str(), "default"); + return pricingStrategy = PricingStrategyFactory::Create(name, this); +} + +QualityCategoryWrapper::QualityCategoryWrapper(Category* category, uint32 quality) : Category(), quality(quality), category(category) +{ + ostringstream out; out << category->GetName() << "."; + switch (quality) + { + case ITEM_QUALITY_POOR: + out << "gray"; + break; + case ITEM_QUALITY_NORMAL: + out << "white"; + break; + case ITEM_QUALITY_UNCOMMON: + out << "green"; + break; + case ITEM_QUALITY_RARE: + out << "blue"; + break; + default: + out << "epic"; + break; + } + + combinedName = out.str(); +} + +bool QualityCategoryWrapper::Contains(ItemPrototype const* proto) +{ + return proto->Quality == quality && category->Contains(proto); +} + +uint32 QualityCategoryWrapper::GetMaxAllowedAuctionCount() +{ + uint32 count = sAhBotConfig.GetMaxAllowedAuctionCount(combinedName); + return count > 0 ? count : category->GetMaxAllowedAuctionCount(); +} + +uint32 QualityCategoryWrapper::GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) +{ + return category->GetMaxAllowedItemAuctionCount(proto); +} + +bool TradeSkill::Contains(ItemPrototype const* proto) +{ + if (!Trade::Contains(proto)) + { + return false; + } + + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + { + SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j); + if (!skillLine || skillLine->skillId != skill) + { + continue; + } + + if (IsCraftedBy(proto, skillLine->spellId)) + { + return true; + } + } + + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co || co->TrainerType != TRAINER_TYPE_TRADESKILLS) + { + continue; + } + + uint32 trainerId = co->TrainerTemplateId; + if (!trainerId) + { + trainerId = co->Entry; + } + + TrainerSpellData const* trainer_spells = sObjectMgr.GetNpcTrainerTemplateSpells(trainerId); + if (!trainer_spells) + { + trainer_spells = sObjectMgr.GetNpcTrainerSpells(trainerId); + } + + if (!trainer_spells) + { + continue; + } + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell || tSpell->reqSkill != skill) + { + continue; + } + + if (IsCraftedBy(proto, tSpell->spell)) + { + return true; + } + } + } + + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* recipe = sItemStorage.LookupEntry(itemId); + if (!recipe) + { + continue; + } + + if (recipe->Class == ITEM_CLASS_RECIPE && ( + (recipe->SubClass == ITEM_SUBCLASS_LEATHERWORKING_PATTERN && skill == SKILL_LEATHERWORKING) || + (recipe->SubClass == ITEM_SUBCLASS_TAILORING_PATTERN && skill == SKILL_TAILORING) || + (recipe->SubClass == ITEM_SUBCLASS_ENGINEERING_SCHEMATIC && skill == SKILL_ENGINEERING) || + (recipe->SubClass == ITEM_SUBCLASS_BLACKSMITHING && skill == SKILL_BLACKSMITHING) || + (recipe->SubClass == ITEM_SUBCLASS_COOKING_RECIPE && skill == SKILL_COOKING) || + (recipe->SubClass == ITEM_SUBCLASS_ALCHEMY_RECIPE && skill == SKILL_ALCHEMY) || + (recipe->SubClass == ITEM_SUBCLASS_FIRST_AID_MANUAL && skill == SKILL_FIRST_AID) || + (recipe->SubClass == ITEM_SUBCLASS_ENCHANTING_FORMULA && skill == SKILL_ENCHANTING) || + (recipe->SubClass == ITEM_SUBCLASS_FISHING_MANUAL && skill == SKILL_FISHING) + )) + { + for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (IsCraftedBy(proto, recipe->Spells[i].SpellId)) + { + return true; + } + } + } + } + + return false; +} + +bool TradeSkill::IsCraftedBySpell(ItemPrototype const* proto, uint32 spellId) +{ + SpellEntry const *entry = sSpellStore.LookupEntry(spellId); + if (!entry) + { + return false; + } + + for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) + { + if (entry->Reagent[x] <= 0) + { + continue; + } + + if (proto->ItemId == entry->Reagent[x]) + { + sLog.outDetail("%s is crafted by %s", proto->Name1, entry->SpellName[0]); + return true; + } + } + + return false; +} + +bool TradeSkill::IsCraftedBy(ItemPrototype const* proto, uint32 spellId) +{ + if (IsCraftedBySpell(proto, spellId)) + { + return true; + } + + SpellEntry const *entry = sSpellStore.LookupEntry(spellId); + if (!entry) + { + return false; + } + + for (uint32 effect = EFFECT_INDEX_0; effect < MAX_EFFECT_INDEX; ++effect) + { + uint32 craftId = entry->EffectTriggerSpell[effect]; + SpellEntry const *craft = sSpellStore.LookupEntry(craftId); + if (!craft) + { + continue; + } + + for (uint32 i = 0; i < MAX_SPELL_REAGENTS; ++i) + { + uint32 itemId = craft->Reagent[i]; + if (itemId == proto->ItemId) + { + sLog.outDetail("%s is crafted by %s", proto->Name1, craft->SpellName[0]); + return true; + } + } + } + + return false; +} + +string TradeSkill::GetName() +{ + switch (skill) + { + case SKILL_TAILORING: + return "tailoring"; + case SKILL_LEATHERWORKING: + return "leatherworking"; + case SKILL_ENGINEERING: + return "engineering"; + case SKILL_BLACKSMITHING: + return "blacksmithing"; + case SKILL_ALCHEMY: + return "alchemy"; + case SKILL_COOKING: + return "cooking"; + case SKILL_FISHING: + return "fishing"; + case SKILL_ENCHANTING: + return "enchanting"; + case SKILL_MINING: + return "mining"; + case SKILL_SKINNING: + return "skinning"; + case SKILL_HERBALISM: + return "herbalism"; + case SKILL_FIRST_AID: + return "firstaid"; + } +} + +string TradeSkill::GetLabel() +{ + switch (skill) + { + case SKILL_TAILORING: + return "tailoring materials"; + case SKILL_LEATHERWORKING: + case SKILL_SKINNING: + return "leather and hides"; + case SKILL_ENGINEERING: + return "engineering materials"; + case SKILL_BLACKSMITHING: + return "blacksmithing materials"; + case SKILL_ALCHEMY: + case SKILL_HERBALISM: + return "herbs"; + case SKILL_COOKING: + return "fish and meat"; + case SKILL_FISHING: + return "fish"; + case SKILL_ENCHANTING: + return "enchants"; + case SKILL_MINING: + return "ore and stone"; + case SKILL_FIRST_AID: + return "first aid reagents"; + } +} diff --git a/src/modules/Bots/ahbot/Category.h b/src/modules/Bots/ahbot/Category.h new file mode 100644 index 000000000..e93c10d08 --- /dev/null +++ b/src/modules/Bots/ahbot/Category.h @@ -0,0 +1,356 @@ +#ifndef AHBOT_CATEGORY_H +#define AHBOT_CATEGORY_H + +#include "Config.h" +#include "PricingStrategy.h" +#include "ItemPrototype.h" +#include "SharedDefines.h" +#include "Util.h" + +using namespace std; + +namespace ahbot +{ + class Category + { + public: + Category() : pricingStrategy(NULL) {} + virtual ~Category() { if (pricingStrategy) delete pricingStrategy; } + + public: + virtual bool Contains(ItemPrototype const* proto) { return false; } + virtual string GetName() { return "default"; } + virtual string GetDisplayName() { return GetName(); } + virtual string GetLabel() { return GetName(); } + + virtual uint32 GetMaxAllowedAuctionCount(); + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto); + virtual uint32 GetStackCount(ItemPrototype const* proto); + virtual uint32 GetSkillId() { return 0; } + + virtual PricingStrategy* GetPricingStrategy(); + + private: + PricingStrategy *pricingStrategy; + }; + + class Consumable : public Category + { + public: + Consumable() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_CONSUMABLE; + } + + virtual string GetName() { return "consumable"; } + virtual string GetLabel() { return "Consumables"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 10; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + if (proto->Quality > ITEM_QUALITY_UNCOMMON) + { + return 1; + } + + uint32 maxStackSize = proto->GetMaxStackSize(); + if (maxStackSize == 1) + { + return 1; + } + + if (maxStackSize <= 10) + { + return urand(1, 10); + } + + return urand(1, 4) * maxStackSize / 5; + } + }; + + class Quest : public Category + { + public: + Quest() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_QUEST; + } + virtual string GetName() { return "quest"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 5; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + if (proto->Quality > ITEM_QUALITY_UNCOMMON) + { + return 1; + } + + uint32 maxStackSize = proto->GetMaxStackSize(); + if (proto->Quality == ITEM_QUALITY_UNCOMMON && maxStackSize > 10) + { + maxStackSize = urand(1, 10); + } + + if (maxStackSize > 20) + { + maxStackSize = urand(1, 20); + } + + return maxStackSize; + } + }; + + class Trade : public Category + { + public: + Trade() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_TRADE_GOODS || + proto->Class == ITEM_CLASS_MISC || + proto->Class == ITEM_CLASS_REAGENT; + } + virtual string GetName() { return "trade"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 5; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + uint32 maxStack = proto->GetMaxStackSize(); + if (maxStack < 2) + { + return maxStack; + } + + switch (proto->Quality) + { + case ITEM_QUALITY_NORMAL: + return maxStack; + case ITEM_QUALITY_UNCOMMON: + return urand(1, maxStack); + } + + return 1; + } + }; + + class TradeSkill : public Trade + { + public: + TradeSkill(uint32 skill) : Trade(), skill(skill) {} + + public: + virtual bool Contains(ItemPrototype const* proto); + virtual string GetName(); + virtual string GetLabel(); + virtual uint32 GetSkillId() { return skill; } + + private: + bool IsCraftedBySpell(ItemPrototype const* proto, uint32 spellId); + bool IsCraftedBy(ItemPrototype const* proto, uint32 craftId); + uint32 skill; + }; + + class Reagent : public Category + { + public: + Reagent() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_REAGENT && proto->ItemLevel > 1; + } + virtual string GetName() { return "reagent"; } + virtual string GetLabel() { return "reagents"; } + }; + + class Recipe : public Category + { + public: + Recipe() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_RECIPE && proto->ItemLevel > 1; + } + virtual string GetName() { return "recipe"; } + virtual string GetLabel() { return "recipes and patterns"; } + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 1; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return 1; + } + }; + + class Equip : public Category + { + public: + Equip() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return (proto->Class == ITEM_CLASS_WEAPON || + proto->Class == ITEM_CLASS_ARMOR) && proto->ItemLevel > 1; + } + virtual string GetName() { return "equip"; } + virtual string GetLabel() { return "armor and weapons"; } + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 1; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return 1; + } + }; + + class Other : public Category + { + public: + Other() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Quality > ITEM_QUALITY_POOR && ( + proto->Class == ITEM_CLASS_MISC) && proto->ItemLevel > 1; + } + virtual string GetName() { return "other"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 1; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return 1; + } + }; + + class Quiver : public Category + { + public: + Quiver() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_QUIVER && proto->ItemLevel > 1; + } + + virtual string GetName() { return "quiver"; } + virtual string GetLabel() { return "quivers and ammo poaches"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 1; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return 1; + } + }; + + class Projectile : public Category + { + public: + Projectile() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_PROJECTILE; + } + + virtual string GetName() { return "projectile"; } + virtual string GetLabel() { return "projectiles"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 5; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return proto->GetMaxStackSize(); + } + }; + + class Container : public Category + { + public: + Container() : Category() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return proto->Class == ITEM_CLASS_CONTAINER && proto->ItemLevel > 1; + } + + virtual string GetName() { return "container"; } + virtual string GetLabel() { return "containers"; } + + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto) + { + return 1; + } + + virtual uint32 GetStackCount(ItemPrototype const* proto) + { + return 1; + } + }; + + class QualityCategoryWrapper : public Category + { + public: + QualityCategoryWrapper(Category* category, uint32 quality); + + public: + virtual bool Contains(ItemPrototype const* proto); + virtual uint32 GetMaxAllowedAuctionCount(); + virtual string GetName() { return category->GetName(); } + virtual string GetDisplayName() { return combinedName; } + virtual string GetLabel() { return category->GetLabel(); } + virtual uint32 GetMaxAllowedItemAuctionCount(ItemPrototype const* proto); + virtual uint32 GetStackCount(ItemPrototype const* proto) { return category->GetStackCount(proto); } + virtual PricingStrategy* GetPricingStrategy() { return category->GetPricingStrategy(); } + virtual uint32 GetSkillId() { return category->GetSkillId(); } + + private: + uint32 quality; + Category* category; + string combinedName; + }; +}; + +#endif diff --git a/src/modules/Bots/ahbot/ConsumableCategory.cpp b/src/modules/Bots/ahbot/ConsumableCategory.cpp new file mode 100644 index 000000000..7061dbd18 --- /dev/null +++ b/src/modules/Bots/ahbot/ConsumableCategory.cpp @@ -0,0 +1,5 @@ +#include "../botpch.h" +#include "ConsumableCategory.h" +#include "ItemBag.h" + +using namespace ahbot; diff --git a/src/modules/Bots/ahbot/ConsumableCategory.h b/src/modules/Bots/ahbot/ConsumableCategory.h new file mode 100644 index 000000000..42345478f --- /dev/null +++ b/src/modules/Bots/ahbot/ConsumableCategory.h @@ -0,0 +1,91 @@ +#ifndef AHBOT_CONSUMABLE_CATEGORY_H +#define AHBOT_CONSUMABLE_CATEGORY_H + +#include "Config/Config.h" +#include "Category.h" + +using namespace std; + +namespace ahbot +{ + class Alchemy : public Consumable + { + public: + Alchemy() : Consumable() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Consumable::Contains(proto) && + (proto->SubClass == ITEM_SUBCLASS_POTION || + proto->SubClass == ITEM_SUBCLASS_ELIXIR || + proto->SubClass == ITEM_SUBCLASS_FLASK); + } + + virtual string GetName() { return "Alchemy"; } + }; + + class Scroll : public Consumable + { + public: + Scroll() : Consumable() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Consumable::Contains(proto) && + (proto->SubClass == ITEM_SUBCLASS_SCROLL || + proto->SubClass == ITEM_SUBCLASS_ITEM_ENHANCEMENT); + } + + virtual string GetName() { return "Scroll"; } + }; + + class Food : public Consumable + { + public: + Food() : Consumable() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Consumable::Contains(proto) && + proto->SubClass == ITEM_SUBCLASS_FOOD; + } + + virtual string GetName() { return "Food"; } + }; + + class Bandage : public Consumable + { + public: + Bandage() : Consumable() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Consumable::Contains(proto) && + proto->SubClass == ITEM_SUBCLASS_BANDAGE; + } + + virtual string GetName() { return "Bandage"; } + }; + + class OtherConsumable : public Consumable + { + public: + OtherConsumable() : Consumable() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Consumable::Contains(proto) && + (proto->SubClass == ITEM_SUBCLASS_CONSUMABLE || + proto->SubClass == ITEM_SUBCLASS_CONSUMABLE_OTHER) && proto->BuyCount < 5; + } + + virtual string GetName() { return "OtherConsumable"; } + }; +}; + +#endif diff --git a/src/modules/Bots/ahbot/ItemBag.cpp b/src/modules/Bots/ahbot/ItemBag.cpp new file mode 100644 index 000000000..0d575a1d9 --- /dev/null +++ b/src/modules/Bots/ahbot/ItemBag.cpp @@ -0,0 +1,216 @@ +#include "../botpch.h" +#include "Category.h" +#include "ItemBag.h" +#include "ConsumableCategory.h" +#include "TradeCategory.h" +#include "AhBotConfig.h" +#include "DBCStructure.h" +#include "Log.h" +#include "QueryResult.h" +#include "DatabaseEnv.h" +#include "SQLStorage.h" +#include "DBCStore.h" +#include "SQLStorages.h" +#include "AuctionHouseMgr.h" +#include "ObjectMgr.h" + +using namespace ahbot; +char * strstri (const char* str1, const char* str2); + +CategoryList CategoryList::instance; + +CategoryList::CategoryList() +{ + Add(new Equip()); + Add(new ahbot::Quest()); + Add(new Quiver()); + Add(new Projectile()); + + Add(new Recipe()); + Add(new Container()); + + Add(new TradeSkill(SKILL_TAILORING)); + Add(new TradeSkill(SKILL_LEATHERWORKING)); + Add(new TradeSkill(SKILL_ENGINEERING)); + Add(new TradeSkill(SKILL_BLACKSMITHING)); + Add(new TradeSkill(SKILL_ALCHEMY)); + Add(new TradeSkill(SKILL_ENCHANTING)); + Add(new TradeSkill(SKILL_FISHING)); + Add(new TradeSkill(SKILL_FIRST_AID)); + Add(new TradeSkill(SKILL_COOKING)); + Add(new TradeSkill(SKILL_MINING)); + Add(new TradeSkill(SKILL_HERBALISM)); + Add(new TradeSkill(SKILL_SKINNING)); + Add(new Reagent()); + Add(new Alchemy()); + Add(new Scroll()); + Add(new Food()); + Add(new Bandage()); + + Add(new Engineering()); + + Add(new OtherConsumable()); + Add(new OtherTrade()); + Add(new Other()); +} + +void CategoryList::Add(Category* category) +{ + for (uint32 quality = ITEM_QUALITY_NORMAL; quality <= ITEM_QUALITY_EPIC; ++quality) + { + categories.push_back(new QualityCategoryWrapper(category, quality)); + } +} + +CategoryList::~CategoryList() +{ + for (vector::const_iterator i = categories.begin(); i != categories.end(); ++i) + { + delete *i; + } +} + +ItemBag::ItemBag() +{ + for (int i = 0; i < CategoryList::instance.size(); i++) + { + content[CategoryList::instance[i]] = vector(); + } +} + +void ItemBag::Init(bool silent) +{ + if (silent) + { + Load(); + return; + } + + sLog.outString("Loading/Scanning %s...", GetName().c_str()); + + Load(); + + for (int i = 0; i < CategoryList::instance.size(); i++) + { + Category* category = CategoryList::instance[i]; + Shuffle(content[category]); + sLog.outString("loaded %d %s items", content[category].size(), category->GetDisplayName().c_str()); + } +} + +int32 ItemBag::GetCount(Category* category, uint32 item) +{ + uint32 count = 0; + + vector& items = content[category]; + for (vector::iterator i = items.begin(); i != items.end(); ++i) + { + if (*i == item) + { + count++; + } + } + + return count; +} + +bool ItemBag::Add(ItemPrototype const* proto) +{ + if (!proto || + proto->Bonding == BIND_WHEN_PICKED_UP || + proto->Bonding == BIND_QUEST_ITEM) + return false; + + if (proto->RequiredLevel > sAhBotConfig.maxRequiredLevel || proto->ItemLevel > sAhBotConfig.maxItemLevel) + { + return false; + } + + if (proto->Duration & 0x80000000) + { + return false; + } + + if (sAhBotConfig.ignoreItemIds.find(proto->ItemId) != sAhBotConfig.ignoreItemIds.end()) + { + return false; + } + + if (strstri(proto->Name1, "qa") || strstri(proto->Name1, "test") || strstri(proto->Name1, "deprecated")) + { + return false; + } + + bool contains = false; + for (int i = 0; i < CategoryList::instance.size(); i++) + { + if (CategoryList::instance[i]->Contains(proto)) + { + content[CategoryList::instance[i]].push_back(proto->ItemId); + contains = true; + } + } + + if (!contains) + { + sLog.outDetail("Item %s does not included in any category", proto->Name1); + } + + return contains; +} + +void AvailableItemsBag::Load() +{ + set vendorItems; + + QueryResult* results = WorldDatabase.PQuery("SELECT item FROM npc_vendor where maxcount = 0"); + if (results != NULL) + { + do + { + Field* fields = results->Fetch(); + vendorItems.insert(fields[0].GetUInt32()); + } while (results->NextRow()); + + delete results; + } + + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + if (vendorItems.find(itemId) != vendorItems.end()) + { + continue; + } + + Add(sObjectMgr.GetItemPrototype(itemId)); + } + +} + +void InAuctionItemsBag::Load() +{ + AuctionHouseEntry const* ahEntry = sAuctionHouseStore.LookupEntry(auctionId); + if(!ahEntry) + { + return; + } + + AuctionHouseObject* auctionHouse = sAuctionMgr.GetAuctionsMap(ahEntry); + AuctionHouseObject::AuctionEntryMap const& auctionEntryMap = auctionHouse->GetAuctions(); + for (AuctionHouseObject::AuctionEntryMap::const_iterator itr = auctionEntryMap.begin(); itr != auctionEntryMap.end(); ++itr) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itr->second->itemTemplate); + if (!proto) + { + continue; + } + + Add(proto); + } +} + +string InAuctionItemsBag::GetName() +{ + ostringstream out; out << "auction house " << auctionId; + return out.str(); +} diff --git a/src/modules/Bots/ahbot/ItemBag.h b/src/modules/Bots/ahbot/ItemBag.h new file mode 100644 index 000000000..ba1b84820 --- /dev/null +++ b/src/modules/Bots/ahbot/ItemBag.h @@ -0,0 +1,86 @@ +#ifndef AHBOT_ITEM_BAG_H +#define AHBOT_ITEM_BAG_H + +#include "Category.h" + +namespace ahbot +{ + using namespace std; + + class CategoryList + { + public: + CategoryList(); + virtual ~CategoryList(); + + Category* operator[](int index) { return categories[index]; } + int32 size() { return categories.size(); } + static CategoryList instance; + + private: + void Add(Category* category); + + private: + vector categories; + }; + + template + void Shuffle(vector& items) + { + uint32 count = items.size(); + for (uint32 i = 0; i < count * 5; i++) + { + int i1 = urand(0, count - 1); + int i2 = urand(0, count - 1); + + T item = items[i1]; + items[i1] = items[i2]; + items[i2] = item; + } + } + + class ItemBag + { + public: + ItemBag(); + + public: + void Init(bool silent = false); + vector& Get(Category* category) { return content[category]; } + int32 GetCount(Category* category) { return content[category].size(); } + int32 GetCount(Category* category, uint32 item); + bool Add(ItemPrototype const* proto); + + protected: + virtual void Load() = 0; + virtual string GetName() = 0; + + protected: + map > content; + }; + + class AvailableItemsBag : public ItemBag + { + public: + AvailableItemsBag() {} + + protected: + virtual void Load(); + virtual string GetName() { return "available"; } + }; + + class InAuctionItemsBag : public ItemBag + { + public: + InAuctionItemsBag(uint32 auctionId) : auctionId(auctionId) {} + + protected: + virtual void Load(); + virtual string GetName(); + + private: + uint32 auctionId; + }; +}; + +#endif diff --git a/src/modules/Bots/ahbot/PricingStrategy.cpp b/src/modules/Bots/ahbot/PricingStrategy.cpp new file mode 100644 index 000000000..634919a38 --- /dev/null +++ b/src/modules/Bots/ahbot/PricingStrategy.cpp @@ -0,0 +1,274 @@ +#include "PricingStrategy.h" +#include "Category.h" +#include "ItemBag.h" +#include "AhBotConfig.h" +#include "../../shared/Database/DatabaseEnv.h" +#include "AhBot.h" + +using namespace ahbot; + +uint32 PricingStrategy::GetSellPrice(ItemPrototype const* proto, uint32 auctionHouse, bool ignoreMarket) +{ + double marketPrice = GetMarketPrice(proto->ItemId, auctionHouse); + + if (!ignoreMarket && marketPrice > 0) + { + return marketPrice; + } + + uint32 now = time(0); + double price = sAhBotConfig.GetItemPriceMultiplier(proto->Name1) * + auctionbot.GetCategoryMultiplier(category->GetName()) * + GetRarityPriceMultiplier(proto->ItemId) * + GetCategoryPriceMultiplier(now, auctionHouse) * + GetItemPriceMultiplier(proto, now, auctionHouse) * + sAhBotConfig.GetSellPriceMultiplier(category->GetName()) * + GetQualityMultiplier(proto) * + sAhBotConfig.priceMultiplier * + GetDefaultSellPrice(proto); + return RoundPrice(price); +} + +double PricingStrategy::GetMarketPrice(uint32 itemId, uint32 auctionHouse) +{ + double marketPrice = 0; + + QueryResult* results = CharacterDatabase.PQuery("SELECT price FROM ahbot_price WHERE item = '%u' AND auction_house = '%u'", itemId, auctionHouse); + if (results) + { + marketPrice = results->Fetch()[0].GetFloat(); + delete results; + } + + return RoundPrice(marketPrice); +} + +uint32 PricingStrategy::GetBuyPrice(ItemPrototype const* proto, uint32 auctionHouse) +{ + uint32 untilTime = time(0) - 3600 * 12; + double price = sAhBotConfig.GetItemPriceMultiplier(proto->Name1) * + auctionbot.GetCategoryMultiplier(category->GetName()) * + GetRarityPriceMultiplier(proto->ItemId) * + GetCategoryPriceMultiplier(untilTime, auctionHouse) * + GetItemPriceMultiplier(proto, untilTime, auctionHouse) * + sAhBotConfig.GetBuyPriceMultiplier(category->GetName()) * + GetQualityMultiplier(proto) * + sAhBotConfig.priceMultiplier * + GetDefaultBuyPrice(proto); + return RoundPrice(price); +} + +string PricingStrategy::ExplainSellPrice(ItemPrototype const* proto, uint32 auctionHouse) +{ + ostringstream out; + + uint32 untilTime = time(0); + out << sAhBotConfig.GetItemPriceMultiplier(proto->Name1) << " (item const) * " << + auctionbot.GetCategoryMultiplier(category->GetName()) << " (random) * " << + GetRarityPriceMultiplier(proto->ItemId) << " (rarity) * " << + GetCategoryPriceMultiplier(untilTime, auctionHouse) << " (category) * " << + GetItemPriceMultiplier(proto, untilTime, auctionHouse) << " (item) * " << + sAhBotConfig.GetSellPriceMultiplier(category->GetName()) << " (sell) * " << + GetQualityMultiplier(proto) << " (quality) * " << + sAhBotConfig.priceMultiplier << " (config) * " << + GetDefaultSellPrice(proto) << " (price)"; + return out.str(); +} + +string PricingStrategy::ExplainBuyPrice(ItemPrototype const* proto, uint32 auctionHouse) +{ + ostringstream out; + + uint32 untilTime = time(0) - 3600 * 12; + out << sAhBotConfig.GetItemPriceMultiplier(proto->Name1) << " (item const) * " << + auctionbot.GetCategoryMultiplier(category->GetName()) << " (random) * " << + GetRarityPriceMultiplier(proto->ItemId) << " (rarity) * " << + GetCategoryPriceMultiplier(untilTime, auctionHouse) << " (category) * " << + GetItemPriceMultiplier(proto, untilTime, auctionHouse) << " (item) * " << + sAhBotConfig.GetBuyPriceMultiplier(category->GetName()) << " (buy) * " << + GetQualityMultiplier(proto) << " (quality) * " << + sAhBotConfig.priceMultiplier << " (config) * " << + GetDefaultBuyPrice(proto) << " (price)"; + return out.str(); +} + +double PricingStrategy::GetRarityPriceMultiplier(uint32 itemId) +{ + double result = 1.0; + + QueryResult* results = WorldDatabase.PQuery( + "select max(ChanceOrQuestChance) from ( " + "select ChanceOrQuestChance from gameobject_loot_template where item = '%u' " + //"union select ChanceOrQuestChance from spell_loot_template where item = '%u' " + "union select ChanceOrQuestChance from disenchant_loot_template where item = '%u' " + "union select ChanceOrQuestChance from fishing_loot_template where item = '%u' " + "union select ChanceOrQuestChance from item_loot_template where item = '%u' " + //"union select ChanceOrQuestChance from milling_loot_template where item = '%u' " + "union select ChanceOrQuestChance from pickpocketing_loot_template where item = '%u' " + //"union select ChanceOrQuestChance from prospecting_loot_template where item = '%u' " + "union select ChanceOrQuestChance from reference_loot_template where item = '%u' " + "union select ChanceOrQuestChance from skinning_loot_template where item = '%u' " + "union select ChanceOrQuestChance from creature_loot_template where item = '%u' " + "union select 0 " + ") a", + itemId,itemId,itemId,itemId,itemId,itemId,itemId,itemId,itemId,itemId,itemId); + + if (results) + { + Field* fields = results->Fetch(); + float chance = fields[0].GetFloat(); + + if (chance > 0 && chance <= 90.0) + { + result = sqrt((100.0 - chance) / 10.0); + } + + delete results; + } + + return result >= 1.0 ? result : 1.0; +} + + +double PricingStrategy::GetCategoryPriceMultiplier(uint32 untilTime, uint32 auctionHouse) +{ + double result = 1.0; + + QueryResult* results = CharacterDatabase.PQuery( + "SELECT count(*) FROM (SELECT round(buytime/3600/24/5) as days FROM ahbot_history WHERE category = '%s' AND won = '1' AND buytime <= '%u' AND auction_house = '%u' group by days) q", + category->GetName().c_str(), untilTime, AhBot::factions[auctionHouse]); + if (results) + { + Field* fields = results->Fetch(); + uint32 count = fields[0].GetUInt32(); + + if (count) + { + result += count; + } + + delete results; + } + + return result; +} + +double PricingStrategy::GetMultiplier(double count, double firstBuyTime, double lastBuyTime) +{ + double k1 = (double)count / (double)((time(0) - firstBuyTime) / 3600 / 24 + 1); + double k2 = (double)count / (double)((time(0) - lastBuyTime) / 3600 / 24 + 1); + return max(1.0, k1 + k2) * sAhBotConfig.priceMultiplier; +} + +double PricingStrategy::GetItemPriceMultiplier(ItemPrototype const* proto, uint32 untilTime, uint32 auctionHouse) +{ + double result = 1.0; + + QueryResult* results = CharacterDatabase.PQuery( + "SELECT count(*) FROM (SELECT round(buytime/3600/24/5) as days FROM ahbot_history WHERE won = '1' AND item = '%u' AND buytime <= '%u' AND auction_house = '%u' group by days) q", + proto->ItemId, untilTime, AhBot::factions[auctionHouse]); + if (results) + { + Field* fields = results->Fetch(); + uint32 count = fields[0].GetUInt32(); + + if (count) + { + result += count; + } + + delete results; + } + + return result; +} + +double PricingStrategy::GetQualityMultiplier(ItemPrototype const* proto) +{ + if (proto->Quality == ITEM_QUALITY_POOR) + { + return 1.0; + } + + return sqrt((double)proto->Quality) * sAhBotConfig.priceQualityMultiplier; +} + +uint32 PricingStrategy::GetDefaultBuyPrice(ItemPrototype const* proto) +{ + uint32 price = 0; + + if (proto->SellPrice) + { + price = proto->SellPrice; + } + if (proto->BuyPrice) + { + price = max(price, proto->BuyPrice / 4); + } + + price *= 2; + + uint32 level = max(proto->ItemLevel, proto->RequiredLevel); + if (proto->Class == ITEM_CLASS_QUEST) + { + QueryResult* results = WorldDatabase.PQuery( + "select max(QuestLevel), max(MinLevel) from quest_template where ReqItemId1 = %u or ReqItemId2 = %u or ReqItemId3 = %u or ReqItemId4 = %u", + proto->ItemId, proto->ItemId, proto->ItemId, proto->ItemId); + if (results) + { + Field* fields = results->Fetch(); + level = max(fields[0].GetUInt32(), fields[1].GetUInt32()); + delete results; + } + } + if (!price) price = sAhBotConfig.defaultMinPrice * level * level / 40; + { + price = max(price, (uint32)100); + } + + return price; +} + +uint32 PricingStrategy::GetDefaultSellPrice(ItemPrototype const* proto) +{ + return GetDefaultBuyPrice(proto) * 4 / 3; +} + + +uint32 BuyOnlyRarePricingStrategy::GetBuyPrice(ItemPrototype const* proto, uint32 auctionHouse) +{ + if (proto->Quality < ITEM_QUALITY_RARE) + { + return 0; + } + + return PricingStrategy::GetBuyPrice(proto, auctionHouse); +} + +uint32 BuyOnlyRarePricingStrategy::GetSellPrice(ItemPrototype const* proto, uint32 auctionHouse) +{ + return PricingStrategy::GetSellPrice(proto, auctionHouse); +} + +uint32 PricingStrategy::RoundPrice(double price) +{ + if (price < 100) { + { + return (uint32) price; + } + } + + if (price < 10000) { + { + return (uint32) (price / 100.0) * 100; + } + } + + if (price < 100000) { + { + return (uint32) (price / 1000.0) * 1000; + } + } + + return (uint32) (price / 10000.0) * 10000; +} diff --git a/src/modules/Bots/ahbot/PricingStrategy.h b/src/modules/Bots/ahbot/PricingStrategy.h new file mode 100644 index 000000000..b978350dd --- /dev/null +++ b/src/modules/Bots/ahbot/PricingStrategy.h @@ -0,0 +1,64 @@ +#ifndef AHBOT_PRICING_STRATEGY_H +#define AHBOT_PRICING_STRATEGY_H + +#include "Config.h" +#include "ItemPrototype.h" + +using namespace std; + +namespace ahbot +{ + class Category; + + class PricingStrategy + { + public: + PricingStrategy(Category* category) : category(category) {} + + public: + virtual uint32 GetSellPrice(ItemPrototype const* proto, uint32 auctionHouse, bool ignoreMarket = false); + virtual uint32 GetBuyPrice(ItemPrototype const* proto, uint32 auctionHouse); + double GetMarketPrice(uint32 itemId, uint32 auctionHouse); + string ExplainSellPrice(ItemPrototype const* proto, uint32 auctionHouse); + string ExplainBuyPrice(ItemPrototype const* proto, uint32 auctionHouse); + virtual double GetRarityPriceMultiplier(uint32 itemId); + static uint32 RoundPrice(double price); + + protected: + virtual uint32 GetDefaultBuyPrice(ItemPrototype const* proto); + virtual uint32 GetDefaultSellPrice(ItemPrototype const* proto); + virtual double GetQualityMultiplier(ItemPrototype const* proto); + virtual double GetCategoryPriceMultiplier(uint32 untilTime, uint32 auctionHouse); + virtual double GetItemPriceMultiplier(ItemPrototype const* proto, uint32 untilTime, uint32 auctionHouse); + double GetMultiplier(double count, double firstBuyTime, double lastBuyTime); + + protected: + Category* category; + }; + + class BuyOnlyRarePricingStrategy : public PricingStrategy + { + public: + BuyOnlyRarePricingStrategy(Category* category) : PricingStrategy(category) {} + + public: + virtual uint32 GetBuyPrice(ItemPrototype const* proto, uint32 auctionHouse); + virtual uint32 GetSellPrice(ItemPrototype const* proto, uint32 auctionHouse); + }; + + class PricingStrategyFactory + { + public: + static PricingStrategy* Create(string name, Category* category) + { + if (name == "buyOnlyRare") + { + return new BuyOnlyRarePricingStrategy(category); + } + + return new PricingStrategy(category); + } + }; +}; + +#endif diff --git a/src/modules/Bots/ahbot/TradeCategory.cpp b/src/modules/Bots/ahbot/TradeCategory.cpp new file mode 100644 index 000000000..15fafa72c --- /dev/null +++ b/src/modules/Bots/ahbot/TradeCategory.cpp @@ -0,0 +1,4 @@ +#include "TradeCategory.h" +//#include "ItemBag.h" + +using namespace ahbot; diff --git a/src/modules/Bots/ahbot/TradeCategory.h b/src/modules/Bots/ahbot/TradeCategory.h new file mode 100644 index 000000000..6766a6cba --- /dev/null +++ b/src/modules/Bots/ahbot/TradeCategory.h @@ -0,0 +1,46 @@ +#ifndef AHBOT_TRADE_CATEGORY_H +#define AHBOT_TRADE_CATEGORY_H + +#include "Category.h" + +using namespace std; + +namespace ahbot +{ + class Engineering : public Trade + { + public: + Engineering() : Trade() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Trade::Contains(proto) && + (proto->SubClass == ITEM_SUBCLASS_PARTS || + proto->SubClass == ITEM_SUBCLASS_DEVICES || + proto->SubClass == ITEM_SUBCLASS_EXPLOSIVES); + } + + virtual string GetName() { return "Engineering"; } + }; + + class OtherTrade : public Trade + { + public: + OtherTrade() : Trade() {} + + public: + virtual bool Contains(ItemPrototype const* proto) + { + return Trade::Contains(proto) && + proto->SubClass != ITEM_SUBCLASS_PARTS && + proto->SubClass != ITEM_SUBCLASS_DEVICES && + proto->SubClass != ITEM_SUBCLASS_EXPLOSIVES; + } + + virtual string GetName() { return "othertrade"; } + virtual string GetLabel() { return "devices and explosives"; } + }; +}; + +#endif diff --git a/src/modules/Bots/ahbot/ahbot.conf.dist.in b/src/modules/Bots/ahbot/ahbot.conf.dist.in new file mode 100644 index 000000000..738ef48d8 --- /dev/null +++ b/src/modules/Bots/ahbot/ahbot.conf.dist.in @@ -0,0 +1,212 @@ +################################################ +# MANGOS Auction House Bot Configuration file # +################################################ + +[AhbotConf] +ConfVersion=@MANGOS_WORLD_VER@ + +################################################################################################################### +# AUCTION HOUSE BOT SETTINGS +# +################################################################################################################### + +# Disable original AuctionHouseBot +#AuctionHouseBot.Seller.Enabled = 0 +#AuctionHouseBot.Buyer.Enabled = 0 + +# Replace with the new AhBot +#AhBot.Enabled = 1 + +# Should be used only if random bots are disabled +# AhBot.GUID = 0 + +# 199 for 80, 80 for 70, 70 for 60, ..., 25 for 20 +#AhBot.MaxItemLevel = 199 +# Same as level cap +#AhBot.MaxRequiredLevel = 80 + +# Ignore items by ID +#AhBot.IgnoreItemIds = 49283,52200,8494,6345,6891,2460 + +# Notify about overpriced auctions +#AhBot.SendMail = 1 + +#AhBot.PriceMultiplier = 1.0 +#AhBot.DefaultMinPrice = 20 +#AhBot.PriceQualityMultiplier = 1.0 +#AhBot.AlwaysAvailableMoney = 2000000 + +# Buy/sell delays +#AhBot.ItemBuyMinInterval = 7200 +#AhBot.ItemBuyMaxInterval = 28800 +#AhBot.ItemSellMinInterval = 7200 +#AhBot.ItemSellMaxInterval = 28800 + +# +# Items +# + +#AhBot.MaxAuctionCount.equip.green = 40 +#AhBot.MaxAuctionCount.equip.blue = 20 +#AhBot.MaxAuctionCount.equip.epic = 10 +#AhBot.PriceMultiplier.Sell.equip = 1.0 +#AhBot.PriceMultiplier.Buy.equip = 1.0 +#AhBot.PricingStrategy.equip = buyOnlyRare + +#AhBot.MaxAuctionCount.reagent.white = 6 +#AhBot.MaxAuctionCount.reagent.green = 2 +#AhBot.PriceMultiplier.Sell.reagent = 1.0 +#AhBot.PriceMultiplier.Buy.reagent = 1.0 +#AhBot.PricingStrategy.reagent = buyOnlyRare + +#AhBot.MaxAuctionCount.other.white = 20 +#AhBot.MaxAuctionCount.other.green = 10 +#AhBot.MaxAuctionCount.other.blue = 6 +#AhBot.PriceMultiplier.Sell.other = 1.0 +#AhBot.PriceMultiplier.Buy.other = 1.0 +#AhBot.PricingStrategy.other = buyOnlyRare + +# +# Container +# + +#AhBot.MaxAuctionCount.quiver.white = 2 +#AhBot.PriceMultiplier.Sell.quiver = 1.0 +#AhBot.PriceMultiplier.Buy.quiver = 1.0 +#AhBot.PricingStrategy.quiver = buyOnlyRare + +#AhBot.MaxAuctionCount.container.white = 6 +#AhBot.MaxAuctionCount.container.green = 2 +#AhBot.MaxAuctionCount.container.blue = 2 +#AhBot.PriceMultiplier.Sell.container = 1.0 +#AhBot.PriceMultiplier.Buy.container = 1.0 + +# +# Glyph +# + +#AhBot.MaxAuctionCount.glyph.white = 50 +#AhBot.MaxAuctionCount.glyph.green = 20 +#AhBot.MaxAuctionCount.glyph.blue = 10 +#AhBot.PriceMultiplier.Sell.glyph = 1.0 +#AhBot.PriceMultiplier.Buy.glyph = 1.0 + +# +# Quest +# + +#AhBot.MaxAuctionCount.quest.white = 14 +#AhBot.MaxAuctionCount.quest.green = 2 +#AhBot.MaxAuctionCount.quest.blue = 2 +#AhBot.PriceMultiplier.Sell.quest = 1.0 +#AhBot.PriceMultiplier.Buy.quest = 1.0 + +# +# Consumables +# + +#AhBot.MaxAuctionCount.alchemy.white = 50 +#AhBot.MaxAuctionCount.alchemy.green = 20 +#AhBot.MaxAuctionCount.alchemy.blue = 10 +#AhBot.PriceMultiplier.Sell.alchemy = 1.0 +#AhBot.PriceMultiplier.Buy.alchemy = 1.0 + +#AhBot.MaxAuctionCount.scroll.white = 40 +#AhBot.MaxAuctionCount.scroll.green = 6 +#AhBot.MaxAuctionCount.scroll.blue = 4 +#AhBot.PriceMultiplier.Sell.scroll = 1.0 +#AhBot.PriceMultiplier.Buy.scroll = 1.0 + +#AhBot.MaxAuctionCount.Food.white = 40 +#AhBot.PriceMultiplier.Sell.Food = 1.0 +#AhBot.PriceMultiplier.Buy.Food = 1.0 + +#AhBot.MaxAuctionCount.bandage.white = 10 +#AhBot.PriceMultiplier.Sell.bandage = 1.0 +#AhBot.PriceMultiplier.Buy.bandage = 1.0 + +#AhBot.MaxAuctionCount.OtherConsumable.white = 20 +#AhBot.MaxAuctionCount.OtherConsumable.green = 10 +#AhBot.MaxAuctionCount.OtherConsumable.blue = 5 +#AhBot.PriceMultiplier.Sell.OtherConsumable = 1.0 +#AhBot.PriceMultiplier.Buy.OtherConsumable = 1.0 + +# +# Recipe +# + +#AhBot.MaxAuctionCount.recipe.white = 8 +#AhBot.MaxAuctionCount.recipe.green = 4 +#AhBot.MaxAuctionCount.recipe.blue = 2 +#AhBot.MaxAuctionCount.recipe.epic = 2 +#AhBot.PriceMultiplier.Sell.recipe = 1.0 +#AhBot.PriceMultiplier.Buy.recipe = 1.0 + +# +# Trade +# + +#AhBot.MaxAuctionCount.Elemental.white = 30 +#AhBot.MaxAuctionCount.Elemental.green = 20 +#AhBot.MaxAuctionCount.Elemental.blue = 6 +#AhBot.PriceMultiplier.Sell.Elemental = 1.0 +#AhBot.PriceMultiplier.Buy.Elemental = 1.0 + +#AhBot.MaxAuctionCount.Cloth.white = 50 +#AhBot.MaxAuctionCount.Cloth.green = 14 +#AhBot.MaxAuctionCount.Cloth.blue = 6 +#AhBot.PriceMultiplier.Sell.Cloth = 1.0 +#AhBot.PriceMultiplier.Buy.Cloth = 1.0 + +#AhBot.MaxAuctionCount.Leather.white = 50 +#AhBot.MaxAuctionCount.Leather.green = 14 +#AhBot.MaxAuctionCount.Leather.blue = 6 +#AhBot.PriceMultiplier.Sell.Leather = 1.0 +#AhBot.PriceMultiplier.Buy.Leather = 1.0 + +#AhBot.MaxAuctionCount.Herb.white = 50 +#AhBot.MaxAuctionCount.Herb.green = 14 +#AhBot.PriceMultiplier.Sell.Herb = 1.0 +#AhBot.PriceMultiplier.Buy.Herb = 1.0 + +#AhBot.MaxAuctionCount.Meat.white = 50 +#AhBot.PriceMultiplier.Sell.Meat = 1.0 +#AhBot.PriceMultiplier.Buy.Meat = 1.0 + +#AhBot.MaxAuctionCount.Metal.white = 50 +#AhBot.MaxAuctionCount.Metal.green = 14 +#AhBot.MaxAuctionCount.Metal.blue = 6 +#AhBot.PriceMultiplier.Sell.Metal = 1.0 +#AhBot.PriceMultiplier.Buy.Metal = 1.0 + +#AhBot.MaxAuctionCount.Engineering.white = 20 +#AhBot.MaxAuctionCount.Engineering.green = 10 +#AhBot.MaxAuctionCount.Engineering.blue = 4 +#AhBot.PriceMultiplier.Sell.Engineering = 1.0 +#AhBot.PriceMultiplier.Buy.Engineering = 1.0 + +#AhBot.MaxAuctionCount.Disenchants.white = 40 +#AhBot.MaxAuctionCount.Disenchants.green = 20 +#AhBot.MaxAuctionCount.Disenchants.blue = 20 +#AhBot.PriceMultiplier.Sell.Disenchants = 1.0 +#AhBot.PriceMultiplier.Buy.Disenchants = 1.0 + +#AhBot.MaxAuctionCount.SimpleGems.green = 30 +#AhBot.MaxAuctionCount.SimpleGems.blue = 20 +#AhBot.MaxAuctionCount.SimpleGems.epic = 4 +#AhBot.PriceMultiplier.Sell.SimpleGems = 1.0 +#AhBot.PriceMultiplier.Buy.SimpleGems = 1.0 + +#AhBot.MaxAuctionCount.SocketGems.green = 50 +#AhBot.MaxAuctionCount.SocketGems.blue = 20 +#AhBot.MaxAuctionCount.SocketGems.epic = 4 +#AhBot.PriceMultiplier.Sell.SocketGems = 1.0 +#AhBot.PriceMultiplier.Buy.SocketGems = 1.0 + +#AhBot.MaxAuctionCount.OtherTrade.white = 10 +#AhBot.PriceMultiplier.Sell.OtherTrade = 1.0 +#AhBot.PriceMultiplier.Buy.OtherTrade = 1.0 + +#AhBot.PriceMultiplier.Sell.projectile = 1.0 +#AhBot.PriceMultiplier.Buy.projectile = 1.0 +#AhBot.PricingStrategy.projectile = buyOnlyRare diff --git a/src/modules/Bots/botpch.cpp b/src/modules/Bots/botpch.cpp new file mode 100644 index 000000000..5417c3eb7 --- /dev/null +++ b/src/modules/Bots/botpch.cpp @@ -0,0 +1 @@ +#include "botpch.h" diff --git a/src/modules/Bots/botpch.h b/src/modules/Bots/botpch.h new file mode 100644 index 000000000..329246e67 --- /dev/null +++ b/src/modules/Bots/botpch.h @@ -0,0 +1,16 @@ +//add here most rarely modified headers to speed up debug build compilation +#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it +#include "Common.h" + +#include "MapManager.h" +#include "Log.h" +#include "ObjectAccessor.h" +#include "ObjectGuid.h" +#include "SQLStorages.h" +#include "Opcodes.h" +#include "SharedDefines.h" +#include "GuildMgr.h" +#include "ObjectMgr.h" +#include "ScriptMgr.h" + +#include "playerbot.h" \ No newline at end of file diff --git a/src/modules/Bots/playerbot/AiFactory.cpp b/src/modules/Bots/playerbot/AiFactory.cpp new file mode 100644 index 000000000..102eb90e8 --- /dev/null +++ b/src/modules/Bots/playerbot/AiFactory.cpp @@ -0,0 +1,374 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "AiFactory.h" +#include "strategy/Engine.h" + +#include "strategy/priest/PriestAiObjectContext.h" +#include "strategy/mage/MageAiObjectContext.h" +#include "strategy/warlock/WarlockAiObjectContext.h" +#include "strategy/warrior/WarriorAiObjectContext.h" +#include "strategy/shaman/ShamanAiObjectContext.h" +#include "strategy/paladin/PaladinAiObjectContext.h" +#include "strategy/druid/DruidAiObjectContext.h" +#include "strategy/hunter/HunterAiObjectContext.h" +#include "strategy/rogue/RogueAiObjectContext.h" +#include "Player.h" +#include "PlayerbotAIConfig.h" +#include "RandomPlayerbotMgr.h" + + +AiObjectContext* AiFactory::createAiObjectContext(Player* player, PlayerbotAI* ai) +{ + switch (player->getClass()) + { + case CLASS_PRIEST: + return new PriestAiObjectContext(ai); + break; + case CLASS_MAGE: + return new MageAiObjectContext(ai); + break; + case CLASS_WARLOCK: + return new WarlockAiObjectContext(ai); + break; + case CLASS_WARRIOR: + return new WarriorAiObjectContext(ai); + break; + case CLASS_SHAMAN: + return new ShamanAiObjectContext(ai); + break; + case CLASS_PALADIN: + return new PaladinAiObjectContext(ai); + break; + case CLASS_DRUID: + return new DruidAiObjectContext(ai); + break; + case CLASS_HUNTER: + return new HunterAiObjectContext(ai); + break; + case CLASS_ROGUE: + return new RogueAiObjectContext(ai); + break; + } + return new AiObjectContext(ai); +} + +int AiFactory::GetPlayerSpecTab(Player* bot) +{ + map tabs = GetPlayerSpecTabs(bot); + + int tab = -1, max = 0; + for (uint32 i = 0; i < uint32(3); i++) + { + if (tab == -1 || max < tabs[i]) + { + tab = i; + max = tabs[i]; + } + } + + return tab; +} + +map AiFactory::GetPlayerSpecTabs(Player* bot) +{ + map tabs; + for (uint32 i = 0; i < uint32(3); i++) + { + tabs[i] = 0; + } + + uint32 classMask = bot->getClassMask(); + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) + { + TalentEntry const *talentInfo = sTalentStore.LookupEntry(i); + if (!talentInfo) + { + continue; + } + + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab); + if (!talentTabInfo) + { + continue; + } + + if ((classMask & talentTabInfo->ClassMask) == 0) + { + continue; + } + + int maxRank = 0; + for (int rank = MAX_TALENT_RANK - 1; rank >= 0; --rank) + { + if (!talentInfo->RankID[rank]) + { + continue; + } + + uint32 spellid = talentInfo->RankID[rank]; + if (spellid && bot->HasSpell(spellid)) + { + maxRank = rank + 1; + } + + } + tabs[talentTabInfo->tabpage] += maxRank; + } + + return tabs; +} + +void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const facade, Engine* engine) +{ + int tab = GetPlayerSpecTab(player); + + engine->addStrategies("racials", "chat", "default", "aoe", "potions", "cast time", "conserve mana", "duel", "pvp", NULL); + + switch (player->getClass()) + { + case CLASS_PRIEST: + if (tab == 2) + { + engine->addStrategies("dps", "threat", NULL); + if (player->getLevel() > 19) + { + engine->addStrategy("dps debuff"); + } + } + else + { + engine->addStrategy("heal"); + } + + engine->addStrategies("dps assist", "flee", "cure", NULL); + break; + case CLASS_MAGE: + if (tab == 0) + { + engine->addStrategies("arcane", "threat", NULL); + } + else if (tab == 1) + { + engine->addStrategies("fire", "fire aoe", "threat", NULL); + } + else + { + engine->addStrategies("frost", "frost aoe", "threat", NULL); + } + + engine->addStrategies("dps assist", "flee", "cure", NULL); + break; + case CLASS_WARRIOR: + if (tab == 2) + { + engine->addStrategies("tank", "tank aoe", NULL); + } + else + { + engine->addStrategies("dps", "dps assist", "threat", NULL); + } + break; + case CLASS_SHAMAN: + if (tab == 0) + { + engine->addStrategies("caster", "caster aoe", "bmana", "threat", "flee", NULL); + } + else if (tab == 2) + { + engine->addStrategies("heal", "bmana", "flee", NULL); + } + else + { + engine->addStrategies("dps", "melee aoe", "bdps", "threat", NULL); + } + + engine->addStrategies("dps assist", "cure", NULL); + break; + case CLASS_PALADIN: + if (tab == 1) + { + engine->addStrategies("tank", "tank aoe", "bthreat", "cure", NULL); + } + else + { + engine->addStrategies("dps", "bdps", "threat", "dps assist", "cure", NULL); + } + break; + case CLASS_DRUID: + if (tab == 0) + { + engine->addStrategies("caster", "cure", "caster aoe", "threat", "flee", "dps assist", NULL); + if (player->getLevel() > 19) + { + engine->addStrategy("caster debuff"); + } + } + else if (tab == 2) + { + engine->addStrategies("heal", "cure", "flee", "dps assist", NULL); + } + else + { + engine->addStrategies("bear", "tank aoe", "flee", NULL); + } + break; + case CLASS_HUNTER: + engine->addStrategies("dps", "bdps", "threat", "dps assist", NULL); + if (player->getLevel() > 19) + { + engine->addStrategy("dps debuff"); + } + break; + case CLASS_ROGUE: + engine->addStrategies("dps", "threat", "dps assist", NULL); + break; + case CLASS_WARLOCK: + if (tab == 1) + { + engine->addStrategies("tank", "threat", NULL); + } + else + { + engine->addStrategies("dps", "threat", NULL); + } + + if (player->getLevel() > 19) + { + engine->addStrategy("dps debuff"); + } + + engine->addStrategies("dps assist", "flee", NULL); + break; + } + + if (sRandomPlayerbotMgr.IsRandomBot(player)) + { + if (!player->GetGroup()) + { + engine->ChangeStrategy(sPlayerbotAIConfig.randomBotCombatStrategies); + if (player->getClass() == CLASS_DRUID && player->getLevel() < 20) + { + engine->addStrategies("bear", NULL); + } + } + } + else + { + engine->ChangeStrategy(sPlayerbotAIConfig.combatStrategies); + } +} + +Engine* AiFactory::createCombatEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext) { + Engine* engine = new Engine(facade, AiObjectContext); + AddDefaultCombatStrategies(player, facade, engine); + return engine; +} + +void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const facade, Engine* nonCombatEngine) +{ + int tab = GetPlayerSpecTab(player); + + switch (player->getClass()){ + case CLASS_PRIEST: + nonCombatEngine->addStrategies("dps assist", "cure", NULL); + break; + case CLASS_PALADIN: + if (tab == 1) + { + nonCombatEngine->addStrategies("bthreat", "tank aoe", NULL); + } + else + { + nonCombatEngine->addStrategies("bdps", "dps assist", NULL); + } + + nonCombatEngine->addStrategies("cure", NULL); + break; + case CLASS_HUNTER: + nonCombatEngine->addStrategies("bdps", "dps assist", NULL); + break; + case CLASS_SHAMAN: + if (tab == 0 || tab == 2) + { + nonCombatEngine->addStrategy("bmana"); + } + else + { + nonCombatEngine->addStrategy("bdps"); + } + + nonCombatEngine->addStrategies("dps assist", "cure", NULL); + break; + case CLASS_MAGE: + if (tab == 1) + { + nonCombatEngine->addStrategy("bdps"); + } + else + { + nonCombatEngine->addStrategy("bmana"); + } + + nonCombatEngine->addStrategies("dps assist", "cure", NULL); + break; + case CLASS_DRUID: + if (tab == 1) + { + nonCombatEngine->addStrategy("tank aoe"); + } + else + { + nonCombatEngine->addStrategies("dps assist", "cure", NULL); + } + break; + case CLASS_WARRIOR: + if (tab == 2) + { + nonCombatEngine->addStrategy("tank aoe"); + } + else + { + nonCombatEngine->addStrategy("dps assist"); + } + break; + default: + nonCombatEngine->addStrategy("dps assist"); + break; + } + nonCombatEngine->addStrategies("nc", "food", "stay", "chat", + "default", "quest", "loot", "gather", "duel", "emote", "conserve mana", NULL); + + if (sRandomPlayerbotMgr.IsRandomBot(player)) + { + if (!player->GetGroup()) + { + nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig.randomBotNonCombatStrategies); + } + } + else + { + nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig.nonCombatStrategies); + } +} + +Engine* AiFactory::createNonCombatEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext) { + Engine* nonCombatEngine = new Engine(facade, AiObjectContext); + + AddDefaultNonCombatStrategies(player, facade, nonCombatEngine); + return nonCombatEngine; +} + +void AiFactory::AddDefaultDeadStrategies(Player* player, PlayerbotAI* const facade, Engine* deadEngine) +{ + deadEngine->addStrategies("dead", "stay", "chat", "default", "follow", NULL); + if (sRandomPlayerbotMgr.IsRandomBot(player) && !player->GetGroup()) + { + deadEngine->removeStrategy("follow"); + } +} + +Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext) { + Engine* deadEngine = new Engine(facade, AiObjectContext); + AddDefaultDeadStrategies(player, facade, deadEngine); + return deadEngine; +} diff --git a/src/modules/Bots/playerbot/AiFactory.h b/src/modules/Bots/playerbot/AiFactory.h new file mode 100644 index 000000000..736ffb63d --- /dev/null +++ b/src/modules/Bots/playerbot/AiFactory.h @@ -0,0 +1,21 @@ +#pragma once + +class Player; + +using namespace ai; + +class AiFactory +{ +public: + static AiObjectContext* createAiObjectContext(Player* player, PlayerbotAI* ai); + static Engine* createCombatEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext); + static Engine* createNonCombatEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext); + static Engine* createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext); + static void AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const facade, Engine* nonCombatEngine); + static void AddDefaultDeadStrategies(Player* player, PlayerbotAI* const facade, Engine* deadEngine); + static void AddDefaultCombatStrategies(Player* player, PlayerbotAI* const facade, Engine* engine); + +public: + static int GetPlayerSpecTab(Player* player); + static map GetPlayerSpecTabs(Player* player); +}; diff --git a/src/modules/Bots/playerbot/ChatFilter.cpp b/src/modules/Bots/playerbot/ChatFilter.cpp new file mode 100644 index 000000000..09c681ac5 --- /dev/null +++ b/src/modules/Bots/playerbot/ChatFilter.cpp @@ -0,0 +1,311 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "ChatFilter.h" +#include "strategy/values/RtiTargetValue.h" + +using namespace ai; +using namespace std; + +string ChatFilter::Filter(string message) +{ + if (message.find("@") == string::npos) + { + return message; + } + + return message.substr(message.find(" ") + 1); +} + +class StrategyChatFilter : public ChatFilter +{ +public: + explicit StrategyChatFilter(PlayerbotAI* ai) : ChatFilter(ai) {} + + virtual string Filter(string message) + { + Player* bot = ai->GetBot(); + + bool tank = message.find("@tank") == 0; + if (tank && !ai->IsTank(bot)) + { + return ""; + } + + bool dps = message.find("@dps") == 0; + if (dps && (ai->IsTank(bot) || ai->IsHeal(bot))) + { + return ""; + } + + bool heal = message.find("@heal") == 0; + if (heal && !ai->IsHeal(bot)) + { + return ""; + } + + if (tank || dps || heal) + { + return ChatFilter::Filter(message); + } + + return message; + } +}; + +class LevelChatFilter : public ChatFilter +{ +public: + explicit LevelChatFilter(PlayerbotAI* ai) : ChatFilter(ai) {} + + virtual string Filter(string message) + { + Player* bot = ai->GetBot(); + + if (message[0] != '@') + { + return message; + } + + if (message.find("-") != string::npos) + { + int fromLevel = atoi(message.substr(message.find("@") + 1, message.find("-")).c_str()); + int toLevel = atoi(message.substr(message.find("-") + 1, message.find(" ")).c_str()); + + if (bot->getLevel() >= fromLevel && bot->getLevel() <= toLevel) + { + return ChatFilter::Filter(message); + } + + return message; + } + + int level = atoi(message.substr(message.find("@") + 1, message.find(" ")).c_str()); + if (bot->getLevel() == level) + { + return ChatFilter::Filter(message); + } + + return message; + } +}; + +class CombatTypeChatFilter : public ChatFilter +{ +public: + explicit CombatTypeChatFilter(PlayerbotAI* ai) : ChatFilter(ai) {} + + virtual string Filter(string message) + { + Player* bot = ai->GetBot(); + + bool melee = message.find("@melee") == 0; + bool ranged = message.find("@ranged") == 0; + + if (!melee && !ranged) + { + return message; + } + + switch (bot->getClass()) + { + case CLASS_WARRIOR: + case CLASS_PALADIN: + case CLASS_ROGUE: + /*case CLASS_DEATH_KNIGHT: + if (ranged) + { + return ""; + } + break;*/ + + case CLASS_HUNTER: + case CLASS_PRIEST: + case CLASS_MAGE: + case CLASS_WARLOCK: + if (melee) + { + return ""; + } + break; + + case CLASS_DRUID: + if (ranged && ai->IsTank(bot)) + { + return ""; + } + if (melee && !ai->IsTank(bot)) + { + return ""; + } + break; + + case CLASS_SHAMAN: + if (melee && ai->IsHeal(bot)) + { + return ""; + } + if (ranged && !ai->IsHeal(bot)) + { + return ""; + } + break; + } + + return ChatFilter::Filter(message); + } +}; + +class RtiChatFilter : public ChatFilter +{ +public: + explicit RtiChatFilter(PlayerbotAI* ai) : ChatFilter(ai) + { + rtis.push_back("@star"); + rtis.push_back("@circle"); + rtis.push_back("@diamond"); + rtis.push_back("@triangle"); + rtis.push_back("@moon"); + rtis.push_back("@square"); + rtis.push_back("@cross"); + rtis.push_back("@skull"); + } + + virtual string Filter(string message) + { + Player* bot = ai->GetBot(); + Group *group = bot->GetGroup(); + if(!group) + { + return message; + } + + bool found = false; + for (list::iterator i = rtis.begin(); i != rtis.end(); i++) + { + string rti = *i; + + bool isRti = message.find(rti) == 0; + if (!isRti) + { + continue; + } + + ObjectGuid rtiTarget = group->GetTargetIcon(RtiTargetValue::GetRtiIndex(rti.substr(1))); + if (bot->GetObjectGuid() == rtiTarget) + { + return ChatFilter::Filter(message); + } + + Unit* target = *ai->GetAiObjectContext()->GetValue("current target"); + if (!target) + { + return ""; + } + + if (target->GetObjectGuid() != rtiTarget) + { + return ""; + } + + if (found |= isRti) + { + break; + } + } + + if (found) + { + return ChatFilter::Filter(message); + } + + return message; + } + +private: + list rtis; +}; + +class ClassChatFilter : public ChatFilter +{ +public: + explicit ClassChatFilter(PlayerbotAI* ai) : ChatFilter(ai) + { + //classNames["@death_knight"] = CLASS_DEATH_KNIGHT; + classNames["@druid"] = CLASS_DRUID; + classNames["@hunter"] = CLASS_HUNTER; + classNames["@mage"] = CLASS_MAGE; + classNames["@paladin"] = CLASS_PALADIN; + classNames["@priest"] = CLASS_PRIEST; + classNames["@rogue"] = CLASS_ROGUE; + classNames["@shaman"] = CLASS_SHAMAN; + classNames["@warlock"] = CLASS_WARLOCK; + classNames["@warrior"] = CLASS_WARRIOR; + } + + virtual string Filter(string message) + { + Player* bot = ai->GetBot(); + + bool found = false; + for (map::iterator i = classNames.begin(); i != classNames.end(); i++) + { + bool isClass = message.find(i->first) == 0; + if (isClass && bot->getClass() != i->second) + { + return ""; + } + + if (found |= isClass) + { + break; + } + } + + if (found) + { + return ChatFilter::Filter(message); + } + + return message; + } + +private: + map classNames; +}; + + + +CompositeChatFilter::CompositeChatFilter(PlayerbotAI* ai) : ChatFilter(ai) +{ + filters.push_back(new StrategyChatFilter(ai)); + filters.push_back(new ClassChatFilter(ai)); + filters.push_back(new RtiChatFilter(ai)); + filters.push_back(new CombatTypeChatFilter(ai)); + filters.push_back(new LevelChatFilter(ai)); +} + +CompositeChatFilter::~CompositeChatFilter() +{ + for (list::iterator i = filters.begin(); i != filters.end(); i++) + { + delete (*i); + } +} + +string CompositeChatFilter::Filter(string message) +{ + for (int j = 0; j < filters.size(); ++j) + { + for (list::iterator i = filters.begin(); i != filters.end(); i++) + { + message = (*i)->Filter(message); + if (message.empty()) + { + break; + } + } + } + + return message; +} + diff --git a/src/modules/Bots/playerbot/ChatFilter.h b/src/modules/Bots/playerbot/ChatFilter.h new file mode 100644 index 000000000..16dde4dfe --- /dev/null +++ b/src/modules/Bots/playerbot/ChatFilter.h @@ -0,0 +1,24 @@ +#pragma once + +using namespace std; + +namespace ai +{ + class ChatFilter : public PlayerbotAIAware + { + public: + ChatFilter(PlayerbotAI* ai) : PlayerbotAIAware(ai) {} + virtual string Filter(string message); + }; + + class CompositeChatFilter : public ChatFilter + { + public: + CompositeChatFilter(PlayerbotAI* ai); + virtual ~CompositeChatFilter(); + string Filter(string message); + + private: + list filters; + }; +}; diff --git a/src/modules/Bots/playerbot/ChatHelper.cpp b/src/modules/Bots/playerbot/ChatHelper.cpp new file mode 100644 index 000000000..09e688685 --- /dev/null +++ b/src/modules/Bots/playerbot/ChatHelper.cpp @@ -0,0 +1,524 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "ChatHelper.h" +#include "AiFactory.h" + +using namespace ai; +using namespace std; + +map ChatHelper::consumableSubClasses; +map ChatHelper::tradeSubClasses; +map ChatHelper::itemQualities; +map ChatHelper::slots; +map ChatHelper::skills; +map ChatHelper::chats; +map ChatHelper::classes; +map ChatHelper::races; +map > ChatHelper::specs; + +template +static bool substrContainsInMap(string searchTerm, map searchIn) +{ + for (typename map::iterator i = searchIn.begin(); i != searchIn.end(); ++i) + { + string term = i->first; + if (term.size() > 1 && searchTerm.find(term) != string::npos) + { + return true; + } + } + + return false; +} + +ChatHelper::ChatHelper(PlayerbotAI* ai) : PlayerbotAIAware(ai) +{ + itemQualities["poor"] = ITEM_QUALITY_POOR; + itemQualities["gray"] = ITEM_QUALITY_POOR; + itemQualities["normal"] = ITEM_QUALITY_NORMAL; + itemQualities["white"] = ITEM_QUALITY_NORMAL; + itemQualities["uncommon"] = ITEM_QUALITY_UNCOMMON; + itemQualities["green"] = ITEM_QUALITY_UNCOMMON; + itemQualities["rare"] = ITEM_QUALITY_RARE; + itemQualities["blue"] = ITEM_QUALITY_RARE; + itemQualities["epic"] = ITEM_QUALITY_EPIC; + itemQualities["violet"] = ITEM_QUALITY_EPIC; + + consumableSubClasses["potion"] = ITEM_SUBCLASS_POTION; + consumableSubClasses["elixir"] = ITEM_SUBCLASS_ELIXIR; + consumableSubClasses["flask"] = ITEM_SUBCLASS_FLASK; + consumableSubClasses["scroll"] = ITEM_SUBCLASS_SCROLL; + consumableSubClasses["food"] = ITEM_SUBCLASS_FOOD; + consumableSubClasses["bandage"] = ITEM_SUBCLASS_BANDAGE; + consumableSubClasses["enchant"] = ITEM_SUBCLASS_CONSUMABLE_OTHER; + + //tradeSubClasses["cloth"] = ITEM_SUBCLASS_CLOTH; + //tradeSubClasses["leather"] = ITEM_SUBCLASS_LEATHER; + //tradeSubClasses["metal"] = ITEM_SUBCLASS_METAL_STONE; + //tradeSubClasses["stone"] = ITEM_SUBCLASS_METAL_STONE; + //tradeSubClasses["ore"] = ITEM_SUBCLASS_METAL_STONE; + //tradeSubClasses["meat"] = ITEM_SUBCLASS_MEAT; + //tradeSubClasses["herb"] = ITEM_SUBCLASS_HERB; + //tradeSubClasses["elemental"] = ITEM_SUBCLASS_ELEMENTAL; + //tradeSubClasses["disenchants"] = ITEM_SUBCLASS_ENCHANTING; + //tradeSubClasses["enchanting"] = ITEM_SUBCLASS_ENCHANTING; + //tradeSubClasses["gems"] = ITEM_SUBCLASS_JEWELCRAFTING; + //tradeSubClasses["jewels"] = ITEM_SUBCLASS_JEWELCRAFTING; + //tradeSubClasses["jewelcrafting"] = ITEM_SUBCLASS_JEWELCRAFTING; + + slots["head"] = EQUIPMENT_SLOT_HEAD; + slots["neck"] = EQUIPMENT_SLOT_NECK; + slots["shoulder"] = EQUIPMENT_SLOT_SHOULDERS; + slots["shirt"] = EQUIPMENT_SLOT_BODY; + slots["chest"] = EQUIPMENT_SLOT_CHEST; + slots["waist"] = EQUIPMENT_SLOT_WAIST; + slots["legs"] = EQUIPMENT_SLOT_LEGS; + slots["feet"] = EQUIPMENT_SLOT_FEET; + slots["wrist"] = EQUIPMENT_SLOT_WRISTS; + slots["hands"] = EQUIPMENT_SLOT_HANDS; + slots["finger 1"] = EQUIPMENT_SLOT_FINGER1; + slots["finger 2"] = EQUIPMENT_SLOT_FINGER2; + slots["trinket 1"] = EQUIPMENT_SLOT_TRINKET1; + slots["trinket 2"] = EQUIPMENT_SLOT_TRINKET2; + slots["back"] = EQUIPMENT_SLOT_BACK; + slots["main hand"] = EQUIPMENT_SLOT_MAINHAND; + slots["off hand"] = EQUIPMENT_SLOT_OFFHAND; + slots["ranged"] = EQUIPMENT_SLOT_RANGED; + slots["tabard"] = EQUIPMENT_SLOT_TABARD; + + skills["first aid"] = SKILL_FIRST_AID; + skills["fishing"] = SKILL_FISHING; + skills["cooking"] = SKILL_COOKING; + skills["alchemy"] = SKILL_ALCHEMY; + skills["enchanting"] = SKILL_ENCHANTING; + skills["engineering"] = SKILL_ENGINEERING; + skills["leatherworking"] = SKILL_LEATHERWORKING; + skills["blacksmithing"] = SKILL_BLACKSMITHING; + skills["tailoring"] = SKILL_TAILORING; + skills["herbalism"] = SKILL_HERBALISM; + skills["mining"] = SKILL_MINING; + skills["skinning"] = SKILL_SKINNING; + + chats["party"] = CHAT_MSG_PARTY; + chats["p"] = CHAT_MSG_PARTY; + chats["guild"] = CHAT_MSG_GUILD; + chats["g"] = CHAT_MSG_GUILD; + chats["raid"] = CHAT_MSG_RAID; + chats["r"] = CHAT_MSG_RAID; + chats["whisper"] = CHAT_MSG_WHISPER; + chats["w"] = CHAT_MSG_WHISPER; + + classes[CLASS_DRUID] = "druid"; + specs[CLASS_DRUID][0] = "balance"; + specs[CLASS_DRUID][1] = "feral combat"; + specs[CLASS_DRUID][2] = "restoration"; + + classes[CLASS_HUNTER] = "hunter"; + specs[CLASS_HUNTER][0] = "beast mastery"; + specs[CLASS_HUNTER][1] = "marksmanship"; + specs[CLASS_HUNTER][2] = "survival"; + + classes[CLASS_MAGE] = "mage"; + specs[CLASS_MAGE][0] = "arcane"; + specs[CLASS_MAGE][1] = "fire"; + specs[CLASS_MAGE][2] = "frost"; + + classes[CLASS_PALADIN] = "paladin"; + specs[CLASS_PALADIN][0] = "holy"; + specs[CLASS_PALADIN][1] = "protection"; + specs[CLASS_PALADIN][2] = "retribution"; + + classes[CLASS_PRIEST] = "priest"; + specs[CLASS_PRIEST][0] = "discipline"; + specs[CLASS_PRIEST][1] = "holy"; + specs[CLASS_PRIEST][2] = "shadow"; + + classes[CLASS_ROGUE] = "rogue"; + specs[CLASS_ROGUE][0] = "assasination"; + specs[CLASS_ROGUE][1] = "combat"; + specs[CLASS_ROGUE][2] = "subtlety"; + + classes[CLASS_SHAMAN] = "shaman"; + specs[CLASS_SHAMAN][0] = "elemental"; + specs[CLASS_SHAMAN][1] = "enhancement"; + specs[CLASS_SHAMAN][2] = "restoration"; + + classes[CLASS_WARLOCK] = "warlock"; + specs[CLASS_WARLOCK][0] = "affliction"; + specs[CLASS_WARLOCK][1] = "demonology"; + specs[CLASS_WARLOCK][2] = "destruction"; + + classes[CLASS_WARRIOR] = "warrior"; + specs[CLASS_WARRIOR][0] = "arms"; + specs[CLASS_WARRIOR][1] = "fury"; + specs[CLASS_WARRIOR][2] = "protection"; + + races[RACE_DWARF] = "Dwarf"; + races[RACE_GNOME] = "Gnome"; + races[RACE_HUMAN] = "Human"; + races[RACE_NIGHTELF] = "Night Elf"; + races[RACE_ORC] = "Orc"; + races[RACE_TAUREN] = "Tauren"; + races[RACE_TROLL] = "Troll"; + races[RACE_DRAENEI] = "Draenei"; + races[RACE_BLOODELF] = "Blood Elf"; + races[RACE_UNDEAD] = "Undead"; +} + +string ChatHelper::formatMoney(uint32 copper) +{ + ostringstream out; + if (!copper) + { + out << "0"; + return out.str(); + } + + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + copper -= (silver * 100); + + bool space = false; + if (gold > 0) + { + out << gold << "g"; + space = true; + } + + if (silver > 0 && gold < 50) + { + if (space) out << " "; + { + out << silver << "s"; + } + space = true; + } + + if (copper > 0 && gold < 10) + { + if (space) out << " "; + { + out << copper << "c"; + } + } + + return out.str(); +} + +uint32 ChatHelper::parseMoney(string& text) +{ + // if user specified money in ##g##s##c format + string acum = ""; + uint32 copper = 0; + for (uint8 i = 0; i < text.length(); i++) + { + if (text[i] == 'g') + { + copper += (atol(acum.c_str()) * 100 * 100); + acum = ""; + } + else if (text[i] == 'c') + { + copper += atol(acum.c_str()); + acum = ""; + } + else if (text[i] == 's') + { + copper += (atol(acum.c_str()) * 100); + acum = ""; + } + else if (text[i] == ' ') + { + break; + } + else if (text[i] >= 48 && text[i] <= 57) + { + acum += text[i]; + } + else + { + copper = 0; + break; + } + } + return copper; +} + +ItemIds ChatHelper::parseItems(string& text) +{ + ItemIds itemIds; + + uint8 pos = 0; + while (true) + { + int i = text.find("Hitem:", pos); + if (i == -1) + { + break; + } + pos = i + 6; + int endPos = text.find(':', pos); + if (endPos == -1) + { + break; + } + string idC = text.substr(pos, endPos - pos); + uint32 id = atol(idC.c_str()); + pos = endPos; + if (id) + { + itemIds.insert(id); + } + } + + return itemIds; +} + +string ChatHelper::formatQuest(Quest const* quest) +{ + ostringstream out; + out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r"; + return out.str(); +} + +string ChatHelper::formatGameobject(GameObject* go) +{ + ostringstream out; + out << "|cFFFFFF00|Hfound:" << go->GetObjectGuid().GetRawValue() << ":" << go->GetEntry() << ":" << "|h[" << go->GetGOInfo()->name << "]|h|r"; + return out.str(); +} + +string ChatHelper::formatSpell(SpellEntry const *sInfo) +{ + ostringstream out; + out << "|cffffffff|Hspell:" << sInfo->Id << "|h[" << sInfo->SpellName[LOCALE_enUS] << "]|h|r"; + return out.str(); +} + +string ChatHelper::formatItem(ItemPrototype const * proto, int count, int total) +{ + char color[32]; + sprintf(color, "%x", ItemQualityColors[proto->Quality]); + + ostringstream out; + out << "|c" << color << "|Hitem:" << proto->ItemId + << ":0:0:0:0:0:0:0" << "|h[" << proto->Name1 + << "]|h|r"; + + if (count > 1) + { + out << "x" << count; + } + + if (total > 0) + { + out << " (" << total << ")"; + } + + return out.str(); +} + +ChatMsg ChatHelper::parseChat(string& text) +{ + if (chats.find(text) != chats.end()) + { + return chats[text]; + } + + return CHAT_MSG_SYSTEM; +} + +string ChatHelper::formatChat(ChatMsg chat) +{ + switch (chat) + { + case CHAT_MSG_GUILD: + return "guild"; + case CHAT_MSG_PARTY: + return "party"; + case CHAT_MSG_WHISPER: + return "whisper"; + case CHAT_MSG_RAID: + return "raid"; + } + + return "unknown"; +} + + +uint32 ChatHelper::parseSpell(string& text) +{ + PlayerbotChatHandler handler(ai->GetBot()); + return handler.extractSpellId(text); +} + +list ChatHelper::parseGameobjects(string& text) +{ + list gos; + // Link format + // |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << "|h[" << gInfo->name << "]|h|r"; + // |cFFFFFF00|Hfound:9582:1731|h[Copper Vein]|h|r + + uint8 pos = 0; + while (true) + { + // extract GO guid + int i = text.find("Hfound:", pos); // base H = 11 + if (i == -1) // break if error + { + break; + } + + pos = i + 7; //start of window in text 11 + 7 = 18 + int endPos = text.find(':', pos); // end of window in text 22 + if (endPos == -1) //break if error + { + break; + } + istringstream stream(text.substr(pos, endPos - pos)); + uint64 guid; stream >> guid; + + // extract GO entry + pos = endPos + 1; + endPos = text.find(':', pos); // end of window in text + if (endPos == -1) //break if error + { + break; + } + + std::string entryC = text.substr(pos, endPos - pos); // get string within window i.e entry + uint32 entry = atol(entryC.c_str()); // convert ascii to float + + ObjectGuid lootCurrent = ObjectGuid(guid); + + if (guid) + { + gos.push_back(lootCurrent); + } + } + + return gos; +} + +string ChatHelper::formatQuestObjective(const string name, int available, int required) +{ + ostringstream out; + out << "|cFFFFFFFF" << name << (available >= required ? "|c0000FF00: " : "|c00FF0000: ") + << available << "/" << required << "|r"; + + return out.str(); +} + + +uint32 ChatHelper::parseItemQuality(string text) +{ + if (itemQualities.find(text) == itemQualities.end()) + { + return MAX_ITEM_QUALITY; + } + + return itemQualities[text]; +} + +bool ChatHelper::parseItemClass(string text, uint32 *itemClass, uint32 *itemSubClass) +{ + if (text == "questitem") + { + *itemClass = ITEM_CLASS_QUEST; + *itemSubClass = ITEM_SUBCLASS_QUEST; + return true; + } + + if (consumableSubClasses.find(text) != consumableSubClasses.end()) + { + *itemClass = ITEM_CLASS_CONSUMABLE; + *itemSubClass = consumableSubClasses[text]; + return true; + } + + if (tradeSubClasses.find(text) != tradeSubClasses.end()) + { + *itemClass = ITEM_CLASS_TRADE_GOODS; + *itemSubClass = tradeSubClasses[text]; + return true; + } + + return false; +} + +uint32 ChatHelper::parseSlot(string text) +{ + if (slots.find(text) != slots.end()) + { + return slots[text]; + } + + return EQUIPMENT_SLOT_END; +} + +bool ChatHelper::parseable(string text) +{ + return text.find("|H") != string::npos || + text == "questitem" || + substrContainsInMap(text, consumableSubClasses) || + substrContainsInMap(text, tradeSubClasses) || + substrContainsInMap(text, itemQualities) || + substrContainsInMap(text, slots) || + substrContainsInMap(text, chats) || + substrContainsInMap(text, skills) || + parseMoney(text) > 0; +} + +string ChatHelper::formatClass(Player* player, int spec) +{ + uint8 cls = player->getClass(); + + ostringstream out; + out << specs[cls][spec] << " ("; + + map tabs = AiFactory::GetPlayerSpecTabs(player); + int c0 = (int)tabs[0]; + int c1 = (int)tabs[1]; + int c2 = (int)tabs[2]; + + out << (c0 ? "|h|cff00ff00" : "") << c0 << "|h|cffffffff/"; + out << (c1 ? "|h|cff00ff00" : "") << c1 << "|h|cffffffff/"; + out << (c2 ? "|h|cff00ff00" : "") << c2 << "|h|cffffffff"; + + out << ") " << classes[cls]; + return out.str(); +} + +string ChatHelper::formatClass(uint8 cls) +{ + return classes[cls]; +} + +string ChatHelper::formatRace(uint8 race) +{ + return races[race]; +} + +uint32 ChatHelper::parseSkill(string& text) +{ + if (skills.find(text) != skills.end()) + { + return skills[text]; + } + + return SKILL_NONE; +} + +string ChatHelper::formatSkill(uint32 skill) +{ + for (map::iterator i = skills.begin(); i != skills.end(); ++i) + { + if (i->second == skill) + { + return i->first; + } + } + + return ""; +} diff --git a/src/modules/Bots/playerbot/ChatHelper.h b/src/modules/Bots/playerbot/ChatHelper.h new file mode 100644 index 000000000..aaed73025 --- /dev/null +++ b/src/modules/Bots/playerbot/ChatHelper.h @@ -0,0 +1,53 @@ +#pragma once + +using namespace std; + +typedef set ItemIds; +typedef set SpellIds; + +namespace ai +{ + class ChatHelper : public PlayerbotAIAware + { + public: + ChatHelper(PlayerbotAI* ai); + + public: + static string formatMoney(uint32 copper); + static uint32 parseMoney(string& text); + static ItemIds parseItems(string& text); + uint32 parseSpell(string& text); + static string formatQuest(Quest const* quest); + static string formatItem(ItemPrototype const * proto, int count = 0, int total = 0); + static string formatSpell(SpellEntry const *sInfo); + static string formatGameobject(GameObject* go); + static string formatQuestObjective(string name, int available, int required); + static list parseGameobjects(string& text); + + static ChatMsg parseChat(string& text); + static string formatChat(ChatMsg chat); + + static string formatClass(Player* player, int spec); + static string formatClass(uint8 cls); + static string formatRace(uint8 race); + static string formatSkill(uint32 skill); + + static uint32 parseItemQuality(string text); + static bool parseItemClass(string text, uint32 *itemClass, uint32 *itemSubClass); + static uint32 parseSlot(string text); + uint32 parseSkill(string& text); + + static bool parseable(string text); + + private: + static map consumableSubClasses; + static map tradeSubClasses; + static map itemQualities; + static map slots; + static map skills; + static map chats; + static map classes; + static map races; + static map > specs; + }; +}; diff --git a/src/modules/Bots/playerbot/FleeManager.cpp b/src/modules/Bots/playerbot/FleeManager.cpp new file mode 100644 index 000000000..cbb3e4a12 --- /dev/null +++ b/src/modules/Bots/playerbot/FleeManager.cpp @@ -0,0 +1,172 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "FleeManager.h" +#include "PlayerbotAIConfig.h" +#include "Group.h" +#include "strategy/values/LastMovementValue.h" + +using namespace ai; +using namespace std; + +void FleeManager::calculateDistanceToPlayers(FleePoint *point) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return; + } + + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if(player == bot) + { + continue; + } + + float d = player->GetDistance2d(point->x, point->y); + point->toAllPlayers.probe(d); + switch (player->getClass()) { + case CLASS_HUNTER: + case CLASS_MAGE: + case CLASS_PRIEST: + case CLASS_WARLOCK: + point->toRangedPlayers.probe(d); + break; + case CLASS_PALADIN: + case CLASS_ROGUE: + case CLASS_WARRIOR: + point->toMeleePlayers.probe(d); + break; + } + } +} + +void FleeManager::calculateDistanceToCreatures(FleePoint *point) +{ + RangePair &distance = point->toCreatures; + + list units = *bot->GetPlayerbotAI()->GetAiObjectContext()->GetValue >("possible targets"); + for (list::iterator i = units.begin(); i != units.end(); ++i) + { + Unit* unit = bot->GetPlayerbotAI()->GetUnit(*i); + if (!unit) + { + continue; + } + + float d = unit->GetDistance2d(point->x, point->y); + distance.probe(d); + } +} + +void FleeManager::calculatePossibleDestinations(list &points) +{ + float botPosX = bot->GetPositionX(); + float botPosY = bot->GetPositionY(); + float botPosZ = bot->GetPositionZ(); + + for (float distance = sPlayerbotAIConfig.tooCloseDistance; distance <= maxAllowedDistance; distance += 1.0f) + { + for (float angle = followAngle; angle < followAngle + 2 * M_PI; angle += M_PI / 8) + { + float x = botPosX + cos(angle) * distance; + float y = botPosY + sin(angle) * distance; + float z = botPosZ; + bot->UpdateGroundPositionZ(x, y, z); + + if (!bot->IsWithinLOS(x, y, z)) + { + continue; + } + + Map* map = bot->GetMap(); + const TerrainInfo* terrain = map->GetTerrain(); + if (terrain && terrain->IsInWater(x, y, z)) + { + continue; + } + + FleePoint *point = new FleePoint(x, y, z); + calculateDistanceToPlayers(point); + calculateDistanceToCreatures(point); + + if (point->isReasonable()) + { + points.push_back(point); + } + else + { + delete point; + } + } + } +} + +void FleeManager::cleanup(list &points) +{ + for (list::iterator i = points.begin(); i != points.end(); i++) + { + FleePoint* point = *i; + delete point; + } + points.clear(); +} + +bool FleePoint::isReasonable() +{ + return toCreatures.min >= 0 && toCreatures.max >= 0 && + toAllPlayers.min >= 0 && toAllPlayers.max >= 0 && + toAllPlayers.min <= sPlayerbotAIConfig.spellDistance && + toAllPlayers.max <= sPlayerbotAIConfig.sightDistance && + toCreatures.min >= sPlayerbotAIConfig.tooCloseDistance && + toCreatures.max >= sPlayerbotAIConfig.shootDistance; +} + +FleePoint* FleeManager::selectOptimalDestination(list &points) +{ + FleePoint* best = NULL; + for (list::iterator i = points.begin(); i != points.end(); i++) + { + FleePoint* point = *i; + if (!best || (point->toCreatures.min - best->toCreatures.min) >= 0.5f) + { + best = point; + } + else if ((point->toCreatures.min - best->toCreatures.min) >= 0) + { + if (point->toRangedPlayers.max >= 0 && best->toRangedPlayers.max >= 0 && + (point->toRangedPlayers.max - best->toRangedPlayers.max) <= 0.5f) + { + best = point; + } + else if (point->toMeleePlayers.max >= 0 && best->toMeleePlayers.max >= 0 && + (point->toMeleePlayers.min - best->toMeleePlayers.min) >= 0.5f) + { + best = point; + } + } + } + + return best; +} + +bool FleeManager::CalculateDestination(float* rx, float* ry, float* rz) +{ + list points; + calculatePossibleDestinations(points); + + FleePoint* point = selectOptimalDestination(points); + if (!point) + { + cleanup(points); + return false; + } + + *rx = point->x; + *ry = point->y; + *rz = point->z; + + cleanup(points); + return true; +} diff --git a/src/modules/Bots/playerbot/FleeManager.h b/src/modules/Bots/playerbot/FleeManager.h new file mode 100644 index 000000000..73391daf2 --- /dev/null +++ b/src/modules/Bots/playerbot/FleeManager.h @@ -0,0 +1,84 @@ +#pragma once + +using namespace std; + +class Player; + +namespace ai +{ + class Engine; + + class RangePair { + public: + RangePair() { + min = -1.0f; + max = -1.0f; + } + + public: + float min; + float max; + + public: + void probe(float d) { + if (min < 0 || min > d) + { + min = d; + } + + if (max < 0 || max < d) + { + max = d; + } + } + }; + + class FleePoint { + public: + FleePoint(float x, float y, float z) { + this->x = x; + this->y = y; + this->z = z; + } + + public: + bool isReasonable(); + + public: + float x; + float y; + float z; + + RangePair toCreatures; + RangePair toAllPlayers; + RangePair toMeleePlayers; + RangePair toRangedPlayers; + }; + + class FleeManager + { + public: + FleeManager(Player* bot, float maxAllowedDistance, float followAngle) { + this->bot = bot; + this->maxAllowedDistance = maxAllowedDistance; + this->followAngle = followAngle; + } + + public: + bool CalculateDestination(float* rx, float* ry, float* rz); + + private: + void calculatePossibleDestinations(list &points); + void calculateDistanceToPlayers(FleePoint *point); + void calculateDistanceToCreatures(FleePoint *point); + void cleanup(list &points); + FleePoint* selectOptimalDestination(list &points); + bool isBetterThan(FleePoint* point, FleePoint* other); + + private: + Player* bot; + float maxAllowedDistance; + float followAngle; + }; + +}; diff --git a/src/modules/Bots/playerbot/GuildTaskMgr.cpp b/src/modules/Bots/playerbot/GuildTaskMgr.cpp new file mode 100644 index 000000000..56a1082b0 --- /dev/null +++ b/src/modules/Bots/playerbot/GuildTaskMgr.cpp @@ -0,0 +1,1179 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "GuildTaskMgr.h" + +#include "../../modules/Bots/ahbot/AhBot.h" +#include "GuildMgr.h" +#include "DatabaseEnv.h" +#include "Mail.h" +#include "PlayerbotAI.h" + +#include "../../modules/Bots/ahbot/AhBotConfig.h" +#include "RandomItemMgr.h" + +INSTANTIATE_SINGLETON_1(GuildTaskMgr); + +char * strstri (const char* str1, const char* str2); + +enum GuildTaskType +{ + GUILD_TASK_TYPE_NONE = 0, + GUILD_TASK_TYPE_ITEM = 1, + GUILD_TASK_TYPE_KILL = 2 +}; + +GuildTaskMgr::GuildTaskMgr() +{ +} + +GuildTaskMgr::~GuildTaskMgr() +{ +} + +void GuildTaskMgr::Update(Player* player, Player* guildMaster) +{ + if (!sPlayerbotAIConfig.guildTaskEnabled) + { + return; + } + + if (!GetTaskValue(0, 0, "advert_cleanup")) + { + CleanupAdverts(); + RemoveDuplicatedAdverts(); + SetTaskValue(0, 0, "advert_cleanup", 1, sPlayerbotAIConfig.guildTaskAdvertCleanupTime); + } + + uint32 guildId = guildMaster->GetGuildId(); + if (!guildId || !guildMaster->GetPlayerbotAI() || !guildMaster->GetGuildId()) + { + return; + } + + if (!player->IsFriendlyTo(guildMaster)) + { + return; + } + + Guild *guild = sGuildMgr.GetGuildById(guildMaster->GetGuildId()); + DenyReason reason = PLAYERBOT_DENY_NONE; + PlayerbotSecurityLevel secLevel = guildMaster->GetPlayerbotAI()->GetSecurity()->LevelFor(player, &reason); + if (secLevel == PLAYERBOT_SECURITY_DENY_ALL || (secLevel == PLAYERBOT_SECURITY_TALK && reason != PLAYERBOT_DENY_FAR)) + { + sLog.outDebug("%s / %s: skipping guild task update - not enough security level, reason = %u", + guild->GetName().c_str(), player->GetName(), reason); + return; + } + + sLog.outDebug("%s: guild task update for player %s", guild->GetName().c_str(), player->GetName()); + + uint32 owner = (uint32)player->GetGUIDLow(); + + uint32 activeTask = GetTaskValue(owner, guildId, "activeTask"); + if (!activeTask) + { + SetTaskValue(owner, guildId, "killTask", 0, 0); + SetTaskValue(owner, guildId, "itemTask", 0, 0); + SetTaskValue(owner, guildId, "itemCount", 0, 0); + SetTaskValue(owner, guildId, "killTask", 0, 0); + SetTaskValue(owner, guildId, "killCount", 0, 0); + SetTaskValue(owner, guildId, "payment", 0, 0); + SetTaskValue(owner, guildId, "thanks", 1, 2 * sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue(owner, guildId, "reward", 1, 2 * sPlayerbotAIConfig.maxGuildTaskChangeTime); + + uint32 task = CreateTask(player, guildId); + + if (task == GUILD_TASK_TYPE_NONE) + { + sLog.outError( "%s / %s: error creating guild task", + guild->GetName().c_str(), player->GetName()); + } + + uint32 time = urand(sPlayerbotAIConfig.minGuildTaskChangeTime, sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue(owner, guildId, "activeTask", task, time); + SetTaskValue(owner, guildId, "advertisement", 1, + urand(sPlayerbotAIConfig.minGuildTaskAdvertisementTime, sPlayerbotAIConfig.maxGuildTaskAdvertisementTime)); + + sLog.outDebug("%s / %s: guild task %u is set for %u secs", + guild->GetName().c_str(), player->GetName(), + task, time); + return; + } + + uint32 advertisement = GetTaskValue(owner, guildId, "advertisement"); + if (!advertisement) + { + sLog.outDebug("%s / %s: sending advertisement", + guild->GetName().c_str(), player->GetName()); + if (SendAdvertisement(owner, guildId)) + { + SetTaskValue(owner, guildId, "advertisement", 1, + urand(sPlayerbotAIConfig.minGuildTaskAdvertisementTime, sPlayerbotAIConfig.maxGuildTaskAdvertisementTime)); + } + else + { + sLog.outError( "%s / %s: error sending advertisement", + guild->GetName().c_str(), player->GetName()); + } + } + + uint32 thanks = GetTaskValue(owner, guildId, "thanks"); + if (!thanks) + { + sLog.outDebug("%s / %s: sending thanks", + guild->GetName().c_str(), player->GetName()); + if (SendThanks(owner, guildId)) + { + SetTaskValue(owner, guildId, "thanks", 1, 2 * sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue(owner, guildId, "payment", 0, 0); + } + else + { + sLog.outError( "%s / %s: error sending thanks", + guild->GetName().c_str(), player->GetName()); + } + } + + uint32 reward = GetTaskValue(owner, guildId, "reward"); + if (!reward) + { + sLog.outDebug("%s / %s: sending reward", + guild->GetName().c_str(), player->GetName()); + if (Reward(owner, guildId)) + { + SetTaskValue(owner, guildId, "reward", 1, 2 * sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue(owner, guildId, "payment", 0, 0); + } + else + { + sLog.outError( "%s / %s: error sending reward", + guild->GetName().c_str(), player->GetName()); + } + } +} + +uint32 GuildTaskMgr::CreateTask(Player* player, uint32 guildId) +{ + switch (urand(0, 1)) + { + case 0: + CreateItemTask(player, guildId); + return GUILD_TASK_TYPE_ITEM; + default: + CreateKillTask(player, guildId); + return GUILD_TASK_TYPE_KILL; + } +} + +bool GuildTaskMgr::CreateItemTask(Player* player, uint32 guildId) +{ + if (!player) + { + return false; + } + + uint32 itemId = sRandomItemMgr.GetRandomItem(RANDOM_ITEM_GUILD_TASK); + if (!itemId) + { + sLog.outError( "%s / %s: no items avaible for item task", + sGuildMgr.GetGuildById(guildId)->GetName().c_str(), player->GetName()); + return false; + } + + uint32 count = GetMaxItemTaskCount(itemId); + + sLog.outDebug("%s / %s: item task %u (x%d)", + sGuildMgr.GetGuildById(guildId)->GetName().c_str(), player->GetName(), + itemId, count); + + SetTaskValue((uint32)player->GetGUIDLow(), guildId, "itemCount", count, sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue((uint32)player->GetGUIDLow(), guildId, "itemTask", itemId, sPlayerbotAIConfig.maxGuildTaskChangeTime); + return true; +} + +bool GuildTaskMgr::CreateKillTask(Player* player, uint32 guildId) +{ + if (!player) + { + return false; + } + + uint32 rank = !urand(0, 2) ? CREATURE_ELITE_RAREELITE : CREATURE_ELITE_RARE; + vector ids; + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co) + { + continue; + } + + if (co->Rank != rank) + { + continue; + } + + if (co->MaxLevel > player->getLevel() + 4 || co->MinLevel < player->getLevel() - 3) + { + continue; + } + + if (strstr(co->Name, "UNUSED")) + { + continue; + } + + ids.push_back(id); + } + + if (ids.empty()) + { + sLog.outError( "%s / %s: no rare creatures available for kill task", + sGuildMgr.GetGuildById(guildId)->GetName().c_str(), player->GetName()); + return false; + } + + uint32 index = urand(0, ids.size() - 1); + uint32 creatureId = ids[index]; + + sLog.outDebug("%s / %s: kill task %u", + sGuildMgr.GetGuildById(guildId)->GetName().c_str(), player->GetName(), + creatureId); + + SetTaskValue((uint32)player->GetGUIDLow(), guildId, "killTask", creatureId, sPlayerbotAIConfig.maxGuildTaskChangeTime); + return true; +} + +bool GuildTaskMgr::SendAdvertisement(uint32 owner, uint32 guildId) +{ + Guild *guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + return false; + } + + Player* leader = sObjectMgr.GetPlayer(guild->GetLeaderGuid()); + if (!leader) + { + return false; + } + + uint32 validIn; + GetTaskValue(owner, guildId, "activeTask", &validIn); + + uint32 itemTask = GetTaskValue(owner, guildId, "itemTask"); + if (itemTask) + { + return SendItemAdvertisement(itemTask, owner, guildId, validIn); + } + + uint32 killTask = GetTaskValue(owner, guildId, "killTask"); + if (killTask) + { + return SendKillAdvertisement(killTask, owner, guildId, validIn); + } + + return false; +} + +string formatTime(uint32 secs) +{ + ostringstream out; + if (secs < 3600) + { + out << secs / 60 << " min"; + } + else if (secs < 7200) + { + out << "1 hr " << (secs - 3600) / 60 << " min"; + } + else if (secs < 3600 * 24) + { + out << secs / 3600 << " hr"; + } else + { + out << secs / 3600 / 24 << " days"; + } + + return out.str(); +} + +string formatDateTime(uint32 secs) +{ + time_t rawtime = time(0) + secs; + tm* timeinfo = localtime (&rawtime); + + char buffer[256]; + strftime(buffer, sizeof(buffer), "%b %d, %H:%M", timeinfo); + return string(buffer); +} + +string GetHelloText(uint32 owner) +{ + ostringstream body; + body << "Hello"; + string playerName; + sObjectMgr.GetPlayerNameByGUID(ObjectGuid(HIGHGUID_PLAYER, owner), playerName); + if (!playerName.empty()) body << ", " << playerName; + { + body << ",\n\n"; + } + return body.str(); +} + +bool GuildTaskMgr::SendItemAdvertisement(uint32 itemId, uint32 owner, uint32 guildId, uint32 validIn) +{ + Guild *guild = sGuildMgr.GetGuildById(guildId); + Player* leader = sObjectMgr.GetPlayer(guild->GetLeaderGuid()); + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return false; + } + + ostringstream body; + body << GetHelloText(owner); + body << "We are in a great need of " << proto->Name1 << ". If you could sell us "; + uint32 count = GetTaskValue(owner, guildId, "itemCount"); + if (count > 1) + { + body << "at least " << count << " of them "; + } + else + { + body << "some "; + } + body << "we'd really appreciate that and pay a high price.\n\n"; + body << "The task will expire at " << formatDateTime(validIn) << "\n"; + body << "\n"; + body << "Best Regards,\n"; + body << guild->GetName() << "\n"; + body << leader->GetName() << "\n"; + + ostringstream subject; + subject << "Guild Task: " << proto->Name1; + if (count > 1) subject << " (x" << count << ")"; + { + MailDraft(subject.str(), body.str()).SendMailTo(MailReceiver(ObjectGuid(HIGHGUID_PLAYER, owner)), MailSender(leader)); + } + + return true; +} + + +bool GuildTaskMgr::SendKillAdvertisement(uint32 creatureId, uint32 owner, uint32 guildId, uint32 validIn) +{ + Guild *guild = sGuildMgr.GetGuildById(guildId); + Player* leader = sObjectMgr.GetPlayer(guild->GetLeaderGuid()); + + CreatureInfo const* proto = sObjectMgr.GetCreatureTemplate(creatureId); + if (!proto) + { + return false; + } + + QueryResult *result = WorldDatabase.PQuery("SELECT map, position_x, position_y, position_z FROM creature where id = '%u'", creatureId); + if (!result) + { + return false; + } + + string location; + do + { + Field* fields = result->Fetch(); + uint32 mapid = fields[0].GetUInt32(); + float x = fields[1].GetFloat(); + float y = fields[2].GetFloat(); + float z = fields[3].GetFloat(); + Map* map = sMapMgr.FindMap(mapid, 0); + if (!map) + { + continue; + } + uint32 area = map->GetTerrain()->GetAreaId(x, y, z); + const AreaTableEntry* entry = sAreaStore.LookupEntry(area); + if (!entry) continue; + { + location = entry->area_name[0]; + } + break; + } while (result->NextRow()); + delete result; + + ostringstream body; + body << GetHelloText(owner); + body << "As you probably know " << proto->Name << " is wanted dead for the crimes it did against our guild. If you should kill it "; + body << "we'd really appreciate that.\n\n"; + if (!location.empty()) + { + body << proto->Name << "'s the last known location was " << location << ".\n"; + } + body << "The task will expire at " << formatDateTime(validIn) << "\n"; + body << "\n"; + body << "Best Regards,\n"; + body << guild->GetName() << "\n"; + body << leader->GetName() << "\n"; + + ostringstream subject; + subject << "Guild Task: "; + if (proto->Rank == CREATURE_ELITE_ELITE || proto->Rank == CREATURE_ELITE_RAREELITE || proto->Rank == CREATURE_ELITE_WORLDBOSS) + { + subject << "(Elite) "; + } + subject << proto->Name; + if (!location.empty()) + { + subject << ", " << location; + } + + MailDraft(subject.str(), body.str()).SendMailTo(MailReceiver(ObjectGuid(HIGHGUID_PLAYER, owner)), MailSender(leader)); + + return true; +} + +bool GuildTaskMgr::SendThanks(uint32 owner, uint32 guildId) +{ + Guild *guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + return false; + } + + Player* leader = sObjectMgr.GetPlayer(guild->GetLeaderGuid()); + if (!leader) + { + return false; + } + + uint32 itemTask = GetTaskValue(owner, guildId, "itemTask"); + if (itemTask) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemTask); + if (!proto) + { + return false; + } + + ostringstream body; + body << GetHelloText(owner); + body << "One of our guild members wishes to thank you for the " << proto->Name1 << "!"; + uint32 count = GetTaskValue(owner, guildId, "itemCount"); + if (count) + { + body << " If we have another "; + body << count << " of them that would help us tremendously.\n"; + } + body << "\n"; + body << "Thanks again,\n"; + body << guild->GetName() << "\n"; + body << leader->GetName() << "\n"; + + MailDraft("Thank You", body.str()). + SetMoney(GetTaskValue(owner, guildId, "payment")). + SendMailTo(MailReceiver(ObjectGuid(HIGHGUID_PLAYER, owner)), MailSender(leader)); + + Player* player = sObjectMgr.GetPlayer(ObjectGuid(HIGHGUID_PLAYER, owner)); + if (player) + { + SendCompletionMessage(player, "payed for"); + } + + return true; + } + + return false; +} + +uint32 GuildTaskMgr::GetMaxItemTaskCount(uint32 itemId) +{ + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return 0; + } + + if (!proto->Stackable || proto->GetMaxStackSize() == 1) + { + return 1; + } + + if (proto->Quality == ITEM_QUALITY_NORMAL) + { + switch (proto->GetMaxStackSize()) + { + case 5: + return urand(1, 3) * proto->GetMaxStackSize(); + case 10: + return urand(2, 6) * proto->GetMaxStackSize() / 2; + case 20: + return urand(4, 12) * proto->GetMaxStackSize() / 4; + default: + return proto->GetMaxStackSize(); + } + } + + if (proto->Quality < ITEM_QUALITY_RARE) + { + switch (proto->GetMaxStackSize()) + { + case 5: + return proto->GetMaxStackSize(); + case 10: + return urand(1, 2) * proto->GetMaxStackSize() / 2; + case 20: + return urand(1, 4) * proto->GetMaxStackSize() / 4; + default: + return proto->GetMaxStackSize(); + } + } + + return 1; +} + +bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId) +{ + uint32 value = 0; + + QueryResult* results = CharacterDatabase.PQuery( + "select `value`, `time`, validIn from ai_playerbot_guild_tasks where `value` = '%u' and guildid = '%u' and `type` = 'itemTask'", + itemId, guildId); + + if (results) + { + Field* fields = results->Fetch(); + value = fields[0].GetUInt32(); + uint32 lastChangeTime = fields[1].GetUInt32(); + uint32 validIn = fields[2].GetUInt32(); + if ((time(0) - lastChangeTime) >= validIn) + { + value = 0; + } + + delete results; + } + + return value; +} + +map GuildTaskMgr::GetTaskValues(uint32 owner, string type, uint32 *validIn /* = NULL */) +{ + map result; + + QueryResult* results = CharacterDatabase.PQuery( + "select `value`, `time`, validIn, guildid from ai_playerbot_guild_tasks where owner = '%u' and `type` = '%s'", + owner, type.c_str()); + + if (!results) + { + return result; + } + + do + { + Field* fields = results->Fetch(); + uint32 value = fields[0].GetUInt32(); + uint32 lastChangeTime = fields[1].GetUInt32(); + uint32 secs = fields[2].GetUInt32(); + uint32 guildId = fields[3].GetUInt32(); + if ((time(0) - lastChangeTime) >= secs) + { + value = 0; + } + + result[guildId] = value; + + } while (results->NextRow()); + + delete results; + return result; +} + +uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, string type, uint32 *validIn /* = NULL */) +{ + uint32 value = 0; + + QueryResult* results = CharacterDatabase.PQuery( + "select `value`, `time`, validIn from ai_playerbot_guild_tasks where owner = '%u' and guildid = '%u' and `type` = '%s'", + owner, guildId, type.c_str()); + + if (results) + { + Field* fields = results->Fetch(); + value = fields[0].GetUInt32(); + uint32 lastChangeTime = fields[1].GetUInt32(); + uint32 secs = fields[2].GetUInt32(); + if ((time(0) - lastChangeTime) >= secs) + { + value = 0; + } + + if (validIn) *validIn = secs; + } + + delete results; + return value; +} + +uint32 GuildTaskMgr::SetTaskValue(uint32 owner, uint32 guildId, string type, uint32 value, uint32 validIn) +{ + CharacterDatabase.DirectPExecute("delete from ai_playerbot_guild_tasks where owner = '%u' and guildid = '%u' and `type` = '%s'", + owner, guildId, type.c_str()); + if (value) + { + CharacterDatabase.DirectPExecute( + "insert into ai_playerbot_guild_tasks (owner, guildid, `time`, validIn, `type`, `value`) values ('%u', '%u', '%u', '%u', '%s', '%u')", + owner, guildId, (uint32)time(0), validIn, type.c_str(), value); + } + + return value; +} + +bool GuildTaskMgr::HandleConsoleCommand(ChatHandler* handler, char const* args) +{ + if (!sPlayerbotAIConfig.guildTaskEnabled) + { + sLog.outError( "Guild task system is currently disabled!"); + return false; + } + + if (!args || !*args) + { + sLog.outError( "Usage: gtask stats/reset"); + return false; + } + + string cmd = args; + + if (cmd == "reset") + { + CharacterDatabase.PExecute("delete from ai_playerbot_guild_tasks"); + sLog.outString("Guild tasks were reset for all players"); + return true; + } + + if (cmd == "stats") + { + sLog.outString("Usage: gtask stats "); + return true; + } + + if (cmd.find("stats ") != string::npos) + { + string charName = cmd.substr(cmd.find("stats ") + 6); + ObjectGuid guid = sObjectMgr.GetPlayerGuidByName(charName); + if (!guid) + { + sLog.outError( "Player %s not found", charName.c_str()); + return false; + } + + uint32 owner = (uint32)guid.GetRawValue(); + + QueryResult* result = CharacterDatabase.PQuery( + "select `value`, `time`, validIn, guildid from ai_playerbot_guild_tasks where owner = '%u' and type='activeTask' order by guildid", + owner); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 value = fields[0].GetUInt32(); + uint32 lastChangeTime = fields[1].GetUInt32(); + uint32 validIn = fields[2].GetUInt32(); + if ((time(0) - lastChangeTime) >= validIn) + { + value = 0; + } + uint32 guildId = fields[3].GetUInt32(); + + Guild *guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + continue; + } + + ostringstream name; + if (value == GUILD_TASK_TYPE_ITEM) + { + name << "ItemTask"; + uint32 itemId = sGuildTaskMgr.GetTaskValue(owner, guildId, "itemTask"); + uint32 itemCount = sGuildTaskMgr.GetTaskValue(owner, guildId, "itemCount"); + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (proto) + { + name << " (" << proto->Name1 << " x" << itemCount << ","; + switch (proto->Quality) + { + case ITEM_QUALITY_UNCOMMON: + name << "green"; + break; + case ITEM_QUALITY_NORMAL: + name << "white"; + break; + case ITEM_QUALITY_RARE: + name << "blue"; + break; + case ITEM_QUALITY_EPIC: + name << "epic"; + break; + } + name << ")"; + } + } + else if (value == GUILD_TASK_TYPE_KILL) + { + name << "KillTask"; + uint32 creatureId = sGuildTaskMgr.GetTaskValue(owner, guildId, "killTask"); + CreatureInfo const* proto = sObjectMgr.GetCreatureTemplate(creatureId); + if (proto) + { + name << " (" << proto->Name << ","; + switch (proto->Rank) + { + case CREATURE_ELITE_RARE: + name << "rare"; + break; + case CREATURE_ELITE_RAREELITE: + name << "rare elite"; + break; + } + name << ")"; + } + } + else + { + continue; + } + + uint32 advertValidIn = 0; + uint32 advert = sGuildTaskMgr.GetTaskValue(owner, guildId, "advertisement", &advertValidIn); + if (advert && advertValidIn < validIn) + { + name << " advert in " << formatTime(advertValidIn); + } + + uint32 thanksValidIn = 0; + uint32 thanks = sGuildTaskMgr.GetTaskValue(owner, guildId, "thanks", &thanksValidIn); + if (thanks && thanksValidIn < validIn) + { + name << " thanks in " << formatTime(thanksValidIn); + } + + uint32 rewardValidIn = 0; + uint32 reward = sGuildTaskMgr.GetTaskValue(owner, guildId, "reward", &rewardValidIn); + if (reward && rewardValidIn < validIn) + { + name << " reward in " << formatTime(rewardValidIn); + } + + uint32 paymentValidIn = 0; + uint32 payment = sGuildTaskMgr.GetTaskValue(owner, guildId, "payment", &paymentValidIn); + if (payment && paymentValidIn < validIn) + { + name << " payment " << ChatHelper::formatMoney(payment) << " in " << formatTime(paymentValidIn); + } + + sLog.outString("%s: %s valid in %s ['%s']", + charName.c_str(), name.str().c_str(), formatTime(validIn).c_str(), guild->GetName().c_str()); + + } while (result->NextRow()); + + delete result; + } + + return true; + } + + if (cmd == "cleanup") + { + sGuildTaskMgr.CleanupAdverts(); + sGuildTaskMgr.RemoveDuplicatedAdverts(); + return true; + } + + if (cmd == "reward") + { + sLog.outString("Usage: gtask reward "); + return true; + } + + if (cmd == "advert") + { + sLog.outString("Usage: gtask advert "); + return true; + } + + bool reward = cmd.find("reward ") != string::npos; + bool advert = cmd.find("advert ") != string::npos; + if (reward || advert) + { + string charName; + if (reward) charName = cmd.substr(cmd.find("reward ") + 7); + { + if (advert) charName = cmd.substr(cmd.find("advert ") + 7); + } + ObjectGuid guid = sObjectMgr.GetPlayerGuidByName(charName); + if (!guid) + { + sLog.outError( "Player %s not found", charName.c_str()); + return false; + } + + uint32 owner = (uint32)guid.GetRawValue(); + QueryResult* result = CharacterDatabase.PQuery( + "select distinct guildid from ai_playerbot_guild_tasks where owner = '%u'", + owner); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 guildId = fields[0].GetUInt32(); + Guild *guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + continue; + } + + if (reward) sGuildTaskMgr.Reward(owner, guildId); + { + if (advert) sGuildTaskMgr.SendAdvertisement(owner, guildId); + } + } while (result->NextRow()); + + delete result; + return true; + } + } + + return false; +} + +bool GuildTaskMgr::CheckItemTask(uint32 itemId, uint32 obtained, Player* ownerPlayer, Player* bot, bool byMail) +{ + uint32 guildId = bot->GetGuildId(); + if (!guildId) + { + return false; + } + + uint32 owner = (uint32)ownerPlayer->GetGUIDLow(); + Guild *guild = sGuildMgr.GetGuildById(bot->GetGuildId()); + if (!guild) + { + return false; + } + + if (!sRandomPlayerbotMgr.IsRandomBot(bot)) + { + return false; + } + + sLog.outDebug("%s / %s: checking guild task", + guild->GetName().c_str(), ownerPlayer->GetName()); + + uint32 itemTask = GetTaskValue(owner, guildId, "itemTask"); + if (itemTask != itemId) + { + sLog.outDebug("%s / %s: item %u is not guild task item (%u)", + guild->GetName().c_str(), ownerPlayer->GetName(), + itemId, itemTask); + SendCompletionMessage(ownerPlayer, "made a mistake with"); + return false; + } + + uint32 count = GetTaskValue(owner, guildId, "itemCount"); + if (!count) { + { + return false; + } + } + + uint32 rewardTime = urand(sPlayerbotAIConfig.minGuildTaskRewardTime, sPlayerbotAIConfig.maxGuildTaskRewardTime); + if (byMail) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return false; + } + + uint32 money = GetTaskValue(owner, guildId, "payment"); + SetTaskValue(owner, guildId, "payment", money + auctionbot.GetBuyPrice(proto) * obtained, rewardTime + 300); + } + + if (obtained >= count) + { + sLog.outDebug("%s / %s: guild task complete", + guild->GetName().c_str(), ownerPlayer->GetName()); + SetTaskValue(owner, guildId, "reward", 1, rewardTime - 15); + SetTaskValue(owner, guildId, "itemCount", 0, 0); + SetTaskValue(owner, guildId, "thanks", 0, 0); + SendCompletionMessage(ownerPlayer, "completed"); + } + else + { + sLog.outDebug("%s / %s: guild task progress %u/%u", + guild->GetName().c_str(), ownerPlayer->GetName(), obtained, count); + SetTaskValue(owner, guildId, "itemCount", count - obtained, sPlayerbotAIConfig.maxGuildTaskChangeTime); + SetTaskValue(owner, guildId, "thanks", 1, rewardTime - 30); + SendCompletionMessage(ownerPlayer, "made a progress with"); + } + return true; +} + +bool GuildTaskMgr::Reward(uint32 owner, uint32 guildId) +{ + Guild *guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + return false; + } + + Player* leader = sObjectMgr.GetPlayer(guild->GetLeaderGuid()); + if (!leader) + { + return false; + } + + uint32 itemTask = GetTaskValue(owner, guildId, "itemTask"); + uint32 killTask = GetTaskValue(owner, guildId, "killTask"); + if (!itemTask && !killTask) + { + return false; + } + + ostringstream body; + body << GetHelloText(owner); + + RandomItemType rewardType; + if (itemTask) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemTask); + if (!proto) + { + return false; + } + + body << "We wish to thank you for the " << proto->Name1 << " you provided so kindly. We really appreciate this and may this small gift bring you our thanks!\n"; + body << "\n"; + body << "Many thanks,\n"; + body << guild->GetName() << "\n"; + body << leader->GetName() << "\n"; + rewardType = proto->Quality > ITEM_QUALITY_NORMAL ? RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE : RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN; + } + else if (killTask) + { + CreatureInfo const* proto = sObjectMgr.GetCreatureTemplate(killTask); + if (!proto) + { + return false; + } + + body << "We wish to thank you for the " << proto->Name << " you've killed recently. We really appreciate this and may this small gift bring you our thanks!\n"; + body << "\n"; + body << "Many thanks,\n"; + body << guild->GetName() << "\n"; + body << leader->GetName() << "\n"; + rewardType = RANDOM_ITEM_GUILD_TASK_REWARD_TRADE; + } + + uint32 payment = GetTaskValue(owner, guildId, "payment"); + if (payment) + { + SendThanks(owner, guildId); + } + + MailDraft draft("Thank You", body.str()); + + uint32 itemId = sRandomItemMgr.GetRandomItem(rewardType); + if (itemId) + { + Item* item = Item::CreateItem(itemId, 1, leader); + item->SaveToDB(); + draft.AddItem(item); + } + + draft.SendMailTo(MailReceiver(ObjectGuid(HIGHGUID_PLAYER, owner)), MailSender(leader)); + Player* player = sObjectMgr.GetPlayer(ObjectGuid(HIGHGUID_PLAYER, owner)); + if (player) + { + SendCompletionMessage(player, "rewarded for"); + } + + SetTaskValue(owner, guildId, "activeTask", 0, 0); + SetTaskValue(owner, guildId, "payment", 0, 0); + return true; +} + +void GuildTaskMgr::CheckKillTask(Player* player, Unit* victim) +{ + Group *group = player->GetGroup(); + if (group) + { + for (GroupReference *gr = group->GetFirstMember(); gr; gr = gr->next()) + { + CheckKillTaskInternal(gr->getSource(), victim); + } + } + else + { + CheckKillTaskInternal(player, victim); + } +} + +void GuildTaskMgr::SendCompletionMessage(Player* player, const string verb) +{ + ostringstream out; out << player->GetName() << " has " << verb << " a guild task"; + + Group* group = player->GetGroup(); + if (group) + { + for (GroupReference* gr = group->GetFirstMember(); gr; gr = gr->next()) + { + Player* member = gr->getSource(); + if (member != player) + { + ChatHandler(member->GetSession()).PSendSysMessage(out.str().c_str()); + } + } + } + else + { + PlayerbotAI *ai = player->GetPlayerbotAI(); + if (ai && ai->GetMaster()) + { + ChatHandler(ai->GetMaster()->GetSession()).PSendSysMessage(out.str().c_str()); + } + } + + ostringstream self; self << "You have " << verb << " a guild task"; + ChatHandler(player->GetSession()).PSendSysMessage(self.str().c_str()); +} + +void GuildTaskMgr::CheckKillTaskInternal(Player* player, Unit* victim) +{ + uint32 owner = player->GetGUIDLow(); + Creature* creature = victim->ToCreature(); + if (!creature) + { + return; + } + + map tasks = GetTaskValues(owner, "killTask"); + for (map::iterator i = tasks.begin(); i != tasks.end(); ++i) + { + uint32 guildId = i->first; + uint32 value = i->second; + Guild* guild = sGuildMgr.GetGuildById(guildId); + + if (value != creature->GetCreatureInfo()->Entry) + { + continue; + } + + sLog.outDebug("%s / %s: guild task complete", + guild->GetName().c_str(), player->GetName()); + SetTaskValue(owner, guildId, "reward", 1, + urand(sPlayerbotAIConfig.minGuildTaskRewardTime, sPlayerbotAIConfig.maxGuildTaskRewardTime)); + + SendCompletionMessage(player, "completed"); + } +} + +void GuildTaskMgr::CleanupAdverts() +{ + uint32 deliverTime = time(0) - sPlayerbotAIConfig.maxGuildTaskChangeTime; + QueryResult *result = CharacterDatabase.PQuery("select id, receiver from mail where subject like 'Guild Task%%' and deliver_time <= '%u'", deliverTime); + if (!result) + { + return; + } + + int count = 0; + do + { + Field* fields = result->Fetch(); + uint32 id = fields[0].GetUInt32(); + uint32 receiver = fields[1].GetUInt32(); + Player *player = sObjectMgr.GetPlayer(ObjectGuid(HIGHGUID_PLAYER, receiver)); + if (player) player->RemoveMail(id); + { + count++; + } + } while (result->NextRow()); + delete result; + + if (count > 0) + { + CharacterDatabase.PExecute("delete from mail where subject like 'Guild Task%%' and deliver_time <= '%u'", deliverTime); + sLog.outBasic("%d old gtask adverts removed", count); + } +} + +void GuildTaskMgr::RemoveDuplicatedAdverts() +{ + uint32 deliverTime = time(0); + QueryResult *result = CharacterDatabase.PQuery( + "select m.id, m.receiver from (SELECT max(id) as id, subject, receiver FROM mail where subject like 'Guild Task%%' and deliver_time <= '%u' group by subject, receiver) q " + "join mail m on m.subject = q.subject where m.id <> q.id and m.deliver_time <= '%u'", + deliverTime, deliverTime); + if (!result) + { + return; + } + + list ids; + int count = 0; + do + { + Field* fields = result->Fetch(); + uint32 id = fields[0].GetUInt32(); + uint32 receiver = fields[1].GetUInt32(); + Player *player = sObjectMgr.GetPlayer(ObjectGuid(HIGHGUID_PLAYER, receiver)); + if (player) player->RemoveMail(id); + { + count++; + } + ids.push_back(id); + } while (result->NextRow()); + delete result; + + if (count > 0) + { + list buffer; + for (list::iterator i = ids.begin(); i != ids.end(); ++i) + { + buffer.push_back(*i); + if (buffer.size() > 50) + { + DeleteMail(buffer); + buffer.clear(); + } + } + DeleteMail(buffer); + sLog.outBasic("%d duplicated gtask adverts removed", count); + } + +} + +void GuildTaskMgr::DeleteMail(list buffer) +{ + ostringstream sql; + sql << "delete from mail where id in ( "; + bool first = true; + for (list::iterator j = buffer.begin(); j != buffer.end(); ++j) + { + if (first) first = false; else sql << ","; + { + sql << "'" << *j << "'"; + } + } + sql << ")"; + CharacterDatabase.PExecute(sql.str().c_str()); +} diff --git a/src/modules/Bots/playerbot/GuildTaskMgr.h b/src/modules/Bots/playerbot/GuildTaskMgr.h new file mode 100644 index 000000000..a004ee8d1 --- /dev/null +++ b/src/modules/Bots/playerbot/GuildTaskMgr.h @@ -0,0 +1,50 @@ +#ifndef _GuildTaskMgr_H +#define _GuildTaskMgr_H + +#include "Common.h" +#include "PlayerbotAIBase.h" + +using namespace std; + +class GuildTaskMgr +{ + public: + GuildTaskMgr(); + virtual ~GuildTaskMgr(); + static GuildTaskMgr& instance() + { + static GuildTaskMgr instance; + return instance; + } + + void Update(Player* owner, Player* guildMaster); + + public: + static bool HandleConsoleCommand(ChatHandler* handler, char const* args); + bool IsGuildTaskItem(uint32 itemId, uint32 guildId); + bool CheckItemTask(uint32 itemId, uint32 obtained, Player* owner, Player* bot, bool byMail = false); + void CheckKillTask(Player* owner, Unit* victim); + void CheckKillTaskInternal(Player* owner, Unit* victim); + + private: + map GetTaskValues(uint32 owner, string type, uint32 *validIn = NULL); + uint32 GetTaskValue(uint32 owner, uint32 guildId, string type, uint32 *validIn = NULL); + uint32 SetTaskValue(uint32 owner, uint32 guildId, string type, uint32 value, uint32 validIn); + uint32 CreateTask(Player* player, uint32 guildId); + bool SendAdvertisement(uint32 owner, uint32 guildId); + bool SendItemAdvertisement(uint32 itemId, uint32 owner, uint32 guildId, uint32 validIn); + bool SendKillAdvertisement(uint32 creatureId, uint32 owner, uint32 guildId, uint32 validIn); + bool SendThanks(uint32 owner, uint32 guildId); + bool Reward(uint32 owner, uint32 guildId); + bool CreateItemTask(Player* player, uint32 guildId); + bool CreateKillTask(Player* player, uint32 guildId); + uint32 GetMaxItemTaskCount(uint32 itemId); + void CleanupAdverts(); + void RemoveDuplicatedAdverts(); + void DeleteMail(list buffer); + void SendCompletionMessage(Player* player, string verb); +}; + +#define sGuildTaskMgr GuildTaskMgr::instance() + +#endif diff --git a/src/modules/Bots/playerbot/Helpers.cpp b/src/modules/Bots/playerbot/Helpers.cpp new file mode 100644 index 000000000..4fe5c5929 --- /dev/null +++ b/src/modules/Bots/playerbot/Helpers.cpp @@ -0,0 +1,78 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "Util.h" +#include +#include +#include + +vector& split(const string &s, char delim, vector &elems) +{ + stringstream ss(s); + string item; + while(getline(ss, item, delim)) + { + elems.push_back(item); + } + return elems; +} + + +vector split(const string &s, char delim) +{ + vector elems; + return split(s, delim, elems); +} + +char *strstri(const char *haystack, const char *needle) +{ + if ( !*needle ) + { + return (char*)haystack; + } + for ( ; *haystack; ++haystack ) + { + if ( tolower(*haystack) == tolower(*needle) ) + { + const char *h = haystack, *n = needle; + for ( ; *h && *n; ++h, ++n ) + { + if ( tolower(*h) != tolower(*n) ) + { + break; + } + } + if ( !*n ) + { + return (char*)haystack; + } + } + } + return 0; +} + + + +uint64 extractGuid(WorldPacket& packet) +{ + uint8 mask; + packet >> mask; + uint64 guid = 0; + uint8 bit = 0; + uint8 testMask = 1; + while (true) + { + if (mask & testMask) + { + uint8 word; + packet >> word; + guid += (word << bit); + } + if (bit == 7) + { + break; + } + ++bit; + testMask <<= 1; + } + return guid; +} diff --git a/src/modules/Bots/playerbot/Helpers.h b/src/modules/Bots/playerbot/Helpers.h new file mode 100644 index 000000000..7ab3d7584 --- /dev/null +++ b/src/modules/Bots/playerbot/Helpers.h @@ -0,0 +1,54 @@ +#pragma once + +template +map filterList(vector src, string filter) +{ + map result; + if (filter.empty() || filter == "*") + { + int idx = 0; + for (auto i = src.begin(); i != src.end(); ++i) + { + result[idx++] = *i; + } + return result; + } + + if (filter.find("-") != string::npos) + { + vector ss = split(filter, '-'); + int from = 0, to = src.size() - 1; + if (!ss[0].empty()) from = atoi(ss[0].c_str()) - 1; + { + if (ss.size() > 1 && !ss[1].empty()) to = atoi(ss[1].c_str()) - 1; + } + if (from < 0) from = 0; + { + if (from > src.size() - 1) from = src.size() - 1; + } + if (to < 0) to = 0; + { + if (to > src.size() - 1) to = src.size() - 1; + } + + for (int i = from; i <= to; ++i) + { + result[i] = src[i]; + } + + return result; + } + + vector ss = split(filter, ','); + for (auto i = ss.begin(); i != ss.end(); ++i) + { + int idx = atoi(i->c_str()) - 1; + if (idx < 0) idx = 0; + { + if (idx > src.size() - 1) idx = src.size() - 1; + } + result[idx] = src[idx]; + } + + return result; +} diff --git a/src/modules/Bots/playerbot/LazyCalculatedValue.h b/src/modules/Bots/playerbot/LazyCalculatedValue.h new file mode 100644 index 000000000..09e79aa56 --- /dev/null +++ b/src/modules/Bots/playerbot/LazyCalculatedValue.h @@ -0,0 +1,42 @@ +#pragma once + +using namespace std; + +namespace ai +{ + template + class LazyCalculatedValue + { + public: + typedef TValue (TOwner::*Calculator)(); + + public: + LazyCalculatedValue(TOwner* owner, Calculator calculator) + { + this->calculator = calculator; + this->owner = owner; + Reset(); + } + + public: + TValue GetValue() + { + if (!calculated) + { + value = (owner->*calculator)(); + calculated = true; + } + return value; + } + void Reset() + { + calculated = false; + } + + protected: + Calculator calculator; + TOwner* owner; + bool calculated; + TValue value; + }; +}; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/LootObjectStack.cpp b/src/modules/Bots/playerbot/LootObjectStack.cpp new file mode 100644 index 000000000..c99ac2842 --- /dev/null +++ b/src/modules/Bots/playerbot/LootObjectStack.cpp @@ -0,0 +1,284 @@ +#include "../botpch.h" +#include "LootObjectStack.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" + +using namespace ai; +using namespace std; + +#define MAX_LOOT_OBJECT_COUNT 10 + +LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(0)) +{ +} + +LootTarget::LootTarget(LootTarget const& other) : guid(other.guid), asOfTime(other.asOfTime) +{ +} + +LootTarget& LootTarget::operator=(LootTarget const& other) +{ + if((void*)this == (void*)&other) + { + return *this; + } + + guid = other.guid; + asOfTime = other.asOfTime; + + return *this; +} + +bool LootTarget::operator< (const LootTarget& other) const +{ + return guid < other.guid; +} + +void LootTargetList::shrink(time_t fromTime) +{ + for (set::iterator i = begin(); i != end(); ) + { + if (i->asOfTime <= fromTime) + { + erase(i++); + } + else + { + ++i; + } + } +} + +LootObject::LootObject(Player* bot, ObjectGuid guid) + : guid(), skillId(SKILL_NONE), reqSkillValue(0), reqItem(0) +{ + Refresh(bot, guid); +} + +void LootObject::Refresh(Player* bot, ObjectGuid guid) +{ + skillId = SKILL_NONE; + reqSkillValue = 0; + reqItem = 0; + this->guid = ObjectGuid(); + + PlayerbotAI* ai = bot->GetPlayerbotAI(); + Creature *creature = ai->GetCreature(guid); + if (creature && creature->GetDeathState() == CORPSE) + { + if (creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) + { + this->guid = guid; + } + + if (creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE)) + { + skillId = creature->GetCreatureInfo()->GetRequiredLootSkill(); + uint32 targetLevel = creature->getLevel(); + reqSkillValue = targetLevel < 10 ? 2 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5; + if (ai->HasSkill((SkillType)skillId) && bot->GetSkillValue(skillId) >= reqSkillValue) + { + this->guid = guid; + } + } + + return; + } + + GameObject* go = ai->GetGameObject(guid); + if (go && go->isSpawned()) + { + uint32 lockId = go->GetGOInfo()->GetLockId(); + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if (!lockInfo) + { + return; + } + + for (int i = 0; i < 8; ++i) + { + switch (lockInfo->Type[i]) + { + case LOCK_KEY_ITEM: + if (lockInfo->Index[i] > 0) + { + reqItem = lockInfo->Index[i]; + this->guid = guid; + } + break; + case LOCK_KEY_SKILL: + if (SkillByLockType(LockType(lockInfo->Index[i])) > 0) + { + skillId = SkillByLockType(LockType(lockInfo->Index[i])); + reqSkillValue = max((uint32)2, lockInfo->Skill[i]); + this->guid = guid; + } + break; + case LOCK_KEY_NONE: + this->guid = guid; + break; + } + } + } +} + +WorldObject* LootObject::GetWorldObject(Player* bot) +{ + Refresh(bot, guid); + + PlayerbotAI* ai = bot->GetPlayerbotAI(); + + Creature *creature = ai->GetCreature(guid); + if (creature && creature->GetDeathState() == CORPSE) + { + return creature; + } + + GameObject* go = ai->GetGameObject(guid); + if (go && go->isSpawned()) + { + return go; + } + + return NULL; +} + +LootObject::LootObject(const LootObject& other) : guid(other.guid), skillId(other.skillId), reqSkillValue(other.reqSkillValue), reqItem(other.reqItem) +{ +} + +bool LootObject::IsLootPossible(Player* bot) +{ + if (IsEmpty() || !GetWorldObject(bot)) + { + return false; + } + + PlayerbotAI* ai = bot->GetPlayerbotAI(); + + if (reqItem && !bot->HasItemCount(reqItem, 1)) + { + return false; + } + + if (abs(GetWorldObject(bot)->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE) + { + return false; + } + + if (skillId == SKILL_NONE) + { + return true; + } + + if (skillId == SKILL_FISHING) + { + return false; + } + + if (!ai->HasSkill((SkillType)skillId)) + { + return false; + } + + if (!reqSkillValue) + { + return true; + } + + uint32 skillValue = uint32(bot->GetPureSkillValue(skillId)); + if (reqSkillValue > skillValue) + { + return false; + } + + if (skillId == SKILL_MINING && !bot->HasItemCount(2901, 1)) + { + return false; + } + + if (skillId == SKILL_SKINNING && !bot->HasItemCount(7005, 1)) + { + return false; + } + + return true; +} + +bool LootObjectStack::Add(ObjectGuid guid) +{ + if (!availableLoot.insert(guid).second) + { + return false; + } + + if (availableLoot.size() < MAX_LOOT_OBJECT_COUNT) + { + return true; + } + + vector ordered = OrderByDistance(); + for (size_t i = MAX_LOOT_OBJECT_COUNT; i < ordered.size(); i++) + { + Remove(ordered[i].guid); + } + + return true; +} + +void LootObjectStack::Remove(ObjectGuid guid) +{ + LootTargetList::iterator i = availableLoot.find(guid); + if (i != availableLoot.end()) + { + availableLoot.erase(i); + } +} + +void LootObjectStack::Clear() +{ + availableLoot.clear(); +} + +bool LootObjectStack::CanLoot(float maxDistance) +{ + vector ordered = OrderByDistance(maxDistance); + return !ordered.empty(); +} + +LootObject LootObjectStack::GetLoot(float maxDistance) +{ + vector ordered = OrderByDistance(maxDistance); + return ordered.empty() ? LootObject() : *ordered.begin(); +} + +vector LootObjectStack::OrderByDistance(float maxDistance) +{ + availableLoot.shrink(time(0) - 30); + + map sortedMap; + LootTargetList safeCopy(availableLoot); + for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++) + { + ObjectGuid guid = i->guid; + LootObject lootObject(bot, guid); + if (!lootObject.IsLootPossible(bot)) + { + continue; + } + + float distance = bot->GetDistance(lootObject.GetWorldObject(bot)); + if (!maxDistance || distance <= maxDistance) + { + sortedMap[distance] = lootObject; + } + } + + vector result; + for (map::iterator i = sortedMap.begin(); i != sortedMap.end(); i++) + { + result.push_back(i->second); + } + return result; +} + diff --git a/src/modules/Bots/playerbot/LootObjectStack.h b/src/modules/Bots/playerbot/LootObjectStack.h new file mode 100644 index 000000000..73a7a8d6a --- /dev/null +++ b/src/modules/Bots/playerbot/LootObjectStack.h @@ -0,0 +1,75 @@ +#pragma once + +using namespace std; + +namespace ai +{ + class LootStrategy + { + public: + LootStrategy() {} + virtual bool CanLoot(ItemPrototype const *proto, AiObjectContext *context) = 0; + virtual string GetName() = 0; + }; + + class LootObject + { + public: + LootObject() {} + LootObject(Player* bot, ObjectGuid guid); + LootObject(const LootObject& other); + + public: + bool IsEmpty() { return !guid; } + bool IsLootPossible(Player* bot); + void Refresh(Player* bot, ObjectGuid guid); + WorldObject* GetWorldObject(Player* bot); + ObjectGuid guid; + + uint32 skillId; + uint32 reqSkillValue; + uint32 reqItem; + }; + + class LootTarget + { + public: + LootTarget(ObjectGuid guid); + LootTarget(LootTarget const& other); + + public: + LootTarget& operator=(LootTarget const& other); + bool operator< (const LootTarget& other) const; + + public: + ObjectGuid guid; + time_t asOfTime; + }; + + class LootTargetList : public set + { + public: + void shrink(time_t fromTime); + }; + + class LootObjectStack + { + public: + LootObjectStack(Player* bot) : bot(bot) {} + + public: + bool Add(ObjectGuid guid); + void Remove(ObjectGuid guid); + void Clear(); + bool CanLoot(float maxDistance); + LootObject GetLoot(float maxDistance = 0); + + private: + vector OrderByDistance(float maxDistance = 0); + + private: + Player* bot; + LootTargetList availableLoot; + }; + +}; diff --git a/src/modules/Bots/playerbot/PlayerbotAI.cpp b/src/modules/Bots/playerbot/PlayerbotAI.cpp new file mode 100644 index 000000000..28622267f --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAI.cpp @@ -0,0 +1,1642 @@ +#include "../botpch.h" +#include "PlayerbotMgr.h" +#include "playerbot.h" + +#include "AiFactory.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" +#include "strategy/values/LastMovementValue.h" +#include "strategy/actions/LogLevelAction.h" +#include "strategy/values/LastSpellCastValue.h" +#include "LootObjectStack.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotAI.h" +#include "PlayerbotFactory.h" +#include "PlayerbotSecurity.h" +#include "Util.h" +#include "Group.h" +#include "Pet.h" +#include "SpellAuras.h" +#include "../ahbot/AhBot.h" +#include "GuildTaskMgr.h" +#include "PlayerbotDbStore.h" + +using namespace ai; +using namespace std; + +vector& split(const string &s, char delim, vector &elems); +vector split(const string &s, char delim); +char * strstri (string str1, string str2); +uint64 extractGuid(WorldPacket& packet); + +uint32 PlayerbotChatHandler::extractQuestId(string str) +{ + char* source = (char*)str.c_str(); + char* cId = ExtractKeyFromLink(&source,"Hquest"); + return cId ? atol(cId) : 0; +} + +void PacketHandlingHelper::AddHandler(uint16 opcode, const string handler) +{ + handlers[opcode] = handler; +} + +void PacketHandlingHelper::Handle(ExternalEventHelper &helper) +{ + while (!queue.empty()) + { + helper.HandlePacket(handlers, queue.top()); + queue.pop(); + } +} + +void PacketHandlingHelper::AddPacket(const WorldPacket& packet) +{ + if (handlers.find(packet.GetOpcode()) != handlers.end()) + { + queue.push(WorldPacket(packet)); + } +} + +PlayerbotAI::PlayerbotAI(Player* bot) : + PlayerbotAIBase(), chatHelper(this), chatFilter(this), security(bot), master(NULL), + accountId(sObjectMgr.GetPlayerAccountIdByGUID(bot->GetObjectGuid())) +{ + this->bot = bot; + + aiObjectContext = AiFactory::createAiObjectContext(bot, this); + + engines[BOT_STATE_COMBAT] = AiFactory::createCombatEngine(bot, this, aiObjectContext); + engines[BOT_STATE_NON_COMBAT] = AiFactory::createNonCombatEngine(bot, this, aiObjectContext); + engines[BOT_STATE_DEAD] = AiFactory::createDeadEngine(bot, this, aiObjectContext); + currentEngine = engines[BOT_STATE_NON_COMBAT]; + currentState = BOT_STATE_NON_COMBAT; + + masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object"); + masterIncomingPacketHandlers.AddHandler(CMSG_AREATRIGGER, "area trigger"); + masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object"); + masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll"); + masterIncomingPacketHandlers.AddHandler(CMSG_GOSSIP_HELLO, "gossip hello"); + masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_HELLO, "gossip hello"); + masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_COMPLETE_QUEST, "complete quest"); + masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_ACCEPT_QUEST, "accept quest"); + masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXI, "activate taxi"); + masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXIEXPRESS, "activate taxi"); + masterIncomingPacketHandlers.AddHandler(CMSG_MOVE_SPLINE_DONE, "taxi done"); + masterIncomingPacketHandlers.AddHandler(CMSG_GROUP_UNINVITE_GUID, "uninvite"); + masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share"); + masterIncomingPacketHandlers.AddHandler(CMSG_GUILD_INVITE, "guild invite"); + + botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_INVITE, "group invite"); + botOutgoingPacketHandlers.AddHandler(BUY_ERR_NOT_ENOUGHT_MONEY, "not enough money"); + botOutgoingPacketHandlers.AddHandler(BUY_ERR_REPUTATION_REQUIRE, "not enough reputation"); + botOutgoingPacketHandlers.AddHandler(SMSG_GROUP_SET_LEADER, "group set leader"); + botOutgoingPacketHandlers.AddHandler(SMSG_FORCE_RUN_SPEED_CHANGE, "check mount state"); + botOutgoingPacketHandlers.AddHandler(SMSG_RESURRECT_REQUEST, "resurrect request"); + botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "cannot equip"); + botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS, "trade status"); + botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response"); + botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest objective completed"); + botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result"); + botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command"); + botOutgoingPacketHandlers.AddHandler(SMSG_CAST_FAILED, "cast failed"); + botOutgoingPacketHandlers.AddHandler(SMSG_DUEL_REQUESTED, "duel requested"); + botOutgoingPacketHandlers.AddHandler(SMSG_INVENTORY_CHANGE_FAILURE, "inventory change failure"); + + masterOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command"); + masterOutgoingPacketHandlers.AddHandler(MSG_RAID_READY_CHECK, "ready check"); + masterOutgoingPacketHandlers.AddHandler(MSG_RAID_READY_CHECK_FINISHED, "ready check finished"); +} + +PlayerbotAI::~PlayerbotAI() +{ + for (int i = 0 ; i < BOT_STATE_MAX; i++) + { + if (engines[i]) + { + delete engines[i]; + } + } + + if (aiObjectContext) + { + delete aiObjectContext; + } +} + +void PlayerbotAI::UpdateAIInternal(uint32 elapsed) +{ + if (bot->IsBeingTeleported()) + { + return; + } + + ExternalEventHelper helper(aiObjectContext); + while (!chatCommands.empty()) + { + ChatCommandHolder holder = chatCommands.front(); + string command = holder.GetCommand(); + Player* owner = holder.GetOwner(); + if (!helper.ParseChatCommand(command, owner) && holder.GetType() == CHAT_MSG_WHISPER) + { + ostringstream out; out << "Unknown command " << command; + TellMaster(out); + helper.ParseChatCommand("help"); + } + chatCommands.pop(); + } + + botOutgoingPacketHandlers.Handle(helper); + masterIncomingPacketHandlers.Handle(helper); + masterOutgoingPacketHandlers.Handle(helper); + + DoNextAction(); +} + +void PlayerbotAI::HandleTeleportAck() +{ + bot->GetMotionMaster()->Clear(true); + bot->InterruptMoving(1); + if (bot->IsBeingTeleportedNear()) + { + WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, sizeof(PackedGuid) + 4 + 4); + p << bot->GetPackGUID(); + p << (uint32) 0; // supposed to be flags? not used currently + p << (uint32) time(0); // time - not currently used + bot->GetSession()->HandleMoveTeleportAckOpcode(p); + } + else if (bot->IsBeingTeleportedFar()) + { + bot->GetSession()->HandleMoveWorldportAckOpcode(); + SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown); + } +} + +void PlayerbotAI::Reset() +{ + if (bot->IsTaxiFlying()) + { + return; + } + + currentEngine = engines[BOT_STATE_NON_COMBAT]; + nextAICheckDelay = 0; + whispers.clear(); + + aiObjectContext->GetValue("old target")->Set(NULL); + aiObjectContext->GetValue("current target")->Set(NULL); + aiObjectContext->GetValue("loot target")->Set(LootObject()); + aiObjectContext->GetValue("lfg proposal")->Set(0); + + LastSpellCast & lastSpell = aiObjectContext->GetValue("last spell cast")->Get(); + lastSpell.Reset(); + + aiObjectContext->GetValue("last movement")->Get().Set(NULL); + aiObjectContext->GetValue("last area trigger")->Get().Set(NULL); + aiObjectContext->GetValue("last taxi")->Get().Set(NULL); + + bot->GetMotionMaster()->Clear(); + bot->m_taxi.ClearTaxiDestinations(); + InterruptSpell(); + + for (int i = 0 ; i < BOT_STATE_MAX; i++) + { + engines[i]->Init(); + } +} + +map chatMap; + +void PlayerbotAI::HandleCommand(uint32 type, const string& text, Player& fromPlayer) +{ + if (!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, type != CHAT_MSG_WHISPER, &fromPlayer)) + { + return; + } + + if (type == CHAT_MSG_ADDON) + { + return; + } + + string filtered = text; + if (!sPlayerbotAIConfig.commandPrefix.empty()) + { + if (!strncmp(filtered.c_str(), sPlayerbotAIConfig.commandPrefix.c_str(), sPlayerbotAIConfig.commandPrefix.size())) + { + return; + } + + filtered = filtered.substr(sPlayerbotAIConfig.commandPrefix.size()); + } + + if (chatMap.empty()) + { + chatMap["#w "] = CHAT_MSG_WHISPER; + chatMap["#p "] = CHAT_MSG_PARTY; + chatMap["#r "] = CHAT_MSG_RAID; + chatMap["#a "] = CHAT_MSG_ADDON; + chatMap["#g "] = CHAT_MSG_GUILD; + } + currentChat = pair(CHAT_MSG_WHISPER, 0); + for (map::iterator i = chatMap.begin(); i != chatMap.end(); ++i) + { + if (strncmp(filtered.c_str(), i->first.c_str(), i->first.size())) + { + filtered = filtered.substr(3); + currentChat = pair(i->second, time(0) + 2); + break; + } + } + + filtered = chatFilter.Filter(trim((string&)filtered)); + if (filtered.empty()) + { + return; + } + + if (filtered.substr(0, 6) == "debug ") + { + string response = HandleRemoteCommand(filtered.substr(6)); + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_ADDON, response.c_str(), LANG_ADDON, + CHAT_TAG_NONE, bot->GetObjectGuid(), bot->GetName()); + fromPlayer.GetSession()->SendPacket(&data); + return; + } + + if (!strncmp(filtered.c_str(), "who", 3) && !GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, &fromPlayer)) + { + return; + } + + if (type == CHAT_MSG_RAID_WARNING && filtered.find(bot->GetName()) != string::npos && filtered.find("award") == string::npos) + { + ChatCommandHolder cmd("warning", &fromPlayer, type); + chatCommands.push(cmd); + return; + } + + if (filtered.size() > 2 && filtered.substr(0, 2) == "d " || filtered.size() > 3 && filtered.substr(0, 3) == "do ") + { + std::string action = filtered.substr(filtered.find(" ") + 1); + DoSpecificAction(action); + } + else if (filtered == "reset") + { + Reset(); + } + else + { + ChatCommandHolder cmd(filtered, &fromPlayer, type); + chatCommands.push(cmd); + } +} + +void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) +{ + switch (packet.GetOpcode()) + { + case SMSG_SPELL_FAILURE: + { + WorldPacket p(packet); + p.rpos(0); + ObjectGuid casterGuid; + p >> casterGuid.ReadAsPacked(); + if (casterGuid != bot->GetObjectGuid()) + { + return; + } + + uint32 spellId; + p >> spellId; + SpellInterrupted(spellId); + return; + } + case SMSG_SPELL_DELAYED: + { + WorldPacket p(packet); + p.rpos(0); + ObjectGuid casterGuid; + p >> casterGuid.ReadAsPacked(); + + if (casterGuid != bot->GetObjectGuid()) + { + return; + } + + uint32 delaytime; + p >> delaytime; + if (delaytime <= 1000) + { + IncreaseNextCheckDelay(delaytime); + } + return; + } + default: + botOutgoingPacketHandlers.AddPacket(packet); + } +} + +void PlayerbotAI::SpellInterrupted(uint32 spellid) +{ + LastSpellCast& lastSpell = aiObjectContext->GetValue("last spell cast")->Get(); + if (!spellid || lastSpell.id != spellid) + { + return; + } + + lastSpell.Reset(); + + time_t now = time(0); + if (now <= lastSpell.time) + { + return; + } + + uint32 castTimeSpent = 1000 * (now - lastSpell.time); + + int32 globalCooldown = CalculateGlobalCooldown(lastSpell.id); + if (castTimeSpent < globalCooldown) + { + SetNextCheckDelay(globalCooldown - castTimeSpent); + } + else + { + SetNextCheckDelay(sPlayerbotAIConfig.reactDelay); + } + + lastSpell.id = 0; +} + +int32 PlayerbotAI::CalculateGlobalCooldown(uint32 spellid) +{ + if (!spellid) + { + return 0; + } + + if (bot->HasSpellCooldown(spellid)) + { + return sPlayerbotAIConfig.globalCoolDown; + } + + return sPlayerbotAIConfig.reactDelay; +} + +void PlayerbotAI::HandleMasterIncomingPacket(const WorldPacket& packet) +{ + masterIncomingPacketHandlers.AddPacket(packet); +} + +void PlayerbotAI::HandleMasterOutgoingPacket(const WorldPacket& packet) +{ + masterOutgoingPacketHandlers.AddPacket(packet); +} + +void PlayerbotAI::ChangeEngine(BotState type) +{ + Engine* engine = engines[type]; + + if (currentEngine != engine) + { + currentEngine = engine; + currentState = type; + ReInitCurrentEngine(); + + switch (type) + { + case BOT_STATE_COMBAT: + sLog.outDebug( "=== %s COMBAT ===", bot->GetName()); + break; + case BOT_STATE_NON_COMBAT: + sLog.outDebug( "=== %s NON-COMBAT ===", bot->GetName()); + break; + case BOT_STATE_DEAD: + sLog.outDebug( "=== %s DEAD ===", bot->GetName()); + break; + } + } +} + +void PlayerbotAI::DoNextAction() +{ + if (bot->IsBeingTeleported() || (GetMaster() && GetMaster()->IsBeingTeleported())) + { + return; + } + + currentEngine->DoNextAction(NULL); + + if (currentEngine != engines[BOT_STATE_DEAD] && !bot->IsAlive()) + { + ChangeEngine(BOT_STATE_DEAD); + } + + if (currentEngine == engines[BOT_STATE_DEAD] && bot->IsAlive()) + { + ChangeEngine(BOT_STATE_NON_COMBAT); + } + + Group *group = bot->GetGroup(); + if (!master && group) + { + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->getSource(); + PlayerbotAI* ai = bot->GetPlayerbotAI(); + if (member && member->IsInWorld() && !member->GetPlayerbotAI() && (!master || master->GetPlayerbotAI())) + { + ai->SetMaster(member); + ai->ResetStrategies(); + ai->TellMaster("Hello"); + break; + } + } + } +} + +void PlayerbotAI::ReInitCurrentEngine() +{ + InterruptSpell(); + currentEngine->Init(); +} + +void PlayerbotAI::ChangeStrategy(string names, BotState type) +{ + Engine* e = engines[type]; + if (!e) + { + return; + } + + e->ChangeStrategy(names); +} + +void PlayerbotAI::ClearStrategies(BotState type) +{ + Engine* e = engines[type]; + if (!e) + { + return; + } + + e->removeAllStrategies(); +} + +list PlayerbotAI::GetStrategies(BotState type) +{ + Engine* e = engines[type]; + if (!e) + { + return list(); + } + + return e->GetStrategies(); +} + +void PlayerbotAI::DoSpecificAction(string name) +{ + for (int i = 0 ; i < BOT_STATE_MAX; i++) + { + ostringstream out; + ActionResult res = engines[i]->ExecuteAction(name); + switch (res) + { + case ACTION_RESULT_UNKNOWN: + continue; + case ACTION_RESULT_OK: + out << name << ": done"; + TellMaster(out); + PlaySound(TEXTEMOTE_NOD); + return; + case ACTION_RESULT_IMPOSSIBLE: + out << name << ": impossible"; + TellMaster(out); + PlaySound(TEXTEMOTE_NO); + return; + case ACTION_RESULT_USELESS: + out << name << ": useless"; + TellMaster(out); + PlaySound(TEXTEMOTE_NO); + return; + case ACTION_RESULT_FAILED: + out << name << ": failed"; + TellMaster(out); + return; + } + } + ostringstream out; + out << name << ": unknown action"; + TellMaster(out); +} + +bool PlayerbotAI::PlaySound(uint32 emote) +{ + //if (EmotesTextSoundEntry const* soundEntry = FindTextSoundEmoteFor(emote, bot->getRace(), bot->getGender())) + //{ + // bot->PlayDistanceSound(soundEntry->SoundId); + // return true; + //} + + return false; +} + +bool PlayerbotAI::ContainsStrategy(StrategyType type) +{ + for (int i = 0 ; i < BOT_STATE_MAX; i++) + { + if (engines[i]->ContainsStrategy(type)) + { + return true; + } + } + return false; +} + +bool PlayerbotAI::HasStrategy(string name, BotState type) +{ + return engines[type]->HasStrategy(name); +} + +void PlayerbotAI::ResetStrategies() +{ + for (int i = 0 ; i < BOT_STATE_MAX; i++) + { + engines[i]->removeAllStrategies(); + } + + AiFactory::AddDefaultCombatStrategies(bot, this, engines[BOT_STATE_COMBAT]); + AiFactory::AddDefaultNonCombatStrategies(bot, this, engines[BOT_STATE_NON_COMBAT]); + AiFactory::AddDefaultDeadStrategies(bot, this, engines[BOT_STATE_DEAD]); + sPlayerbotDbStore.Load(this); +} + +bool PlayerbotAI::IsRanged(Player* player) +{ + PlayerbotAI* botAi = player->GetPlayerbotAI(); + if (botAi) + { + return botAi->ContainsStrategy(STRATEGY_TYPE_RANGED); + } + + switch (player->getClass()) + { + case CLASS_PALADIN: + case CLASS_WARRIOR: + case CLASS_ROGUE: + return false; + case CLASS_DRUID: + return !HasAnyAuraOf(player, "cat form", "bear form", "dire bear form", NULL); + } + return true; +} + +bool PlayerbotAI::IsTank(Player* player) +{ + PlayerbotAI* botAi = player->GetPlayerbotAI(); + if (botAi) + { + return botAi->ContainsStrategy(STRATEGY_TYPE_TANK); + } + + switch (player->getClass()) + { + case CLASS_PALADIN: + case CLASS_WARRIOR: + return true; + case CLASS_DRUID: + return HasAnyAuraOf(player, "bear form", "dire bear form", NULL); + } + return false; +} + +bool PlayerbotAI::IsHeal(Player* player) +{ + PlayerbotAI* botAi = player->GetPlayerbotAI(); + if (botAi) + { + return botAi->ContainsStrategy(STRATEGY_TYPE_HEAL); + } + + switch (player->getClass()) + { + case CLASS_PRIEST: + return true; + case CLASS_DRUID: + return HasAnyAuraOf(player, "tree of life form", NULL); + } + return false; +} + + + +namespace MaNGOS +{ + + class UnitByGuidInRangeCheck + { + public: + UnitByGuidInRangeCheck(WorldObject const* obj, ObjectGuid guid, float range) : i_obj(obj), i_range(range), i_guid(guid) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(Unit* u) + { + return u->GetObjectGuid() == i_guid && i_obj->IsWithinDistInMap(u, i_range); + } + private: + WorldObject const* i_obj; + float i_range; + ObjectGuid i_guid; + }; + + class GameObjectByGuidInRangeCheck + { + public: + GameObjectByGuidInRangeCheck(WorldObject const* obj, ObjectGuid guid, float range) : i_obj(obj), i_range(range), i_guid(guid) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(GameObject* u) + { + if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo() && u->GetObjectGuid() == i_guid) + { + return true; + } + + return false; + } + private: + WorldObject const* i_obj; + float i_range; + ObjectGuid i_guid; + }; + +}; + + +Unit* PlayerbotAI::GetUnit(ObjectGuid guid) +{ + if (!guid) + { + return NULL; + } + + Map* map = bot->GetMap(); + if (!map) + { + return NULL; + } + + return sObjectAccessor.GetUnit(*bot, guid); +} + + +Creature* PlayerbotAI::GetCreature(ObjectGuid guid) +{ + if (!guid) + { + return NULL; + } + + Map* map = bot->GetMap(); + if (!map) + { + return NULL; + } + + return map->GetCreature(guid); +} + +GameObject* PlayerbotAI::GetGameObject(ObjectGuid guid) +{ + if (!guid) + { + return NULL; + } + + Map* map = bot->GetMap(); + if (!map) + { + return NULL; + } + + return map->GetGameObject(guid); +} + +bool PlayerbotAI::TellMasterNoFacing(string text, PlayerbotSecurityLevel securityLevel) +{ + Player* master = GetMaster(); + if (!master || master->IsBeingTeleported()) + { + return false; + } + + if (!GetSecurity()->CheckLevelFor(securityLevel, true, master)) + { + return false; + } + + if (sPlayerbotAIConfig.whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr.IsRandomBot(bot) && + master->GetSession()->GetSecurity() < SEC_GAMEMASTER && + (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.whisperDistance)) + return false; + + time_t lastSaid = whispers[text]; + if (!lastSaid || (time(0) - lastSaid) >= sPlayerbotAIConfig.repeatDelay / 1000) + { + whispers[text] = time(0); + + ChatMsg type = CHAT_MSG_WHISPER; + if (currentChat.second - time(0) >= 1) + { + type = currentChat.first; + } + + WorldPacket data; + ChatHandler::BuildChatPacket(data, + type == CHAT_MSG_ADDON ? CHAT_MSG_PARTY : type, + text.c_str(), + type == CHAT_MSG_ADDON ? LANG_ADDON : LANG_UNIVERSAL, + CHAT_TAG_NONE, bot->GetObjectGuid(), bot->GetName()); + master->GetSession()->SendPacket(&data); + } + return true; +} + +bool PlayerbotAI::TellMaster(const string text, PlayerbotSecurityLevel securityLevel) +{ + if (!TellMasterNoFacing(text, securityLevel)) + { + return false; + } + + if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() && !bot->IsTaxiFlying()) + { + if (!bot->IsInFront(master, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT)) + { + bot->SetFacingTo(bot->GetAngle(master)); + } + + bot->HandleEmoteCommand(EMOTE_ONESHOT_TALK); + } + + return true; +} + +bool IsRealAura(Player* bot, Aura const* aura, Unit* unit) +{ + if (!aura) + { + return false; + } + + if (!unit->IsHostileTo(bot)) + { + return true; + } + + uint32 stacks = aura->GetStackAmount(); + if (stacks >= aura->GetSpellProto()->StackAmount) + { + return true; + } + + if (aura->GetCaster() == bot || IsPositiveSpell(aura->GetSpellProto()) || aura->IsAreaAura()) + { + return true; + } + + return false; +} + +bool PlayerbotAI::HasAura(string name, Unit* unit) +{ + if (!unit) + { + return false; + } + + wstring wnamepart; + if (!Utf8toWStr(name, wnamepart)) + { + return 0; + } + + wstrToLower(wnamepart); + + for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; auraType++) + { + Unit::AuraList const& auras = unit->GetAurasByType((AuraType)auraType); + for (Unit::AuraList::const_iterator i = auras.begin(); i != auras.end(); i++) + { + Aura* aura = *i; + if (!aura) + { + continue; + } + + const string auraName = aura->GetSpellProto()->SpellName[0]; + if (auraName.empty() || auraName.length() != wnamepart.length() || !Utf8FitTo(auraName, wnamepart)) + { + continue; + } + + if (IsRealAura(bot, aura, unit)) + { + return true; + } + } + } + + return false; +} + +bool PlayerbotAI::HasAura(uint32 spellId, const Unit* unit) +{ + if (!spellId || !unit) + { + return false; + } + + for (uint32 effect = EFFECT_INDEX_0; effect <= EFFECT_INDEX_2; effect++) + { + Aura* aura = const_cast(unit)->GetAura(spellId, (SpellEffectIndex)effect); + + if (IsRealAura(bot, aura, const_cast(unit))) + { + return true; + } + } + + return false; +} + + +bool PlayerbotAI::HasAnyAuraOf(Unit* player, ...) +{ + if (!player) + { + return false; + } + + va_list vl; + va_start(vl, player); + + const char* cur; + do { + cur = va_arg(vl, const char*); + if (cur && HasAura(cur, player)) { + { + va_end(vl); + } + return true; + } + } + while (cur); + + va_end(vl); + return false; +} + +bool PlayerbotAI::CanCastSpell(string name, Unit* target) +{ + return CanCastSpell(aiObjectContext->GetValue("spell id", name)->Get(), target); +} + +bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell) +{ + if (!spellid) + { + return false; + } + + if (!target) + { + target = bot; + } + + Pet* pet = bot->GetPet(); + if (pet && pet->HasSpell(spellid)) + { + return true; + } + + if (checkHasSpell && !bot->HasSpell(spellid)) + { + return false; + } + + if (bot->HasSpellCooldown(spellid)) + { + return false; + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid); + if (!spellInfo) + { + return false; + } + + bool positiveSpell = IsPositiveSpell(spellInfo); + if (positiveSpell && bot->IsHostileTo(target)) + { + return false; + } + + if (!positiveSpell && bot->IsFriendlyTo(target)) + { + return false; + } + + if (target->IsImmuneToSpell(spellInfo, false)) + { + return false; + } + + if (bot != target && bot->GetDistance(target) > sPlayerbotAIConfig.sightDistance) + { + return false; + } + + ObjectGuid oldSel = bot->GetSelectionGuid(); + bot->SetSelectionGuid(target->GetObjectGuid()); + Spell *spell = new Spell(bot, spellInfo, false); + + spell->m_targets.setUnitTarget(target); + spell->m_CastItem = aiObjectContext->GetValue("item for spell", spellid)->Get(); + spell->m_targets.setItemTarget(spell->m_CastItem); + SpellCastResult result = spell->CheckCast(false); + delete spell; + if (oldSel) + { + bot->SetSelectionGuid(oldSel); + } + + switch (result) + { + case SPELL_FAILED_NOT_INFRONT: + case SPELL_FAILED_NOT_STANDING: + case SPELL_FAILED_UNIT_NOT_INFRONT: + case SPELL_FAILED_MOVING: + case SPELL_FAILED_TRY_AGAIN: + case SPELL_FAILED_BAD_IMPLICIT_TARGETS: + case SPELL_FAILED_BAD_TARGETS: + case SPELL_CAST_OK: + return true; + default: + return false; + } +} + + +bool PlayerbotAI::CastSpell(string name, Unit* target) +{ + bool result = CastSpell(aiObjectContext->GetValue("spell id", name)->Get(), target); + if (result) + { + aiObjectContext->GetValue("last spell cast time", name)->Set(time(0)); + } + + return result; +} + +bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target) +{ + if (!spellId) + { + return false; + } + + if (!target) + { + target = bot; + } + + Pet* pet = bot->GetPet(); + SpellEntry const *pSpellInfo = sSpellStore.LookupEntry(spellId); + if (pet && pet->HasSpell(spellId)) + { + bool autocast = false; + for(AutoSpellList::iterator i = pet->m_autospells.begin(); i != pet->m_autospells.end(); ++i) + { + if (*i == spellId) + { + autocast = true; + break; + } + } + + pet->ToggleAutocast(spellId, !autocast); + ostringstream out; + out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for "; + out << chatHelper.formatSpell(pSpellInfo); + TellMaster(out); + return true; + } + + aiObjectContext->GetValue("last movement")->Get().Set(NULL); + + MotionMaster &mm = *bot->GetMotionMaster(); + + if (bot->IsFlying()) + { + return false; + } + + bot->clearUnitState(UNIT_STAT_CHASE); + bot->clearUnitState(UNIT_STAT_FOLLOW); + + ObjectGuid oldSel = bot->GetSelectionGuid(); + bot->SetSelectionGuid(target->GetObjectGuid()); + + Spell *spell = new Spell(bot, pSpellInfo, false); + if (bot->isMoving() && spell->GetCastTime()) + { + delete spell; + spell->cancel(); + return false; + } + + SpellCastTargets targets; + WorldObject* faceTo = target; + + if (pSpellInfo->Targets & TARGET_FLAG_ITEM) + { + spell->m_CastItem = aiObjectContext->GetValue("item for spell", spellId)->Get(); + targets.setItemTarget(spell->m_CastItem); + } + else if (pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION) + { + WorldLocation aoe = aiObjectContext->GetValue("aoe position")->Get(); + targets.setDestination(aoe.coord_x, aoe.coord_y, aoe.coord_z); + } + else if (pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION) + { + targets.setDestination(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + } + else + { + targets.setUnitTarget(target); + } + + + if (pSpellInfo->Effect[0] == SPELL_EFFECT_OPEN_LOCK || + pSpellInfo->Effect[0] == SPELL_EFFECT_SKINNING) + { + LootObject loot = *aiObjectContext->GetValue("loot target"); + if (!loot.IsLootPossible(bot)) + { + spell->cancel(); + delete spell; + return false; + } + + GameObject* go = GetGameObject(loot.guid); + if (go && go->isSpawned()) + { + WorldPacket* const packetgouse = new WorldPacket(CMSG_GAMEOBJ_USE, 8); + *packetgouse << loot.guid; + bot->GetSession()->QueuePacket(packetgouse); + targets.setGOTarget(go); + faceTo = go; + } + else + { + Unit* creature = GetUnit(loot.guid); + if (creature) + { + targets.setUnitTarget(creature); + faceTo = creature; + } + } + } + + + if (!bot->IsInFront(faceTo, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT) && !bot->IsTaxiFlying()) + { + bot->SetFacingTo(bot->GetAngle(faceTo)); + spell->cancel(); + delete spell; + SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown); + return false; + } + + spell->prepare(&targets); + WaitForSpellCast(spell); + aiObjectContext->GetValue("last spell cast")->Get().Set(spellId, target->GetObjectGuid(), time(0)); + + if (oldSel) + { + bot->SetSelectionGuid(oldSel); + } + + return true; +} + +void PlayerbotAI::WaitForSpellCast(Spell *spell) +{ + const SpellEntry* const pSpellInfo = spell->m_spellInfo; + + float castTime = spell->GetCastTime(); + if (IsChanneledSpell(pSpellInfo)) + { + int32 duration = GetSpellDuration(pSpellInfo); + if (duration > 0) + { + castTime += duration; + } + } + + castTime = ceil(castTime); + + uint32 globalCooldown = CalculateGlobalCooldown(pSpellInfo->Id); + if (castTime < globalCooldown) + { + castTime = globalCooldown; + } + + SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay); +} + +void PlayerbotAI::InterruptSpell() +{ + for (int type = CURRENT_MELEE_SPELL; type < CURRENT_CHANNELED_SPELL; type++) + { + Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type); + if (!spell) + { + continue; + } + + if (IsPositiveSpell(spell->m_spellInfo)) + { + continue; + } + + bot->InterruptSpell((CurrentSpellTypes)type); + + WorldPacket data(SMSG_SPELL_FAILURE, 8 + 1 + 4 + 1); + data.appendPackGUID(bot->GetObjectGuid().GetRawValue()); + data << uint8(1); + data << uint32(spell->m_spellInfo->Id); + data << uint8(0); + bot->SendMessageToSet(&data, true); + + data.Initialize(SMSG_SPELL_FAILED_OTHER, 8 + 1 + 4 + 1); + data.appendPackGUID(bot->GetObjectGuid().GetRawValue()); + data << uint8(1); + data << uint32(spell->m_spellInfo->Id); + data << uint8(0); + bot->SendMessageToSet(&data, true); + + SpellInterrupted(spell->m_spellInfo->Id); + } +} + + +void PlayerbotAI::RemoveAura(string name) +{ + uint32 spellid = aiObjectContext->GetValue("spell id", name)->Get(); + if (spellid && HasAura(spellid, bot)) + { + bot->RemoveAurasDueToSpell(spellid); + } +} + +bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, string spell) +{ + uint32 spellid = aiObjectContext->GetValue("spell id", spell)->Get(); + if (!spellid || !target->IsNonMeleeSpellCasted(true)) + { + return false; + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid); + if (!spellInfo) + { + return false; + } + + if (target->IsImmuneToSpell(spellInfo, false)) + { + return false; + } + + for (int32 i = EFFECT_INDEX_0; i <= EFFECT_INDEX_2; i++) + { + if ((spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_INTERRUPT) && spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) + { + return true; + } + + if ((spellInfo->Effect[i] == SPELL_EFFECT_INTERRUPT_CAST) && + !target->IsImmuneToSpellEffect(spellInfo, (SpellEffectIndex)i, true)) + return true; + } + + return false; +} + +bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType) +{ + for (uint32 type = SPELL_AURA_NONE; type < TOTAL_AURAS; ++type) + { + Unit::AuraList const& auras = target->GetAurasByType((AuraType)type); + for (Unit::AuraList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) + { + const Aura* aura = *itr; + const SpellEntry* entry = aura->GetSpellProto(); + uint32 spellId = entry->Id; + + bool isPositiveSpell = IsPositiveSpell(spellId); + if (isPositiveSpell && bot->IsFriendlyTo(target)) + { + continue; + } + + if (!isPositiveSpell && bot->IsHostileTo(target)) + { + continue; + } + + if (sPlayerbotAIConfig.dispelAuraDuration && aura->GetAuraDuration() && aura->GetAuraDuration() < (int32)sPlayerbotAIConfig.dispelAuraDuration) + { + return false; + } + + if (canDispel(entry, dispelType)) + { + return true; + } + } + } + return false; +} + + + +#ifndef WIN32 +inline int strcmpi(const char* s1, const char* s2) +{ + for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2); + { + return *s1 - *s2; + } +} +#endif + +bool PlayerbotAI::canDispel(const SpellEntry* entry, uint32 dispelType) +{ + if (entry->Dispel != dispelType) + { + return false; + } + + return !entry->SpellName[0] || + (strcmpi((const char*)entry->SpellName[0], "demon skin") && + strcmpi((const char*)entry->SpellName[0], "mage armor") && + strcmpi((const char*)entry->SpellName[0], "frost armor") && + strcmpi((const char*)entry->SpellName[0], "wavering will") && + strcmpi((const char*)entry->SpellName[0], "chilled") && + strcmpi((const char*)entry->SpellName[0], "ice armor")); +} + +bool IsAlliance(uint8 race) +{ + return race == RACE_HUMAN || race == RACE_DWARF || race == RACE_NIGHTELF || race == RACE_DRAENEI || + race == RACE_GNOME; +} + +bool PlayerbotAI::IsOpposing(Player* player) +{ + return IsOpposing(player->getRace(), bot->getRace()); +} + +bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2) +{ + return (IsAlliance(race1) && !IsAlliance(race2)) || (!IsAlliance(race1) && IsAlliance(race2)); +} + +void PlayerbotAI::RemoveShapeshift() +{ + RemoveAura("bear form"); + RemoveAura("dire bear form"); + RemoveAura("moonkin form"); + RemoveAura("travel form"); + RemoveAura("cat form"); + RemoveAura("flight form"); + RemoveAura("swift flight form"); + RemoveAura("aquatic form"); + RemoveAura("ghost wolf"); + RemoveAura("tree of life"); +} + +uint32 PlayerbotAI::GetEquipGearScore(Player* player, bool withBags, bool withBank) +{ + std::vector gearScore(EQUIPMENT_SLOT_END); + uint32 twoHandScore = 0; + + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + _fillGearScoreData(player, item, &gearScore, twoHandScore); + } + } + + if (withBags) + { + // check inventory + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + _fillGearScoreData(player, item, &gearScore, twoHandScore); + } + } + + // check bags + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* item2 = pBag->GetItemByPos(j)) + { + _fillGearScoreData(player, item2, &gearScore, twoHandScore); + } + } + } + } + } + + if (withBank) + { + for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + _fillGearScoreData(player, item, &gearScore, twoHandScore); + } + } + + for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (item->IsBag()) + { + Bag* bag = (Bag*)item; + for (uint8 j = 0; j < bag->GetBagSize(); ++j) + { + if (Item* item2 = bag->GetItemByPos(j)) + { + _fillGearScoreData(player, item2, &gearScore, twoHandScore); + } + } + } + } + } + } + + uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots + uint32 sum = 0; + + // check if 2h hand is higher level than main hand + off hand + if (gearScore[EQUIPMENT_SLOT_MAINHAND] + gearScore[EQUIPMENT_SLOT_OFFHAND] < twoHandScore * 2) + { + gearScore[EQUIPMENT_SLOT_OFFHAND] = 0; // off hand is ignored in calculations if 2h weapon has higher score + --count; + gearScore[EQUIPMENT_SLOT_MAINHAND] = twoHandScore; + } + + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + sum += gearScore[i]; + } + + if (count) + { + uint32 res = uint32(sum / count); + return res; + } + else + { + return 0; + } +} + +void PlayerbotAI::_fillGearScoreData(Player *player, Item* item, std::vector* gearScore, uint32& twoHandScore) +{ + if (!item) + { + return; + } + + if (player->CanUseItem(item->GetProto()) != EQUIP_ERR_OK) + { + return; + } + + uint8 type = item->GetProto()->InventoryType; + uint32 level = item->GetProto()->ItemLevel; + + switch (type) + { + case INVTYPE_2HWEAPON: + twoHandScore = std::max(twoHandScore, level); + break; + case INVTYPE_WEAPON: + case INVTYPE_WEAPONMAINHAND: + (*gearScore)[SLOT_MAIN_HAND] = std::max((*gearScore)[SLOT_MAIN_HAND], level); + break; + case INVTYPE_SHIELD: + case INVTYPE_WEAPONOFFHAND: + (*gearScore)[EQUIPMENT_SLOT_OFFHAND] = std::max((*gearScore)[EQUIPMENT_SLOT_OFFHAND], level); + break; + case INVTYPE_THROWN: + case INVTYPE_RANGEDRIGHT: + case INVTYPE_RANGED: + case INVTYPE_QUIVER: + case INVTYPE_RELIC: + (*gearScore)[EQUIPMENT_SLOT_RANGED] = std::max((*gearScore)[EQUIPMENT_SLOT_RANGED], level); + break; + case INVTYPE_HEAD: + (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level); + break; + case INVTYPE_NECK: + (*gearScore)[EQUIPMENT_SLOT_NECK] = std::max((*gearScore)[EQUIPMENT_SLOT_NECK], level); + break; + case INVTYPE_SHOULDERS: + (*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level); + break; + case INVTYPE_BODY: + (*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level); + break; + case INVTYPE_CHEST: + (*gearScore)[EQUIPMENT_SLOT_CHEST] = std::max((*gearScore)[EQUIPMENT_SLOT_CHEST], level); + break; + case INVTYPE_WAIST: + (*gearScore)[EQUIPMENT_SLOT_WAIST] = std::max((*gearScore)[EQUIPMENT_SLOT_WAIST], level); + break; + case INVTYPE_LEGS: + (*gearScore)[EQUIPMENT_SLOT_LEGS] = std::max((*gearScore)[EQUIPMENT_SLOT_LEGS], level); + break; + case INVTYPE_FEET: + (*gearScore)[EQUIPMENT_SLOT_FEET] = std::max((*gearScore)[EQUIPMENT_SLOT_FEET], level); + break; + case INVTYPE_WRISTS: + (*gearScore)[EQUIPMENT_SLOT_WRISTS] = std::max((*gearScore)[EQUIPMENT_SLOT_WRISTS], level); + break; + case INVTYPE_HANDS: + (*gearScore)[EQUIPMENT_SLOT_HEAD] = std::max((*gearScore)[EQUIPMENT_SLOT_HEAD], level); + break; + // equipped gear score check uses both rings and trinkets for calculation, assume that for bags/banks it is the same + // with keeping second highest score at second slot + case INVTYPE_FINGER: + { + if ((*gearScore)[EQUIPMENT_SLOT_FINGER1] < level) + { + (*gearScore)[EQUIPMENT_SLOT_FINGER2] = (*gearScore)[EQUIPMENT_SLOT_FINGER1]; + (*gearScore)[EQUIPMENT_SLOT_FINGER1] = level; + } + else if ((*gearScore)[EQUIPMENT_SLOT_FINGER2] < level) + { + (*gearScore)[EQUIPMENT_SLOT_FINGER2] = level; + } + break; + } + case INVTYPE_TRINKET: + { + if ((*gearScore)[EQUIPMENT_SLOT_TRINKET1] < level) + { + (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = (*gearScore)[EQUIPMENT_SLOT_TRINKET1]; + (*gearScore)[EQUIPMENT_SLOT_TRINKET1] = level; + } + else if ((*gearScore)[EQUIPMENT_SLOT_TRINKET2] < level) + { + (*gearScore)[EQUIPMENT_SLOT_TRINKET2] = level; + } + break; + } + case INVTYPE_CLOAK: + (*gearScore)[EQUIPMENT_SLOT_BACK] = std::max((*gearScore)[EQUIPMENT_SLOT_BACK], level); + break; + default: + break; + } +} + +string PlayerbotAI::HandleRemoteCommand(const string command) +{ + if (command == "state") + { + switch (currentState) + { + case BOT_STATE_COMBAT: + return "combat"; + case BOT_STATE_DEAD: + return "dead"; + case BOT_STATE_NON_COMBAT: + return "non-combat"; + default: + return "unknown"; + } + } + else if (command == "position") + { + ostringstream out; out << bot->GetPositionX() << " " << bot->GetPositionY() << " " << bot->GetPositionZ() << " " << bot->GetMapId() << " " << bot->GetOrientation(); + uint32 area = bot->GetAreaId(); + if (const AreaTableEntry* entry = sAreaStore.LookupEntry(area)) + { + out << " |" << entry->area_name[0] << "|"; + } + return out.str(); + } + else if (command == "tpos") + { + Unit* target = *GetAiObjectContext()->GetValue("current target"); + if (!target) { + { + return ""; + } + } + + ostringstream out; out << target->GetPositionX() << " " << target->GetPositionY() << " " << target->GetPositionZ() << " " << target->GetMapId() << " " << target->GetOrientation(); + return out.str(); + } + else if (command == "movement") + { + LastMovement& data = *GetAiObjectContext()->GetValue("last movement"); + ostringstream out; out << data.lastMoveToX << " " << data.lastMoveToY << " " << data.lastMoveToZ << " " << bot->GetMapId() << " " << data.lastMoveToOri; + return out.str(); + } + else if (command == "target") + { + Unit* target = *GetAiObjectContext()->GetValue("current target"); + if (!target) { + { + return ""; + } + } + + return target->GetName(); + } + else if (command == "hp") + { + int pct = (int)((static_cast (bot->GetHealth()) / bot->GetMaxHealth()) * 100); + ostringstream out; out << pct << "%"; + + Unit* target = *GetAiObjectContext()->GetValue("current target"); + if (!target) { + { + return out.str(); + } + } + + pct = (int)((static_cast (target->GetHealth()) / target->GetMaxHealth()) * 100); + out << " / " << pct << "%"; + return out.str(); + } + else if (command == "strategy") + { + return currentEngine->ListStrategies(); + } + else if (command == "action") + { + return currentEngine->GetLastAction(); + } + else if (command == "values") + { + return GetAiObjectContext()->FormatValues(); + } + ostringstream out; out << "invalid command: " << command; + return out.str(); +} + +bool PlayerbotAI::HasSkill(SkillType skill) +{ + return bot->HasSkill(skill) && bot->GetBaseSkillValue(skill) > 1; +} + +bool ChatHandler::HandlePlayerbotCommand(char* args) +{ + return PlayerbotMgr::HandlePlayerbotMgrCommand(this, args); +} + +bool ChatHandler::HandleRandomPlayerbotCommand(char* args) +{ + return RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(args); +} + +bool ChatHandler::HandleAhBotCommand(char* args) +{ + return ahbot::AhBot::HandleAhBotCommand(this, args); +} + +bool ChatHandler::HandleGuildTaskCommand(char* args) +{ + return GuildTaskMgr::HandleConsoleCommand(this, args); +} + diff --git a/src/modules/Bots/playerbot/PlayerbotAI.h b/src/modules/Bots/playerbot/PlayerbotAI.h new file mode 100644 index 000000000..bd2c5b953 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAI.h @@ -0,0 +1,193 @@ +#pragma once + +#include "../botpch.h" +#include "PlayerbotMgr.h" +#include "PlayerbotAIBase.h" +#include "strategy/AiObjectContext.h" +#include "strategy/Engine.h" +#include "strategy/ExternalEventHelper.h" +#include "ChatFilter.h" +#include "PlayerbotSecurity.h" +#include +#include "Unit.h" + +class Player; +class PlayerbotMgr; +class ChatHandler; + +using namespace std; +using namespace ai; + +bool IsAlliance(uint8 race); + +class PlayerbotChatHandler: protected ChatHandler +{ +public: + explicit PlayerbotChatHandler(Player* pMasterPlayer) : ChatHandler(pMasterPlayer->GetSession()) {} + void sysmessage(string str) { SendSysMessage(str.c_str()); } + uint32 extractQuestId(string str); + uint32 extractSpellId(string str) + { + char* source = (char*)str.c_str(); + return ExtractSpellIdFromLink(&source); + } +}; + +namespace ai +{ + class MinValueCalculator { + public: + MinValueCalculator(float def = 0.0f) { + param = NULL; + minValue = def; + } + + public: + void probe(float value, void* p) { + if (!param || minValue >= value) { + { + minValue = value; + } + param = p; + } + } + + public: + void* param; + float minValue; + }; +}; + +enum BotState +{ + BOT_STATE_COMBAT = 0, + BOT_STATE_NON_COMBAT = 1, + BOT_STATE_DEAD = 2 +}; + +#define BOT_STATE_MAX 3 + +class PacketHandlingHelper +{ +public: + void AddHandler(uint16 opcode, string handler); + void Handle(ExternalEventHelper &helper); + void AddPacket(const WorldPacket& packet); + +private: + map handlers; + stack queue; +}; + +class ChatCommandHolder +{ +public: + ChatCommandHolder(string command, Player* owner = NULL, uint32 type = CHAT_MSG_WHISPER) : command(command), owner(owner), type(type) {} + ChatCommandHolder(ChatCommandHolder const& other) + { + this->command = other.command; + this->owner = other.owner; + this->type = other.type; + } + +public: + string GetCommand() { return command; } + Player* GetOwner() { return owner; } + uint32 GetType() { return type; } + +private: + string command; + Player* owner; + uint32 type; +}; + +class PlayerbotAI : public PlayerbotAIBase +{ +public: + PlayerbotAI(Player* bot); + virtual ~PlayerbotAI(); + +public: + virtual void UpdateAIInternal(uint32 elapsed); + string HandleRemoteCommand(string command); + void HandleCommand(uint32 type, const string& text, Player& fromPlayer); + void HandleBotOutgoingPacket(const WorldPacket& packet); + void HandleMasterIncomingPacket(const WorldPacket& packet); + void HandleMasterOutgoingPacket(const WorldPacket& packet); + void HandleTeleportAck(); + void ChangeEngine(BotState type); + void DoNextAction(); + void DoSpecificAction(string name); + void ChangeStrategy(string name, BotState type); + void ClearStrategies(BotState type); + list GetStrategies(BotState type); + bool ContainsStrategy(StrategyType type); + bool HasStrategy(string name, BotState type); + void ResetStrategies(); + void ReInitCurrentEngine(); + void Reset(); + bool IsTank(Player* player); + bool IsHeal(Player* player); + bool IsRanged(Player* player); + Creature* GetCreature(ObjectGuid guid); + Unit* GetUnit(ObjectGuid guid); + GameObject* GetGameObject(ObjectGuid guid); + bool TellMaster(ostringstream &stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL) { return TellMaster(stream.str(), securityLevel); } + bool TellMaster(string text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); + bool TellMasterNoFacing(string text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); + void SpellInterrupted(uint32 spellid); + int32 CalculateGlobalCooldown(uint32 spellid); + void InterruptSpell(); + void RemoveAura(string name); + void RemoveShapeshift(); + void WaitForSpellCast(Spell *spell); + bool PlaySound(uint32 emote); + + virtual bool CanCastSpell(string name, Unit* target); + virtual bool CastSpell(string name, Unit* target); + virtual bool HasAura(string spellName, Unit* player); + virtual bool HasAnyAuraOf(Unit* player, ...); + + virtual bool IsInterruptableSpellCasting(Unit* player, string spell); + virtual bool HasAuraToDispel(Unit* player, uint32 dispelType); + bool CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell = true); + + bool HasAura(uint32 spellId, const Unit* player); + bool CastSpell(uint32 spellId, Unit* target); + bool canDispel(const SpellEntry* entry, uint32 dispelType); + + uint32 GetEquipGearScore(Player* player, bool withBags, bool withBank); + bool HasSkill(SkillType skill); + +private: + void _fillGearScoreData(Player *player, Item* item, std::vector* gearScore, uint32& twoHandScore); + +public: + Player* GetBot() { return bot; } + Player* GetMaster() { return master; } + void SetMaster(Player* master) { this->master = master; } + AiObjectContext* GetAiObjectContext() { return aiObjectContext; } + ChatHelper* GetChatHelper() { return &chatHelper; } + bool IsOpposing(Player* player); + static bool IsOpposing(uint8 race1, uint8 race2); + PlayerbotSecurity* GetSecurity() { return &security; } + +protected: + Player* bot; + Player* master; + uint32 accountId; + AiObjectContext* aiObjectContext; + Engine* currentEngine; + Engine* engines[BOT_STATE_MAX]; + BotState currentState; + ChatHelper chatHelper; + queue chatCommands; + PacketHandlingHelper botOutgoingPacketHandlers; + PacketHandlingHelper masterIncomingPacketHandlers; + PacketHandlingHelper masterOutgoingPacketHandlers; + CompositeChatFilter chatFilter; + PlayerbotSecurity security; + map whispers; + pair currentChat; +}; + diff --git a/src/modules/Bots/playerbot/PlayerbotAIAware.h b/src/modules/Bots/playerbot/PlayerbotAIAware.h new file mode 100644 index 000000000..c639f2c78 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAIAware.h @@ -0,0 +1,13 @@ +#pragma once + +namespace ai +{ + class PlayerbotAIAware + { + public: + PlayerbotAIAware(PlayerbotAI* const ai) : ai(ai) { } + + protected: + PlayerbotAI* ai; + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/PlayerbotAIBase.cpp b/src/modules/Bots/playerbot/PlayerbotAIBase.cpp new file mode 100644 index 000000000..7251f6098 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAIBase.cpp @@ -0,0 +1,73 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" + +using namespace ai; +using namespace std; + +PlayerbotAIBase::PlayerbotAIBase() : nextAICheckDelay(0) +{ +} + +void PlayerbotAIBase::UpdateAI(uint32 elapsed) +{ + if (nextAICheckDelay > elapsed) + { + nextAICheckDelay -= elapsed; + } + else + { + nextAICheckDelay = 0; + } + + if (!CanUpdateAI()) + { + return; + } + + UpdateAIInternal(elapsed); + YieldThread(); +} + +void PlayerbotAIBase::SetNextCheckDelay(const uint32 delay) +{ + if (nextAICheckDelay < delay) + { + sLog.outDebug("Setting lesser delay %d -> %d", nextAICheckDelay, delay); + } + + nextAICheckDelay = delay; + + if (nextAICheckDelay > sPlayerbotAIConfig.globalCoolDown) + { + sLog.outDebug( "set next check delay: %d", nextAICheckDelay); + } +} + +void PlayerbotAIBase::IncreaseNextCheckDelay(uint32 delay) +{ + nextAICheckDelay += delay; + + if (nextAICheckDelay > sPlayerbotAIConfig.globalCoolDown) + { + sLog.outDebug( "increase next check delay: %d", nextAICheckDelay); + } +} + +bool PlayerbotAIBase::CanUpdateAI() +{ + return nextAICheckDelay < 100; +} + +void PlayerbotAIBase::YieldThread() +{ + if (nextAICheckDelay < sPlayerbotAIConfig.reactDelay) + { + nextAICheckDelay = sPlayerbotAIConfig.reactDelay; + } +} + +bool PlayerbotAIBase::IsActive() +{ + return nextAICheckDelay < sPlayerbotAIConfig.passiveDelay; +} diff --git a/src/modules/Bots/playerbot/PlayerbotAIBase.h b/src/modules/Bots/playerbot/PlayerbotAIBase.h new file mode 100644 index 000000000..1cd9d0aa3 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAIBase.h @@ -0,0 +1,25 @@ +#pragma once + +class Player; +class PlayerbotMgr; +class ChatHandler; + +using namespace std; + +class PlayerbotAIBase +{ +public: + PlayerbotAIBase(); + +public: + bool CanUpdateAI(); + void SetNextCheckDelay(const uint32 delay); + void IncreaseNextCheckDelay(uint32 delay); + void YieldThread(); + virtual void UpdateAI(uint32 elapsed); + virtual void UpdateAIInternal(uint32 elapsed) = 0; + bool IsActive(); + +protected: + uint32 nextAICheckDelay; +}; diff --git a/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp b/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp new file mode 100644 index 000000000..b103a4f7b --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp @@ -0,0 +1,309 @@ +#include "../botpch.h" +#include "PlayerbotAIConfig.h" +#include "playerbot.h" +#include "RandomPlayerbotFactory.h" +#include "AccountMgr.h" +#include "SystemConfig.h" + +using namespace std; + +INSTANTIATE_SINGLETON_1(PlayerbotAIConfig); + +template +void LoadList(const string& value, T &list) +{ + vector ids = split(value, ','); + for (vector::iterator i = ids.begin(); i != ids.end(); i++) + { + uint32 id = atoi((*i).c_str()); + if (!id) + { + continue; + } + + list.push_back(id); + } +} + +bool PlayerbotAIConfig::Initialize() +{ + sLog.outString("Initializing AI Playerbot by ike3, based on the original Playerbot by blueboy"); + + if (!config.SetSource(SYSCONFDIR"aiplayerbot.conf")) + { + sLog.outString("AI Playerbot is Disabled. Unable to open configuration file aiplayerbot.conf"); + return false; + } + + enabled = config.GetBoolDefault("AiPlayerbot.Enabled", false); + if (!enabled) + { + sLog.outString("AI Playerbot is Disabled in aiplayerbot.conf"); + return false; + } + + globalCoolDown = (uint32) config.GetIntDefault("AiPlayerbot.GlobalCooldown", 500); + maxWaitForMove = config.GetIntDefault("AiPlayerbot.MaxWaitForMove", 3000); + expireActionTime = config.GetIntDefault("AiPlayerbot.ExpireActionTime", 5000); + dispelAuraDuration = config.GetIntDefault("AiPlayerbot.DispelAuraDuration", 7000); + reactDelay = (uint32) config.GetIntDefault("AiPlayerbot.ReactDelay", 100); + passiveDelay = (uint32) config.GetIntDefault("AiPlayerbot.PassiveDelay", 3000); + repeatDelay = (uint32) config.GetIntDefault("AiPlayerbot.RepeatDelay", 3000); + + sightDistance = config.GetFloatDefault("AiPlayerbot.SightDistance", 50.0f); + spellDistance = config.GetFloatDefault("AiPlayerbot.SpellDistance", 25.0f); + shootDistance = config.GetFloatDefault("AiPlayerbot.ShootDistance", 13.0f); + reactDistance = config.GetFloatDefault("AiPlayerbot.ReactDistance", 150.0f); + grindDistance = config.GetFloatDefault("AiPlayerbot.GrindDistance", 100.0f); + lootDistance = config.GetFloatDefault("AiPlayerbot.LootDistance", 15.0f); + fleeDistance = config.GetFloatDefault("AiPlayerbot.FleeDistance", 5.0f); + tooCloseDistance = config.GetFloatDefault("AiPlayerbot.TooCloseDistance", 5.0f); + meleeDistance = config.GetFloatDefault("AiPlayerbot.MeleeDistance", 0.5f); + followDistance = config.GetFloatDefault("AiPlayerbot.FollowDistance", 1.5f); + whisperDistance = config.GetFloatDefault("AiPlayerbot.WhisperDistance", 6000.0f); + contactDistance = config.GetFloatDefault("AiPlayerbot.ContactDistance", 0.5f); + aoeRadius = config.GetFloatDefault("AiPlayerbot.AoeRadius", 10.0f); + + criticalHealth = config.GetIntDefault("AiPlayerbot.CriticalHealth", 20); + lowHealth = config.GetIntDefault("AiPlayerbot.LowHealth", 50); + mediumHealth = config.GetIntDefault("AiPlayerbot.MediumHealth", 70); + almostFullHealth = config.GetIntDefault("AiPlayerbot.AlmostFullHealth", 85); + lowMana = config.GetIntDefault("AiPlayerbot.LowMana", 15); + mediumMana = config.GetIntDefault("AiPlayerbot.MediumMana", 40); + + randomGearLoweringChance = config.GetFloatDefault("AiPlayerbot.RandomGearLoweringChance", 0.15); + randomBotMaxLevelChance = config.GetFloatDefault("AiPlayerbot.RandomBotMaxLevelChance", 0.15); + + iterationsPerTick = config.GetIntDefault("AiPlayerbot.IterationsPerTick", 100); + + allowGuildBots = config.GetBoolDefault("AiPlayerbot.AllowGuildBots", true); + + randomBotMapsAsString = config.GetStringDefault("AiPlayerbot.RandomBotMaps", "0,1,530,571"); + LoadList >(randomBotMapsAsString, randomBotMaps); + LoadList >(config.GetStringDefault("AiPlayerbot.RandomBotQuestItems", "6948,5175,5176,5177,5178"), randomBotQuestItems); + LoadList >(config.GetStringDefault("AiPlayerbot.RandomBotSpellIds", "54197"), randomBotSpellIds); + + botAutologin = config.GetBoolDefault("AiPlayerbot.BotAutologin", false); + randomBotAutologin = config.GetBoolDefault("AiPlayerbot.RandomBotAutologin", false); + minRandomBots = config.GetIntDefault("AiPlayerbot.MinRandomBots", 50); + maxRandomBots = config.GetIntDefault("AiPlayerbot.MaxRandomBots", 200); + randomBotUpdateInterval = config.GetIntDefault("AiPlayerbot.RandomBotUpdateInterval", 60); + randomBotCountChangeMinInterval = config.GetIntDefault("AiPlayerbot.RandomBotCountChangeMinInterval", 24 * 3600); + randomBotCountChangeMaxInterval = config.GetIntDefault("AiPlayerbot.RandomBotCountChangeMaxInterval", 3 * 24 * 3600); + minRandomBotInWorldTime = config.GetIntDefault("AiPlayerbot.MinRandomBotInWorldTime", 24 * 3600); + maxRandomBotInWorldTime = config.GetIntDefault("AiPlayerbot.MaxRandomBotInWorldTime", 14 * 24 * 3600); + minRandomBotRandomizeTime = config.GetIntDefault("AiPlayerbot.MinRandomBotRandomizeTime", 2 * 3600); + maxRandomBotRandomizeTime = config.GetIntDefault("AiPlayerbot.MaxRandomRandomizeTime", 14 * 24 * 3600); + minRandomBotReviveTime = config.GetIntDefault("AiPlayerbot.MinRandomBotReviveTime", 60); + maxRandomBotReviveTime = config.GetIntDefault("AiPlayerbot.MaxRandomReviveTime", 300); + randomBotTeleportDistance = config.GetIntDefault("AiPlayerbot.RandomBotTeleportDistance", 133); + minRandomBotsPerInterval = config.GetIntDefault("AiPlayerbot.MinRandomBotsPerInterval", 50); + maxRandomBotsPerInterval = config.GetIntDefault("AiPlayerbot.MaxRandomBotsPerInterval", 100); + minRandomBotsPriceChangeInterval = config.GetIntDefault("AiPlayerbot.MinRandomBotsPriceChangeInterval", 2 * 3600); + maxRandomBotsPriceChangeInterval = config.GetIntDefault("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * 3600); + randomBotJoinLfg = config.GetBoolDefault("AiPlayerbot.RandomBotJoinLfg", true); + logInGroupOnly = config.GetBoolDefault("AiPlayerbot.LogInGroupOnly", true); + logValuesPerTick = config.GetBoolDefault("AiPlayerbot.LogValuesPerTick", false); + fleeingEnabled = config.GetBoolDefault("AiPlayerbot.FleeingEnabled", true); + randomBotMinLevel = config.GetIntDefault("AiPlayerbot.RandomBotMinLevel", 1); + randomBotMaxLevel = config.GetIntDefault("AiPlayerbot.RandomBotMaxLevel", 255); + randomBotLoginAtStartup = config.GetBoolDefault("AiPlayerbot.RandomBotLoginAtStartup", true); + randomBotTeleLevel = config.GetIntDefault("AiPlayerbot.RandomBotTeleLevel", 3); + openGoSpell = config.GetIntDefault("AiPlayerbot.OpenGoSpell", 6477); + + randomChangeMultiplier = config.GetFloatDefault("AiPlayerbot.RandomChangeMultiplier", 1.0); + + randomBotCombatStrategies = config.GetStringDefault("AiPlayerbot.RandomBotCombatStrategies", "+dps,+dps assist,-threat"); + randomBotNonCombatStrategies = config.GetStringDefault("AiPlayerbot.RandomBotNonCombatStrategies", "+grind,+move random,+loot"); + combatStrategies = config.GetStringDefault("AiPlayerbot.CombatStrategies", "+custom::say"); + nonCombatStrategies = config.GetStringDefault("AiPlayerbot.NonCombatStrategies", "+custom::say"); + + commandPrefix = config.GetStringDefault("AiPlayerbot.CommandPrefix", ""); + + commandServerPort = config.GetIntDefault("AiPlayerbot.CommandServerPort", 0); + + for (uint32 cls = 0; cls < MAX_CLASSES; ++cls) + { + for (uint32 spec = 0; spec < 3; ++spec) + { + ostringstream os; os << "AiPlayerbot.RandomClassSpecProbability." << cls << "." << spec; + specProbability[cls][spec] = config.GetIntDefault(os.str().c_str(), 33); + } + } + + randomBotAccountPrefix = config.GetStringDefault("AiPlayerbot.RandomBotAccountPrefix", "rndbot"); + randomBotAccountCount = config.GetIntDefault("AiPlayerbot.RandomBotAccountCount", 50); + deleteRandomBotAccounts = config.GetBoolDefault("AiPlayerbot.DeleteRandomBotAccounts", false); + randomBotGuildCount = config.GetIntDefault("AiPlayerbot.RandomBotGuildCount", 50); + deleteRandomBotGuilds = config.GetBoolDefault("AiPlayerbot.DeleteRandomBotGuilds", false); + + guildTaskEnabled = config.GetBoolDefault("AiPlayerbot.EnableGuildTasks", true); + minGuildTaskChangeTime = config.GetIntDefault("AiPlayerbot.MinGuildTaskChangeTime", 2 * 24 * 3600); + maxGuildTaskChangeTime = config.GetIntDefault("AiPlayerbot.MaxGuildTaskChangeTime", 5 * 24 * 3600); + minGuildTaskAdvertisementTime = config.GetIntDefault("AiPlayerbot.MinGuildTaskAdvertisementTime", 60); + maxGuildTaskAdvertisementTime = config.GetIntDefault("AiPlayerbot.MaxGuildTaskAdvertisementTime", 12 * 3600); + minGuildTaskRewardTime = config.GetIntDefault("AiPlayerbot.MinGuildTaskRewardTime", 30); + maxGuildTaskRewardTime = config.GetIntDefault("AiPlayerbot.MaxGuildTaskRewardTime", 120); + guildTaskAdvertCleanupTime = config.GetIntDefault("AiPlayerbot.GuildTaskAdvertCleanupTime", 3600); + enableGreet = config.GetBoolDefault("AiPlayerbot.EnableGreet", false); + //cosmetic + randomBotShowCloak = config.GetBoolDefault("AiPlayerbot.RandomBotShowCloak", false); + randomBotShowHelmet = config.GetBoolDefault("AiPlayerbot.RandomBotShowHelmet", false); + + if (sPlayerbotAIConfig.deleteRandomBotAccounts) + { + RandomPlayerbotFactory::DeleteRandomBots(); + } + else if (sPlayerbotAIConfig.deleteRandomBotGuilds) + { + RandomPlayerbotFactory::DeleteRandomGuilds(); + } + + RandomPlayerbotFactory::CreateRandomBots(); + BASIC_LOG("AI Playerbot configuration loaded"); + + return true; +} + + +bool PlayerbotAIConfig::IsInRandomAccountList(uint32 id) +{ + return find(randomBotAccounts.begin(), randomBotAccounts.end(), id) != randomBotAccounts.end(); +} + +bool PlayerbotAIConfig::IsInRandomQuestItemList(uint32 id) +{ + return find(randomBotQuestItems.begin(), randomBotQuestItems.end(), id) != randomBotQuestItems.end(); +} + +string PlayerbotAIConfig::GetValue(const string name) +{ + ostringstream out; + + if (name == "GlobalCooldown") + { + out << globalCoolDown; + } + else if (name == "ReactDelay") + { + out << reactDelay; + } + + else if (name == "SightDistance") + { + out << sightDistance; + } + else if (name == "SpellDistance") + { + out << spellDistance; + } + else if (name == "ReactDistance") + { + out << reactDistance; + } + else if (name == "GrindDistance") + { + out << grindDistance; + } + else if (name == "LootDistance") + { + out << lootDistance; + } + else if (name == "FleeDistance") + { + out << fleeDistance; + } + + else if (name == "CriticalHealth") + { + out << criticalHealth; + } + else if (name == "LowHealth") + { + out << lowHealth; + } + else if (name == "MediumHealth") + { + out << mediumHealth; + } + else if (name == "AlmostFullHealth") + { + out << almostFullHealth; + } + else if (name == "LowMana") + { + out << lowMana; + } + + else if (name == "IterationsPerTick") + { + out << iterationsPerTick; + } + + return out.str(); +} + +void PlayerbotAIConfig::SetValue(const string name, string value) +{ + istringstream out(value, istringstream::in); + + if (name == "GlobalCooldown") + { + out >> globalCoolDown; + } + else if (name == "ReactDelay") + { + out >> reactDelay; + } + + else if (name == "SightDistance") + { + out >> sightDistance; + } + else if (name == "SpellDistance") + { + out >> spellDistance; + } + else if (name == "ReactDistance") + { + out >> reactDistance; + } + else if (name == "GrindDistance") + { + out >> grindDistance; + } + else if (name == "LootDistance") + { + out >> lootDistance; + } + else if (name == "FleeDistance") + { + out >> fleeDistance; + } + + else if (name == "CriticalHealth") + { + out >> criticalHealth; + } + else if (name == "LowHealth") + { + out >> lowHealth; + } + else if (name == "MediumHealth") + { + out >> mediumHealth; + } + else if (name == "AlmostFullHealth") + { + out >> almostFullHealth; + } + else if (name == "LowMana") + { + out >> lowMana; + } + + else if (name == "IterationsPerTick") + { + out >> iterationsPerTick; + } +} diff --git a/src/modules/Bots/playerbot/PlayerbotAIConfig.h b/src/modules/Bots/playerbot/PlayerbotAIConfig.h new file mode 100644 index 000000000..50f901518 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotAIConfig.h @@ -0,0 +1,90 @@ +#pragma once + +#include "Config.h" + +class Player; +class PlayerbotMgr; +class ChatHandler; + +class PlayerbotAIConfig +{ +public: + static PlayerbotAIConfig& instance() + { + static PlayerbotAIConfig instance; + return instance; + } + +public: + bool Initialize(); + bool IsInRandomAccountList(uint32 id); + bool IsInRandomQuestItemList(uint32 id); + + bool enabled; + bool allowGuildBots; + uint32 globalCoolDown, reactDelay, maxWaitForMove, expireActionTime, dispelAuraDuration, passiveDelay, repeatDelay; + float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, + fleeDistance, tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, + aoeRadius; + uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth; + uint32 lowMana, mediumMana; + + uint32 openGoSpell; + bool randomBotAutologin; + bool botAutologin; + std::string randomBotMapsAsString; + std::vector randomBotMaps; + std::list randomBotQuestItems; + std::list randomBotAccounts; + std::list randomBotSpellIds; + uint32 randomBotTeleportDistance; + float randomGearLoweringChance; + float randomBotMaxLevelChance; + uint32 minRandomBots, maxRandomBots; + uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval; + uint32 minRandomBotInWorldTime, maxRandomBotInWorldTime; + uint32 minRandomBotRandomizeTime, maxRandomBotRandomizeTime; + uint32 minRandomBotReviveTime, maxRandomBotReviveTime; + uint32 minRandomBotPvpTime, maxRandomBotPvpTime; + uint32 minRandomBotsPerInterval, maxRandomBotsPerInterval; + uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval; + bool randomBotJoinLfg; + bool randomBotLoginAtStartup; + uint32 randomBotTeleLevel; + bool logInGroupOnly, logValuesPerTick; + bool fleeingEnabled; + std::string combatStrategies, nonCombatStrategies; + std::string randomBotCombatStrategies, randomBotNonCombatStrategies; + uint32 randomBotMinLevel, randomBotMaxLevel; + float randomChangeMultiplier; + uint32 specProbability[MAX_CLASSES][3]; + std::string commandPrefix; + std::string randomBotAccountPrefix; + uint32 randomBotAccountCount; + bool deleteRandomBotAccounts; + uint32 randomBotGuildCount; + bool deleteRandomBotGuilds; + std::list randomBotGuilds; + bool randomBotShowHelmet; + bool randomBotShowCloak; + bool enableGreet; + + bool guildTaskEnabled; + uint32 minGuildTaskChangeTime, maxGuildTaskChangeTime; + uint32 minGuildTaskAdvertisementTime, maxGuildTaskAdvertisementTime; + uint32 minGuildTaskRewardTime, maxGuildTaskRewardTime; + uint32 guildTaskAdvertCleanupTime; + + uint32 iterationsPerTick; + + int commandServerPort; + + std::string GetValue(std::string name); + void SetValue(std::string name, std::string value); + +private: + Config config; +}; + +#define sPlayerbotAIConfig MaNGOS::Singleton::Instance() + diff --git a/src/modules/Bots/playerbot/PlayerbotCommandServer.cpp b/src/modules/Bots/playerbot/PlayerbotCommandServer.cpp new file mode 100644 index 000000000..1f4db47b7 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotCommandServer.cpp @@ -0,0 +1,78 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotFactory.h" +#include "PlayerbotCommandServer.h" +#include +#include + +INSTANTIATE_SINGLETON_1(PlayerbotCommandServer); + +using namespace std; + +bool ReadLine(ACE_SOCK_Stream& client_stream, string* buffer, string* line) +{ + // Do the real reading from fd until buffer has '\n'. + string::iterator pos; + while ((pos = find(buffer->begin(), buffer->end(), '\n')) == buffer->end()) + { + char buf[33]; + size_t n = client_stream.recv_n(buf, 1, 0); + if (n == -1) + { + return false; + } + + buf[n] = 0; + *buffer += buf; + } + + *line = string(buffer->begin(), pos); + *buffer = string(pos + 1, buffer->end()); + return true; +} + +class PlayerbotCommandServerThread: public ACE_Task +{ +public: + int svc(void) { + if (!sPlayerbotAIConfig.commandServerPort) { + { + return 0; + } + } + + ostringstream s; s << "Starting Playerbot Command Server on port " << sPlayerbotAIConfig.commandServerPort; + sLog.outString(s.str().c_str()); + + ACE_INET_Addr server(sPlayerbotAIConfig.commandServerPort); + ACE_SOCK_Acceptor client_responder(server); + + while (true) + { + ACE_SOCK_Stream client_stream; + ACE_Time_Value timeout(5); + ACE_INET_Addr client; + if (-1 != client_responder.accept(client_stream, &client, &timeout)) + { + string buffer, request; + while (ReadLine(client_stream, &buffer, &request)) + { + string response = sRandomPlayerbotMgr.HandleRemoteCommand(request) + "\n"; + client_stream.send_n(response.c_str(), response.size(), 0); + request = ""; + } + client_stream.close(); + } + } + + return 0; + } +}; + + +void PlayerbotCommandServer::Start() +{ + PlayerbotCommandServerThread *thread = new PlayerbotCommandServerThread(); + thread->activate(); +} diff --git a/src/modules/Bots/playerbot/PlayerbotCommandServer.h b/src/modules/Bots/playerbot/PlayerbotCommandServer.h new file mode 100644 index 000000000..0ea66c7ca --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotCommandServer.h @@ -0,0 +1,26 @@ +#ifndef _PlayerbotCommandServer_H +#define _PlayerbotCommandServer_H + +#include "Common.h" +#include "PlayerbotAIBase.h" +#include "PlayerbotMgr.h" + +using namespace std; + +class PlayerbotCommandServer +{ +public: + PlayerbotCommandServer() {} + virtual ~PlayerbotCommandServer() {} + static PlayerbotCommandServer& instance() + { + static PlayerbotCommandServer instance; + return instance; + } + + void Start(); +}; + +#define sPlayerbotCommandServer PlayerbotCommandServer::instance() + +#endif diff --git a/src/modules/Bots/playerbot/PlayerbotDbStore.cpp b/src/modules/Bots/playerbot/PlayerbotDbStore.cpp new file mode 100644 index 000000000..6e64ca5d6 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotDbStore.cpp @@ -0,0 +1,135 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotFactory.h" +#include "PlayerbotDbStore.h" +#include +#include + +#include "LootObjectStack.h" +#include "strategy/values/Formations.h" +#include "strategy/values/PositionValue.h" +INSTANTIATE_SINGLETON_1(PlayerbotDbStore); + +using namespace std; +using namespace ai; + +void PlayerbotDbStore::Load(PlayerbotAI *ai) +{ + ObjectGuid guid = ai->GetBot()->GetObjectGuid(); + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + return; + } + + QueryResult* results = CharacterDatabase.PQuery("SELECT `key`,`value` FROM `ai_playerbot_db_store` WHERE `guid` = '%u'", guid); + if (results) + { + ai->ClearStrategies(BOT_STATE_COMBAT); + ai->ClearStrategies(BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("+chat", BOT_STATE_COMBAT); + ai->ChangeStrategy("+chat", BOT_STATE_NON_COMBAT); + + do + { + Field* fields = results->Fetch(); + string key = fields[0].GetString(); + string value = fields[1].GetString(); + ExternalEventHelper helper(ai->GetAiObjectContext()); + helper.ParseChatCommand(value, ai->GetMaster()); + ai->DoNextAction(); + } while (results->NextRow()); + + delete results; + } +} + +void PlayerbotDbStore::Save(PlayerbotAI *ai) +{ + ObjectGuid guid = ai->GetBot()->GetObjectGuid(); + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + return; + } + + Reset(ai); + + SaveValue(guid, "co", FormatStrategies("co", ai->GetStrategies(BOT_STATE_COMBAT))); + SaveValue(guid, "nc", FormatStrategies("nc", ai->GetStrategies(BOT_STATE_NON_COMBAT))); + SaveValue(guid, "dead", FormatStrategies("dead", ai->GetStrategies(BOT_STATE_DEAD))); + + Value* formation = ai->GetAiObjectContext()->GetValue("formation"); + ostringstream outFormation; outFormation << "formation " << formation->Get()->getName(); + SaveValue(guid, "formation", outFormation.str()); + + Value* lootStrategy = ai->GetAiObjectContext()->GetValue("loot strategy"); + ostringstream outLoot; outLoot << "ll " << lootStrategy->Get()->GetName(); + SaveValue(guid, "ll", outLoot.str()); + + list& outfits = ai->GetAiObjectContext()->GetValue&>("outfit list")->Get(); + for (list::iterator i = outfits.begin(); i != outfits.end(); ++i) + { + ostringstream outOutfit; outOutfit << "outfit " << *i; + SaveValue(guid, "outfit", outOutfit.str()); + } + + set& ss = ai->GetAiObjectContext()->GetValue&>("skip spells list")->Get(); + ostringstream outSs; + outSs << "ss "; + bool first = true; + for (set::iterator i = ss.begin(); i != ss.end(); ++i) + { + if (first) first = false; else outSs << ","; + { + outSs << *i; + } + } + SaveValue(guid, "ss", outSs.str()); + + uint32 saveMana = (uint32)round(ai->GetAiObjectContext()->GetValue("mana save level")->Get()); + ostringstream outSaveMana; outSaveMana << "save mana " << saveMana; + SaveValue(guid, "save mana", outSaveMana.str()); + + ai::PositionMap& posMap = ai->GetAiObjectContext()->GetValue("position")->Get(); + for (ai::PositionMap::iterator i = posMap.begin(); i != posMap.end(); ++i) + { + ai::Position pos = i->second; + if (pos.isSet()) + { + ostringstream out; out << "position " << i->first << " " << pos.x << "," << pos.y << "," << pos.z; + SaveValue(guid, "position", out.str()); + } + } +} + +string PlayerbotDbStore::FormatStrategies(const string type, list strategies) +{ + ostringstream out; + out << type << " "; + for(list::iterator i = strategies.begin(); i != strategies.end(); ++i) + { + out << "+" << (*i).c_str() << ","; + } + + string res = out.str(); + return res.substr(0, res.size() - 1); +} + +void PlayerbotDbStore::Reset(PlayerbotAI *ai) +{ + ObjectGuid guid = ai->GetBot()->GetObjectGuid(); + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + return; + } + + CharacterDatabase.PExecute("DELETE FROM `ai_playerbot_db_store` WHERE `guid` = '%u'", guid); +} + +void PlayerbotDbStore::SaveValue(uint64 guid, string key, string value) +{ + CharacterDatabase.PExecute("INSERT INTO `ai_playerbot_db_store` (`guid`, `key`, `value`) VALUES ('%u', '%s', '%s')", guid, key.c_str(), value.c_str()); +} diff --git a/src/modules/Bots/playerbot/PlayerbotDbStore.h b/src/modules/Bots/playerbot/PlayerbotDbStore.h new file mode 100644 index 000000000..da27a50c3 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotDbStore.h @@ -0,0 +1,32 @@ +#ifndef _PlayerbotDbStore_H +#define _PlayerbotDbStore_H + +#include "Common.h" +#include "PlayerbotAIBase.h" +#include "PlayerbotMgr.h" + +using namespace std; + +class PlayerbotDbStore +{ +public: + PlayerbotDbStore() {} + virtual ~PlayerbotDbStore() {} + static PlayerbotDbStore& instance() + { + static PlayerbotDbStore instance; + return instance; + } + + void Save(PlayerbotAI *ai); + void Load(PlayerbotAI *ai); + void Reset(PlayerbotAI *ai); + +private: + void SaveValue(uint64 guid, string key, string value); + string FormatStrategies(string type, list strategies); +}; + +#define sPlayerbotDbStore PlayerbotDbStore::instance() + +#endif diff --git a/src/modules/Bots/playerbot/PlayerbotFactory.cpp b/src/modules/Bots/playerbot/PlayerbotFactory.cpp new file mode 100644 index 000000000..9c1f889f5 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotFactory.cpp @@ -0,0 +1,1963 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotFactory.h" +#include "SQLStorages.h" +#include "ItemPrototype.h" +#include "PlayerbotAIConfig.h" +#include "AccountMgr.h" +#include "DBCStore.h" +#include "SharedDefines.h" +#include "ahbot/AhBot.h" +#include "RandomPlayerbotFactory.h" + +using namespace ai; +using namespace std; + +uint32 PlayerbotFactory::tradeSkills[] = +{ + SKILL_ALCHEMY, + SKILL_ENCHANTING, + SKILL_SKINNING, + SKILL_TAILORING, + SKILL_JEWELCRAFTING, + SKILL_LEATHERWORKING, + SKILL_ENGINEERING, + SKILL_HERBALISM, + SKILL_MINING, + SKILL_BLACKSMITHING, + SKILL_COOKING, + SKILL_FIRST_AID, + SKILL_FISHING +}; + +list PlayerbotFactory::classQuestIds; + +void PlayerbotFactory::Randomize() +{ + Randomize(true); +} + +void PlayerbotFactory::Refresh() +{ + Prepare(); + InitEquipment(true); + InitAmmo(); + InitFood(); + InitPotions(); + InitPet(); + + uint32 money = urand(level * 1000, level * 5 * 1000); + if (bot->GetMoney() < money) + { + bot->SetMoney(money); + } + bot->SaveToDB(); +} + +void PlayerbotFactory::CleanRandomize() +{ + Randomize(false); +} + +void PlayerbotFactory::Prepare() +{ + if (!itemQuality) + { + if (level <= 10) + { + itemQuality = urand(ITEM_QUALITY_NORMAL, ITEM_QUALITY_UNCOMMON); + } + else if (level <= 20) + { + itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_RARE); + } + else if (level <= 40) + { + itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_EPIC); + } + else if (level < 60) + { + itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_EPIC); + } + else + { + itemQuality = urand(ITEM_QUALITY_RARE, ITEM_QUALITY_EPIC); + } + } + + if (bot->IsDead()) + { + bot->ResurrectPlayer(1.0f, false); + } + + bot->CombatStop(true); + bot->SetLevel(level); + if (!sPlayerbotAIConfig.randomBotShowHelmet) + { + bot->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM); + } + + if (!sPlayerbotAIConfig.randomBotShowCloak) + { + bot->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK); + } +} + +void PlayerbotFactory::Randomize(bool incremental) +{ + sLog.outDetail("Preparing to randomize..."); + Prepare(); + + sLog.outDetail("Resetting player..."); + bot->resetTalents(true); + ClearSpells(); + ClearInventory(); + bot->SaveToDB(); + + sLog.outDetail("Initializing quests..."); + InitQuests(); + // quest rewards boost bot level, so reduce back + bot->SetLevel(level); + ClearInventory(); + bot->SetUInt32Value(PLAYER_XP, 0); + CancelAuras(); + bot->SaveToDB(); + + sLog.outDetail("Initializing spells (step 1)..."); + InitAvailableSpells(); + + sLog.outDetail("Initializing skills (step 1)..."); + InitSkills(); + InitTradeSkills(); + + sLog.outDetail("Initializing talents..."); + InitTalents(); + + sLog.outDetail("Initializing spells (step 2)..."); + InitAvailableSpells(); + InitSpecialSpells(); + + sLog.outDetail("Initializing mounts..."); + InitMounts(); + + sLog.outDetail("Initializing skills (step 2)..."); + UpdateTradeSkills(); + bot->SaveToDB(); + + sLog.outDetail("Initializing equipmemt..."); + InitEquipment(incremental); + + sLog.outDetail("Initializing bags..."); + InitBags(); + + sLog.outDetail("Initializing ammo..."); + InitAmmo(); + + sLog.outDetail("Initializing food..."); + InitFood(); + + sLog.outDetail("Initializing potions..."); + InitPotions(); + + sLog.outDetail("Initializing second equipment set..."); + InitSecondEquipmentSet(); + + sLog.outDetail("Initializing inventory..."); + InitInventory(); + + sLog.outDetail("Initializing guilds..."); + InitGuild(); + + sLog.outDetail("Initializing pet..."); + InitPet(); + + sLog.outDetail("Saving to DB..."); + bot->SetMoney(urand(level * 1000, level * 5 * 1000)); + bot->SetHealth(bot->GetMaxHealth()); + bot->SaveToDB(); + sLog.outDetail("Done."); +} + +void PlayerbotFactory::InitPet() +{ + Pet* pet = bot->GetPet(); + if (!pet) + { + if (bot->getClass() != CLASS_HUNTER) + { + return; + } + + Map* map = bot->GetMap(); + if (!map) + { + return; + } + + vector ids; + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co) + { + continue; + } + + if (!co->isTameable(bot->CanTameExoticPets())) + { + continue; + } + + if ((int)co->MinLevel > (int)bot->getLevel()) + { + continue; + } + + ids.push_back(id); + } + + if (ids.empty()) + { + sLog.outError("No pets available for bot %s (%d level)", bot->GetName(), bot->getLevel()); + return; + } + + for (int i = 0; i < 100; i++) + { + int index = urand(0, ids.size() - 1); + CreatureInfo const* co = sCreatureStorage.LookupEntry(ids[index]); + if (!co) + { + continue; + } + + uint32 guid = map->GenerateLocalLowGuid(HIGHGUID_PET); + CreatureCreatePos pos(map, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), bot->GetPhaseMask()); + uint32 pet_number = sObjectMgr.GeneratePetNumber(); + pet = new Pet(HUNTER_PET); + if (!pet->Create(guid, pos, co, pet_number)) + { + delete pet; + pet = NULL; + continue; + } + + pet->SetOwnerGuid(bot->GetObjectGuid()); + pet->SetCreatorGuid(bot->GetObjectGuid()); + pet->setFaction(bot->getFaction()); + pet->SetLevel(bot->getLevel()); + pet->InitStatsForLevel(bot->getLevel(), bot); + pet->SetPower(POWER_HAPPINESS, HAPPINESS_LEVEL_SIZE * 2); + pet->GetCharmInfo()->SetPetNumber(sObjectMgr.GeneratePetNumber(), true); + pet->AIM_Initialize(); + pet->InitPetCreateSpells(); + bot->SetPet(pet); + bot->SetPetGuid(pet->GetObjectGuid()); + + sLog.outDebug( "Bot %s: assign pet %d (%d level)", bot->GetName(), co->Entry, bot->getLevel()); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + bot->PetSpellInitialize(); + break; + } + } + + pet = bot->GetPet(); + if (pet) + { + pet->InitStatsForLevel(bot->getLevel()); + pet->SetLevel(bot->getLevel()); + pet->SetPower(POWER_HAPPINESS, HAPPINESS_LEVEL_SIZE * 2); + pet->SetHealth(pet->GetMaxHealth()); + } + else + { + sLog.outError("Cannot create pet for bot %s", bot->GetName()); + return; + } + + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if(itr->second.state == PETSPELL_REMOVED) + { + continue; + } + + uint32 spellId = itr->first; + if(IsPassiveSpell(spellId)) + { + continue; + } + + pet->ToggleAutocast(spellId, true); + } +} + +void PlayerbotFactory::ClearSpells() +{ + list spells; + for(PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + { + continue; + } + + spells.push_back(spellId); + } + + for (list::iterator i = spells.begin(); i != spells.end(); ++i) + { + bot->removeSpell(*i, false, false); + } +} + +void PlayerbotFactory::InitSpells() +{ + for (int i = 0; i < 15; i++) + { + InitAvailableSpells(); + } +} + +void PlayerbotFactory::InitTalents() +{ + uint32 point = urand(0, 100); + uint8 cls = bot->getClass(); + uint32 p1 = sPlayerbotAIConfig.specProbability[cls][0]; + uint32 p2 = p1 + sPlayerbotAIConfig.specProbability[cls][1]; + + uint32 specNo = (point < p1 ? 0 : (point < p2 ? 1 : 2)); + InitTalents(specNo); + + if (bot->GetFreeTalentPoints()) + { + InitTalents(2 - specNo); + } +} + + +class DestroyItemsVisitor : public IterateItemsVisitor +{ +public: + explicit DestroyItemsVisitor(Player* bot) : IterateItemsVisitor(), bot(bot) {} + + virtual bool Visit(Item* item) + { + uint32 id = item->GetProto()->ItemId; + if (CanKeep(id)) + { + keep.insert(id); + return true; + } + + bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true); + return true; + } + +private: + bool CanKeep(uint32 id) + { + if (keep.find(id) != keep.end()) + { + return false; + } + + if (sPlayerbotAIConfig.IsInRandomQuestItemList(id)) + { + return true; + } + + + ItemPrototype const* proto = sItemStorage.LookupEntry(id); + if (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_JUNK) + { + return true; + } + + return false; + } + +private: + Player* bot; + set keep; + +}; + +bool PlayerbotFactory::CanEquipArmor(ItemPrototype const* proto) +{ + if (bot->HasSkill(SKILL_SHIELD) && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + return true; + } + + if (bot->HasSkill(SKILL_PLATE_MAIL)) + { + if (proto->SubClass != ITEM_SUBCLASS_ARMOR_PLATE) + { + return false; + } + } + else if (bot->HasSkill(SKILL_MAIL)) + { + if (proto->SubClass != ITEM_SUBCLASS_ARMOR_MAIL) + { + return false; + } + } + else if (bot->HasSkill(SKILL_LEATHER)) + { + if (proto->SubClass != ITEM_SUBCLASS_ARMOR_LEATHER) + { + return false; + } + } + + if (proto->Quality <= ITEM_QUALITY_NORMAL) + { + return true; + } + + uint8 sp = 0, ap = 0, tank = 0; + for (int j = 0; j < MAX_ITEM_PROTO_STATS; ++j) + { + // for ItemStatValue != 0 + if(!proto->ItemStat[j].ItemStatValue) + { + continue; + } + + AddItemStats(proto->ItemStat[j].ItemStatType, sp, ap, tank); + } + + return CheckItemStats(sp, ap, tank); +} + +bool PlayerbotFactory::CheckItemStats(uint8 sp, uint8 ap, uint8 tank) +{ + switch (bot->getClass()) + { + case CLASS_PRIEST: + case CLASS_MAGE: + case CLASS_WARLOCK: + if (!sp || ap > sp || tank > sp) + { + return false; + } + break; + case CLASS_PALADIN: + case CLASS_WARRIOR: + if ((!ap && !tank) || sp > ap || sp > tank) + { + return false; + } + break; + case CLASS_HUNTER: + case CLASS_ROGUE: + if (!ap || sp > ap || sp > tank) + { + return false; + } + break; + } + + return sp || ap || tank; +} + +void PlayerbotFactory::AddItemStats(uint32 mod, uint8 &sp, uint8 &ap, uint8 &tank) +{ + switch (mod) + { + case ITEM_MOD_HEALTH: + case ITEM_MOD_STAMINA: + case ITEM_MOD_MANA: + case ITEM_MOD_INTELLECT: + case ITEM_MOD_SPIRIT: + sp++; + break; + } + + switch (mod) + { + case ITEM_MOD_AGILITY: + case ITEM_MOD_STRENGTH: + case ITEM_MOD_HEALTH: + case ITEM_MOD_STAMINA: + tank++; + break; + } + + switch (mod) + { + case ITEM_MOD_HEALTH: + case ITEM_MOD_STAMINA: + case ITEM_MOD_AGILITY: + case ITEM_MOD_STRENGTH: + ap++; + break; + } +} + +bool PlayerbotFactory::CanEquipWeapon(ItemPrototype const* proto) +{ + switch(bot->getClass()) + { + case CLASS_PRIEST: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE) + return false; + break; + case CLASS_MAGE: + case CLASS_WARLOCK: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND && + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) + return false; + break; + case CLASS_WARRIOR: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && + proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + return false; + break; + case CLASS_PALADIN: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) + return false; + break; + case CLASS_SHAMAN: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + return false; + break; + case CLASS_DRUID: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && + proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF) + return false; + break; + case CLASS_HUNTER: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && + proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW) + return false; + break; + case CLASS_ROGUE: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && + proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && + proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && + proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN) + return false; + break; + } + + return true; +} + +bool PlayerbotFactory::CanEquipItem(ItemPrototype const* proto, uint32 desiredQuality) +{ + if (proto->Duration & 0x80000000) + { + return false; + } + + if (proto->Quality != desiredQuality) + { + return false; + } + + if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE) + { + return false; + } + + if (proto->Class == ITEM_CLASS_CONTAINER) + { + return true; + } + + uint32 requiredLevel = proto->RequiredLevel; + if (!requiredLevel) + { + return false; + } + + uint32 level = bot->getLevel(); + uint32 delta = 2; + if (level < 15) + { + delta = urand(7, 15); + } + else if (proto->Class == ITEM_CLASS_WEAPON || proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + delta = urand(2, 3); + } + else if (!(level % 10) || (level % 10) == 9) + { + delta = 2; + } + else if (level < 40) + { + delta = urand(5, 10); + } + else if (level < 60) + { + delta = urand(3, 7); + } + else if (level < 70) + { + delta = urand(2, 5); + } + else if (level < 80) + { + delta = urand(2, 4); + } + + if (desiredQuality > ITEM_QUALITY_NORMAL && + (requiredLevel > level || requiredLevel < level - delta)) + return false; + + for (uint32 gap = 60; gap <= 80; gap += 10) + { + if (level > gap && requiredLevel <= gap) + { + return false; + } + } + + return true; +} + +void PlayerbotFactory::InitEquipment(bool incremental) +{ + DestroyItemsVisitor visitor(bot); + IterateItems(&visitor, ITERATE_ALL_ITEMS); + + map > items; + for(uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) + { + continue; + } + + uint32 desiredQuality = itemQuality; + if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) { + { + desiredQuality--; + } + } + + do + { + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Class != ITEM_CLASS_WEAPON && + proto->Class != ITEM_CLASS_ARMOR && + proto->Class != ITEM_CLASS_CONTAINER && + proto->Class != ITEM_CLASS_PROJECTILE) + continue; + + if (!CanEquipItem(proto, desiredQuality)) + { + continue; + } + + if (proto->Class == ITEM_CLASS_ARMOR && ( + slot == EQUIPMENT_SLOT_HEAD || + slot == EQUIPMENT_SLOT_SHOULDERS || + slot == EQUIPMENT_SLOT_CHEST || + slot == EQUIPMENT_SLOT_WAIST || + slot == EQUIPMENT_SLOT_LEGS || + slot == EQUIPMENT_SLOT_FEET || + slot == EQUIPMENT_SLOT_WRISTS || + slot == EQUIPMENT_SLOT_HANDS) && !CanEquipArmor(proto)) + continue; + + if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto)) + { + continue; + } + + if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON) + { + continue; + } + if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_PALADIN && proto->SubClass != ITEM_SUBCLASS_ARMOR_SHIELD) + { + continue; + } + + uint16 dest = 0; + if (CanEquipUnseenItem(slot, dest, itemId)) + { + items[slot].push_back(itemId); + } + } + } while (items[slot].empty() && desiredQuality-- > ITEM_QUALITY_NORMAL); + } + + for(uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) + { + continue; + } + + vector& ids = items[slot]; + if (ids.empty()) + { + sLog.outDebug( "%s: no items to equip for slot %d", bot->GetName(), slot); + continue; + } + + for (int attempts = 0; attempts < 15; attempts++) + { + uint32 index = urand(0, ids.size() - 1); + uint32 newItemId = ids[index]; + Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + + if (incremental && !IsDesiredReplacement(oldItem)) { + { + continue; + } + } + + uint16 dest; + if (!CanEquipUnseenItem(slot, dest, newItemId)) + { + continue; + } + + if (oldItem) + { + bot->RemoveItem(INVENTORY_SLOT_BAG_0, slot, true); + oldItem->DestroyForPlayer(bot); + } + + Item* newItem = bot->EquipNewItem(dest, newItemId, true); + if (newItem) + { + newItem->AddToWorld(); + newItem->AddToUpdateQueueOf(bot); + bot->AutoUnequipOffhandIfNeed(); + EnchantItem(newItem); + break; + } + } + } +} + +bool PlayerbotFactory::IsDesiredReplacement(Item* item) +{ + if (!item) + { + return true; + } + + ItemPrototype const* proto = item->GetProto(); + int delta = 1 + (80 - bot->getLevel()) / 10; + return (int)bot->getLevel() - (int)proto->RequiredLevel > delta; +} + +void PlayerbotFactory::InitSecondEquipmentSet() +{ + if (bot->getClass() == CLASS_MAGE || bot->getClass() == CLASS_WARLOCK || bot->getClass() == CLASS_PRIEST) + { + return; + } + + map > items; + + uint32 desiredQuality = itemQuality; + while (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) { + desiredQuality--; + } + + do + { + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (!CanEquipItem(proto, desiredQuality)) + { + continue; + } + + if (proto->Class == ITEM_CLASS_WEAPON) + { + if (!CanEquipWeapon(proto)) + { + continue; + } + + Item* existingItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND); + if (existingItem) + { + switch (existingItem->GetProto()->SubClass) + { + case ITEM_SUBCLASS_WEAPON_AXE: + case ITEM_SUBCLASS_WEAPON_DAGGER: + case ITEM_SUBCLASS_WEAPON_FIST: + case ITEM_SUBCLASS_WEAPON_MACE: + case ITEM_SUBCLASS_WEAPON_SWORD: + if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || + proto->SubClass == ITEM_SUBCLASS_WEAPON_FIST || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || + proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD) + continue; + break; + default: + if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && + proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && + proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD) + continue; + break; + } + } + } + else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + if (!CanEquipArmor(proto)) + { + continue; + } + + Item* existingItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND); + if (existingItem && existingItem->GetProto()->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD) + { + continue; + } + } + else + { + continue; + } + + items[proto->Class].push_back(itemId); + } + } while (items[ITEM_CLASS_ARMOR].empty() && items[ITEM_CLASS_WEAPON].empty() && desiredQuality-- > ITEM_QUALITY_NORMAL); + + for (map >::iterator i = items.begin(); i != items.end(); ++i) + { + vector& ids = i->second; + if (ids.empty()) + { + sLog.outDebug( "%s: no items to make second equipment set for slot %d", bot->GetName(), i->first); + continue; + } + + for (int attempts = 0; attempts < 15; attempts++) + { + uint32 index = urand(0, ids.size() - 1); + uint32 newItemId = ids[index]; + + Item* newItem = bot->StoreNewItemInInventorySlot(newItemId, 1); + if (newItem) + { + EnchantItem(newItem); + newItem->AddToWorld(); + newItem->AddToUpdateQueueOf(bot); + break; + } + } + } +} + +void PlayerbotFactory::InitBags() +{ + vector ids; + + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto || proto->Class != ITEM_CLASS_CONTAINER) + { + continue; + } + + if (!CanEquipItem(proto, ITEM_QUALITY_NORMAL)) + { + continue; + } + + ids.push_back(itemId); + } + + if (ids.empty()) + { + sLog.outError("%s: no bags found", bot->GetName()); + return; + } + + for (uint8 slot = INVENTORY_SLOT_BAG_START; slot < INVENTORY_SLOT_BAG_END; ++slot) + { + for (int attempts = 0; attempts < 15; attempts++) + { + uint32 index = urand(0, ids.size() - 1); + uint32 newItemId = ids[index]; + + uint16 dest; + if (!CanEquipUnseenItem(slot, dest, newItemId)) + { + continue; + } + + Item* newItem = bot->EquipNewItem(dest, newItemId, true); + if (newItem) + { + newItem->AddToWorld(); + newItem->AddToUpdateQueueOf(bot); + break; + } + } + } +} + +void PlayerbotFactory::EnchantItem(Item* item) +{ + if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance) + { + return; + } + + if (bot->getLevel() < urand(40, 50)) + { + return; + } + + ItemPrototype const* proto = item->GetProto(); + int32 itemLevel = proto->ItemLevel; + + vector ids; + for (uint32 id = 0; id < sSpellStore.GetNumRows(); ++id) + { + SpellEntry const *entry = sSpellStore.LookupEntry(id); + if (!entry) + { + continue; + } + + int32 requiredLevel = (int32)entry->baseLevel; + if (requiredLevel && (requiredLevel > itemLevel || requiredLevel < itemLevel - 35)) + { + continue; + } + + if (entry->maxLevel && level > entry->maxLevel) + { + continue; + } + + uint32 spellLevel = entry->spellLevel; + if (spellLevel && (spellLevel > level || spellLevel < level - 10)) + { + continue; + } + + for (int j = 0; j < 3; ++j) + { + if (entry->Effect[j] != SPELL_EFFECT_ENCHANT_ITEM) + { + continue; + } + + uint32 enchant_id = entry->EffectMiscValue[j]; + if (!enchant_id) + { + continue; + } + + SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchant || enchant->slot != PERM_ENCHANTMENT_SLOT) + { + continue; + } + + const SpellEntry *enchantSpell = sSpellStore.LookupEntry(enchant->spellid[0]); + if (!enchantSpell || (enchantSpell->spellLevel && enchantSpell->spellLevel > level)) + { + continue; + } + + uint8 sp = 0, ap = 0, tank = 0; + for (int i = 0; i < 3; ++i) + { + if (enchant->type[i] != ITEM_ENCHANTMENT_TYPE_STAT) + { + continue; + } + + AddItemStats(enchant->spellid[i], sp, ap, tank); + } + + if (!CheckItemStats(sp, ap, tank)) + { + continue; + } + + if (!item->IsFitToSpellRequirements(entry)) + { + continue; + } + + ids.push_back(enchant_id); + } + } + + if (ids.empty()) + { + sLog.outDebug( "%s: no enchantments found for item %d", bot->GetName(), item->GetProto()->ItemId); + return; + } + + int index = urand(0, ids.size() - 1); + uint32 id = ids[index]; + + SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(id); + if (!enchant) + { + return; + } + + bot->ApplyEnchantment(item, PERM_ENCHANTMENT_SLOT, false); + item->SetEnchantment(PERM_ENCHANTMENT_SLOT, id, 0, 0); + bot->ApplyEnchantment(item, PERM_ENCHANTMENT_SLOT, true); +} + +bool PlayerbotFactory::CanEquipUnseenItem(uint8 slot, uint16 &dest, uint32 item) +{ + dest = 0; + Item *pItem = Item::CreateItem(item, 1, bot); + if (pItem) + { + InventoryResult result = bot->CanEquipItem(slot, dest, pItem, true, false); + pItem->RemoveFromUpdateQueueOf(bot); + delete pItem; + return result == EQUIP_ERR_OK; + } + + return false; +} + +#define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) +void PlayerbotFactory::InitTradeSkills() +{ + for (int i = 0; i < sizeof(tradeSkills) / sizeof(uint32); ++i) + { + bot->SetSkill(tradeSkills[i], 0, 0, 0); + } + + bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0); + bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0); + + vector firstSkills; + vector secondSkills; + switch (bot->getClass()) + { + case CLASS_WARRIOR: + case CLASS_PALADIN: + firstSkills.push_back(SKILL_MINING); + secondSkills.push_back(SKILL_BLACKSMITHING); + secondSkills.push_back(SKILL_ENGINEERING); + break; + case CLASS_SHAMAN: + case CLASS_DRUID: + case CLASS_HUNTER: + case CLASS_ROGUE: + firstSkills.push_back(SKILL_SKINNING); + secondSkills.push_back(SKILL_LEATHERWORKING); + break; + default: + firstSkills.push_back(SKILL_TAILORING); + secondSkills.push_back(SKILL_ENCHANTING); + } + + SetRandomSkill(SKILL_FIRST_AID); + SetRandomSkill(SKILL_FISHING); + SetRandomSkill(SKILL_COOKING); + + switch (urand(0, 3)) + { + case 0: + SetRandomSkill(SKILL_HERBALISM); + SetRandomSkill(SKILL_ALCHEMY); + break; + case 1: + SetRandomSkill(SKILL_HERBALISM); + break; + case 2: + SetRandomSkill(SKILL_MINING); + SetRandomSkill(SKILL_JEWELCRAFTING); + break; + case 3: + SetRandomSkill(firstSkills[urand(0, firstSkills.size() - 1)]); + SetRandomSkill(secondSkills[urand(0, secondSkills.size() - 1)]); + break; + } +} + +void PlayerbotFactory::UpdateTradeSkills() +{ + for (int i = 0; i < sizeof(tradeSkills) / sizeof(uint32); ++i) + { + if (bot->GetSkillValue(tradeSkills[i]) == 1) + { + bot->SetSkill(tradeSkills[i], 0, 0, 0); + } + } +} + +void PlayerbotFactory::InitSkills() //Cosine +{ + uint32 maxValue = level * 5; + if (bot->getLevel() >= 70) + { + bot->SetSkill(SKILL_RIDING, 300, 300); + } + else if (bot->getLevel() >= 60) + { + bot->SetSkill(SKILL_RIDING, 225, 225); + } + else if (bot->getLevel() >= 40) + { + bot->SetSkill(SKILL_RIDING, 150, 150); + } + else if (bot->getLevel() >= 20) + { + bot->SetSkill(SKILL_RIDING, 75, 75); + } + else + { + bot->SetSkill(SKILL_RIDING, 0, 0); + } + + uint32 skillLevel = bot->getLevel() < 40 ? 0 : 1; + switch (bot->getClass()) + { + case CLASS_DRUID: + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_2H_MACES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_POLEARMS); + SetRandomSkill(SKILL_FIST_WEAPONS); + break; + case CLASS_WARRIOR: + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_AXES); + SetRandomSkill(SKILL_BOWS); + SetRandomSkill(SKILL_GUNS); + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_2H_SWORDS); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_2H_MACES); + SetRandomSkill(SKILL_2H_AXES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_CROSSBOWS); + SetRandomSkill(SKILL_POLEARMS); + SetRandomSkill(SKILL_FIST_WEAPONS); + SetRandomSkill(SKILL_THROWN); + break; + case CLASS_PALADIN: + bot->SetSkill(SKILL_PLATE_MAIL, 0, skillLevel, skillLevel); + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_AXES); + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_2H_SWORDS); + SetRandomSkill(SKILL_2H_MACES); + SetRandomSkill(SKILL_2H_AXES); + SetRandomSkill(SKILL_POLEARMS); + break; + case CLASS_PRIEST: + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_WANDS); + break; + case CLASS_SHAMAN: + SetRandomSkill(SKILL_AXES); + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_2H_MACES); + SetRandomSkill(SKILL_2H_AXES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_FIST_WEAPONS); + break; + case CLASS_MAGE: + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_WANDS); + break; + case CLASS_WARLOCK: + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_WANDS); + break; + case CLASS_HUNTER: + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_AXES); + SetRandomSkill(SKILL_BOWS); + SetRandomSkill(SKILL_GUNS); + SetRandomSkill(SKILL_2H_SWORDS); + SetRandomSkill(SKILL_STAVES); + SetRandomSkill(SKILL_2H_AXES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_CROSSBOWS); + SetRandomSkill(SKILL_POLEARMS); + SetRandomSkill(SKILL_FIST_WEAPONS); + SetRandomSkill(SKILL_THROWN); + bot->SetSkill(SKILL_MAIL, 0, skillLevel, skillLevel); + break; + case CLASS_ROGUE: + SetRandomSkill(SKILL_SWORDS); + SetRandomSkill(SKILL_AXES); + SetRandomSkill(SKILL_BOWS); + SetRandomSkill(SKILL_GUNS); + SetRandomSkill(SKILL_MACES); + SetRandomSkill(SKILL_DAGGERS); + SetRandomSkill(SKILL_CROSSBOWS); + SetRandomSkill(SKILL_FIST_WEAPONS); + SetRandomSkill(SKILL_THROWN); + } +} + +void PlayerbotFactory::SetRandomSkill(uint16 id) +{ + uint32 maxValue = level * 5; + uint32 curValue = urand(maxValue - level, maxValue); + bot->SetSkill(id, curValue, maxValue); + +} + +void PlayerbotFactory::InitAvailableSpells() +{ + bot->learnDefaultSpells(); + + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co) + { + continue; + } + + if (co->TrainerType != TRAINER_TYPE_TRADESKILLS && co->TrainerType != TRAINER_TYPE_CLASS) + { + continue; + } + + if (co->TrainerType == TRAINER_TYPE_CLASS && co->TrainerClass != bot->getClass()) + { + continue; + } + + uint32 trainerId = co->TrainerTemplateId; + if (!trainerId) + { + trainerId = co->Entry; + } + + TrainerSpellData const* trainer_spells = sObjectMgr.GetNpcTrainerTemplateSpells(trainerId); + if (!trainer_spells) + { + trainer_spells = sObjectMgr.GetNpcTrainerSpells(trainerId); + } + + if (!trainer_spells) + { + continue; + } + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell) + { + continue; + } + + uint32 reqLevel = 0; + + reqLevel = tSpell->isProvidedReqLevel ? tSpell->reqLevel : std::max(reqLevel, tSpell->reqLevel); + TrainerSpellState state = bot->GetTrainerSpellState(tSpell, reqLevel); + if (state != TRAINER_SPELL_GREEN) + { + continue; + } + + bot->learnSpell(tSpell->spell, false); + } + } +} + + +void PlayerbotFactory::InitSpecialSpells() +{ + for (list::iterator i = sPlayerbotAIConfig.randomBotSpellIds.begin(); i != sPlayerbotAIConfig.randomBotSpellIds.end(); ++i) + { + uint32 spellId = *i; + bot->learnSpell(spellId, false); + } +} + +void PlayerbotFactory::InitTalents(uint32 specNo) +{ + uint32 classMask = bot->getClassMask(); + + map > spells; + for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i) + { + TalentEntry const *talentInfo = sTalentStore.LookupEntry(i); + if(!talentInfo) + { + continue; + } + + TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab ); + if(!talentTabInfo || talentTabInfo->tabpage != specNo) + { + continue; + } + + if( (classMask & talentTabInfo->ClassMask) == 0 ) + { + continue; + } + + spells[talentInfo->Row].push_back(talentInfo); + } + + uint32 freePoints = bot->GetFreeTalentPoints(); + for (map >::iterator i = spells.begin(); i != spells.end(); ++i) + { + vector &spells = i->second; + if (spells.empty()) + { + sLog.outError("%s: No spells for talent row %d", bot->GetName(), i->first); + continue; + } + + int attemptCount = 0; + while (!spells.empty() && (int)freePoints - (int)bot->GetFreeTalentPoints() < 5 && attemptCount++ < 3 && bot->GetFreeTalentPoints()) + { + int index = urand(0, spells.size() - 1); + TalentEntry const *talentInfo = spells[index]; + for (int rank = 0; rank < MAX_TALENT_RANK && bot->GetFreeTalentPoints(); ++rank) + { + uint32 spellId = talentInfo->RankID[rank]; + if (!spellId) + { + continue; + } + bot->learnSpell(spellId, false); + bot->UpdateFreeTalentPoints(false); + } + spells.erase(spells.begin() + index); + } + + freePoints = bot->GetFreeTalentPoints(); + } +} + +ObjectGuid PlayerbotFactory::GetRandomBot() +{ + vector guids; + for (list::iterator i = sPlayerbotAIConfig.randomBotAccounts.begin(); i != sPlayerbotAIConfig.randomBotAccounts.end(); i++) + { + uint32 accountId = *i; + if (!sAccountMgr.GetCharactersCount(accountId)) + { + continue; + } + + QueryResult *result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE account = '%u'", accountId); + if (!result) + { + continue; + } + + do + { + Field* fields = result->Fetch(); + ObjectGuid guid = ObjectGuid(fields[0].GetUInt64()); + if (!sObjectMgr.GetPlayer(guid)) + { + guids.push_back(guid); + } + } while (result->NextRow()); + + delete result; + } + + if (guids.empty()) + { + return ObjectGuid(); + } + + int index = urand(0, guids.size() - 1); + return guids[index]; +} + + +void AddPrevQuests(uint32 questId, list& questIds) +{ + Quest const *quest = sObjectMgr.GetQuestTemplate(questId); + for (Quest::PrevQuests::const_iterator iter = quest->prevQuests.begin(); iter != quest->prevQuests.end(); ++iter) + { + uint32 prevId = abs(*iter); + AddPrevQuests(prevId, questIds); + questIds.remove(prevId); + questIds.push_back(prevId); + } +} + +void PlayerbotFactory::InitQuests() +{ + if (classQuestIds.empty()) + { + ObjectMgr::QuestMap const& questTemplates = sObjectMgr.GetQuestTemplates(); + for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i) + { + uint32 questId = i->first; + Quest const *quest = i->second; + + if (!quest->GetRequiredClasses() || quest->IsRepeatable()) + { + continue; + } + + AddPrevQuests(questId, classQuestIds); + classQuestIds.remove(questId); + classQuestIds.push_back(questId); + } + } + + int count = 0; + for (list::iterator i = classQuestIds.begin(); i != classQuestIds.end(); ++i) + { + uint32 questId = *i; + Quest const *quest = sObjectMgr.GetQuestTemplate(questId); + + if (!bot->SatisfyQuestClass(quest, false) || + quest->GetMinLevel() > bot->getLevel() || + !bot->SatisfyQuestRace(quest, false)) + continue; + + bot->SetQuestStatus(questId, QUEST_STATUS_COMPLETE); + bot->RewardQuest(quest, 0, bot, false); + if (!(count++ % 10)) + { + ClearInventory(); + } + } + ClearInventory(); +} + +void PlayerbotFactory::ClearInventory() +{ + DestroyItemsVisitor visitor(bot); + IterateItems(&visitor); +} + +void PlayerbotFactory::InitAmmo() +{ + if (bot->getClass() != CLASS_HUNTER && bot->getClass() != CLASS_ROGUE && bot->getClass() != CLASS_WARRIOR) + { + return; + } + + Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); + if (!pItem) + { + return; + } + + uint32 subClass = 0; + switch (pItem->GetProto()->SubClass) + { + case ITEM_SUBCLASS_WEAPON_GUN: + subClass = ITEM_SUBCLASS_BULLET; + break; + case ITEM_SUBCLASS_WEAPON_BOW: + case ITEM_SUBCLASS_WEAPON_CROSSBOW: + subClass = ITEM_SUBCLASS_ARROW; + break; + } + + if (!subClass) + { + return; + } + + QueryResult* results = WorldDatabase.PQuery("select max(entry), max(RequiredLevel) from item_template where class = '%u' and subclass = '%u' and RequiredLevel <= '%u'", + ITEM_CLASS_PROJECTILE, subClass, bot->getLevel()); + if (!results) + { + return; + } + + Field* fields = results->Fetch(); + if (fields) + { + uint32 entry = fields[0].GetUInt32(); + for (int i = 0; i < 10; i++) + { + Item* newItem = bot->StoreNewItemInInventorySlot(entry, 200); + if (newItem) + { + newItem->AddToUpdateQueueOf(bot); + } + } + bot->SetAmmo(entry); + } + + delete results; +} + +void PlayerbotFactory::InitMounts() +{ + map > spells; + + for (uint32 spellId = 0; spellId < sSpellStore.GetNumRows(); ++spellId) + { + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo || spellInfo->EffectApplyAuraName[0] != SPELL_AURA_MOUNTED) + { + continue; + } + + if (GetSpellCastTime(spellInfo) < 500 || GetSpellDuration(spellInfo) != -1) + { + continue; + } + + int32 effect = max(spellInfo->EffectBasePoints[1], spellInfo->EffectBasePoints[2]); + if (effect < 50) + { + continue; + } + + spells[effect].push_back(spellId); + } + + for (uint32 type = 0; type < 2; ++type) + { + for (map >::iterator i = spells.begin(); i != spells.end(); ++i) + { + int32 effect = i->first; + vector& ids = i->second; + uint32 index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + continue; + } + + bot->learnSpell(ids[index], false); + } + } +} + +void PlayerbotFactory::InitPotions() +{ + map > items; + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Class != ITEM_CLASS_CONSUMABLE || + proto->SubClass != ITEM_SUBCLASS_POTION || + proto->Spells[0].SpellCategory != 4 || + proto->Bonding != NO_BIND) + continue; + + if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10) + { + continue; + } + + if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill)) + { + continue; + } + + if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank) + { + continue; + } + + for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) + { + const SpellEntry* const spellInfo = sSpellStore.LookupEntry(proto->Spells[j].SpellId); + if (!spellInfo) + { + continue; + } + + for (int i = 0 ; i < 3; i++) + { + if (spellInfo->Effect[i] == SPELL_EFFECT_HEAL || spellInfo->Effect[i] == SPELL_EFFECT_ENERGIZE) + { + items[spellInfo->Effect[i]].push_back(itemId); + break; + } + } + } + } + + uint32 effects[] = { SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE }; + for (int i = 0; i < sizeof(effects) / sizeof(uint32); ++i) + { + uint32 effect = effects[i]; + vector& ids = items[effect]; + uint32 index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + continue; + } + + uint32 itemId = ids[index]; + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + Item* newItem = bot->StoreNewItemInInventorySlot(itemId, urand(1, proto->GetMaxStackSize())); + if (newItem) + { + newItem->AddToUpdateQueueOf(bot); + } + } +} + +void PlayerbotFactory::InitFood() +{ + map > items; + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Class != ITEM_CLASS_CONSUMABLE || + proto->SubClass != ITEM_SUBCLASS_FOOD || + (proto->Spells[0].SpellCategory != 11 && proto->Spells[0].SpellCategory != 59) || + proto->Bonding != NO_BIND) + continue; + + if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10) + { + continue; + } + + if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill)) + { + continue; + } + + if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank) + { + continue; + } + + items[proto->Spells[0].SpellCategory].push_back(itemId); + } + + uint32 categories[] = { 11, 59 }; + for (int i = 0; i < sizeof(categories) / sizeof(uint32); ++i) + { + uint32 category = categories[i]; + vector& ids = items[category]; + uint32 index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + continue; + } + + uint32 itemId = ids[index]; + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + Item* newItem = bot->StoreNewItemInInventorySlot(itemId, urand(1, proto->GetMaxStackSize())); + if (newItem) + { + newItem->AddToUpdateQueueOf(bot); + } + } +} + + +void PlayerbotFactory::CancelAuras() +{ + bot->RemoveAllAuras(); +} + +void PlayerbotFactory::InitInventory() +{ + InitInventoryTrade(); + InitInventoryEquip(); + InitInventorySkill(); +} + +void PlayerbotFactory::InitInventorySkill() +{ + if (bot->HasSkill(SKILL_MINING)) { + { + StoreItem(2901, 1); // Mining Pick + } + } + if (bot->HasSkill(SKILL_BLACKSMITHING) || bot->HasSkill(SKILL_ENGINEERING)) { + { + StoreItem(5956, 1); // Blacksmith Hammer + } + } + if (bot->HasSkill(SKILL_ENGINEERING)) { + { + StoreItem(6219, 1); // Arclight Spanner + } + } + if (bot->HasSkill(SKILL_JEWELCRAFTING)) { + { + StoreItem(20815, 1); // Jeweler's Kit + } + StoreItem(20824, 1); // Simple Grinder + } + if (bot->HasSkill(SKILL_ENCHANTING)) { + { + StoreItem(16207, 1); // Runed Arcanite Rod + } + } + if (bot->HasSkill(SKILL_SKINNING)) { + { + StoreItem(7005, 1); // Skinning Knife + } + } +} + +Item* PlayerbotFactory::StoreItem(uint32 itemId, uint32 count) +{ + ItemPosCountVec sDest; + InventoryResult msg = bot->CanStoreNewItem(INVENTORY_SLOT_BAG_0, NULL_SLOT, sDest, itemId, count); + if (msg != EQUIP_ERR_OK) + { + return NULL; + } + + return bot->StoreNewItem(sDest, itemId, true, Item::GenerateItemRandomPropertyId(itemId)); +} + +void PlayerbotFactory::InitInventoryTrade() +{ + vector ids; + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Class != ITEM_CLASS_TRADE_GOODS || proto->Bonding != NO_BIND) + { + continue; + } + + if (proto->ItemLevel < bot->getLevel()) + { + continue; + } + + if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10) + { + continue; + } + + if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill)) + { + continue; + } + + ids.push_back(itemId); + } + + if (ids.empty()) + { + sLog.outError("No trade items available for bot %s (%d level)", bot->GetName(), bot->getLevel()); + return; + } + + uint32 index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + return; + } + + uint32 itemId = ids[index]; + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return; + } + + uint32 count = 1, stacks = 1; + switch (proto->Quality) + { + case ITEM_QUALITY_NORMAL: + count = proto->GetMaxStackSize(); + stacks = urand(1, 7) / auctionbot.GetRarityPriceMultiplier(proto); + break; + case ITEM_QUALITY_UNCOMMON: + stacks = 1; + count = urand(1, proto->GetMaxStackSize()); + break; + case ITEM_QUALITY_RARE: + stacks = 1; + count = urand(1, min(uint32(3), proto->GetMaxStackSize())); + break; + } + + for (uint32 i = 0; i < stacks; i++) + { + StoreItem(itemId, count); + } +} + +void PlayerbotFactory::InitInventoryEquip() +{ + vector ids; + + uint32 desiredQuality = itemQuality; + if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) { + { + desiredQuality--; + } + } + + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Class != ITEM_CLASS_ARMOR && proto->Class != ITEM_CLASS_WEAPON || (proto->Bonding == BIND_WHEN_PICKED_UP || + proto->Bonding == BIND_WHEN_USE)) + continue; + + if (proto->Class == ITEM_CLASS_ARMOR && !CanEquipArmor(proto)) + { + continue; + } + + if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto)) + { + continue; + } + + if (!CanEquipItem(proto, desiredQuality)) + { + continue; + } + + ids.push_back(itemId); + } + + int maxCount = urand(0, 3); + int count = 0; + for (int attempts = 0; attempts < 15; attempts++) + { + uint32 index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + continue; + } + + uint32 itemId = ids[index]; + if (StoreItem(itemId, 1) && count++ >= maxCount) + { + break; + } + } +} + +void PlayerbotFactory::InitGuild() +{ + if (bot->GetGuildId()) + { + return; + } + + if (sPlayerbotAIConfig.randomBotGuilds.empty()) + { + RandomPlayerbotFactory::CreateRandomGuilds(); + } + + vector guilds; + for(list::iterator i = sPlayerbotAIConfig.randomBotGuilds.begin(); i != sPlayerbotAIConfig.randomBotGuilds.end(); ++i) + { + guilds.push_back(*i); + } + + if (guilds.empty()) + { + sLog.outError("No random guilds available"); + return; + } + + int index = urand(0, guilds.size() - 1); + uint32 guildId = guilds[index]; + Guild* guild = sGuildMgr.GetGuildById(guildId); + if (!guild) + { + sLog.outError("Invalid guild %u", guildId); + return; + } + + if (guild->GetMemberSize() < 10) + { + guild->AddMember(bot->GetObjectGuid(), urand(GR_OFFICER, GR_INITIATE)); + } +} diff --git a/src/modules/Bots/playerbot/PlayerbotFactory.h b/src/modules/Bots/playerbot/PlayerbotFactory.h new file mode 100644 index 000000000..28935c9fe --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotFactory.h @@ -0,0 +1,68 @@ +#pragma once + +#include "strategy/actions/InventoryAction.h" + +class Player; +class PlayerbotMgr; +class ChatHandler; + +using namespace std; +using ai::InventoryAction; + +class PlayerbotFactory : public InventoryAction +{ +public: + PlayerbotFactory(Player* bot, uint32 level, uint32 itemQuality = 0) : + bot(bot), level(level), itemQuality(itemQuality), InventoryAction(bot->GetPlayerbotAI(), "factory") {} + + static ObjectGuid GetRandomBot(); + void CleanRandomize(); + void Randomize(); + void Refresh(); + +private: + void Randomize(bool incremental); + void Prepare(); + void InitSecondEquipmentSet(); + void InitEquipment(bool incremental); + bool CanEquipItem(ItemPrototype const* proto, uint32 desiredQuality); + bool CanEquipUnseenItem(uint8 slot, uint16 &dest, uint32 item); + void InitSkills(); + void InitTradeSkills(); + void UpdateTradeSkills(); + void SetRandomSkill(uint16 id); + void InitSpells(); + void ClearSpells(); + void InitAvailableSpells(); + void InitSpecialSpells(); + void InitTalents(); + void InitTalents(uint32 specNo); + void InitQuests(); + void InitPet(); + void ClearInventory(); + void InitAmmo(); + void InitMounts(); + void InitPotions(); + void InitFood(); + bool CanEquipArmor(ItemPrototype const* proto); + bool CanEquipWeapon(ItemPrototype const* proto); + void EnchantItem(Item* item); + void AddItemStats(uint32 mod, uint8 &sp, uint8 &ap, uint8 &tank); + bool CheckItemStats(uint8 sp, uint8 ap, uint8 tank); + void CancelAuras(); + bool IsDesiredReplacement(Item* item); + void InitBags(); + void InitInventory(); + void InitInventoryTrade(); + void InitInventoryEquip(); + void InitInventorySkill(); + Item* StoreItem(uint32 itemId, uint32 count); + void InitGuild(); + +private: + Player* bot; + uint32 level; + uint32 itemQuality; + static uint32 tradeSkills[]; + static list classQuestIds; +}; diff --git a/src/modules/Bots/playerbot/PlayerbotMgr.cpp b/src/modules/Bots/playerbot/PlayerbotMgr.cpp new file mode 100644 index 000000000..44c1c1c67 --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotMgr.cpp @@ -0,0 +1,621 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotDbStore.h" +#include "PlayerbotFactory.h" +#include "RandomPlayerbotMgr.h" + + +class LoginQueryHolder; +class CharacterHandler; + +PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase() +{ + for (uint32 spellId = 0; spellId < sSpellStore.GetNumRows(); spellId++) + { + sSpellStore.LookupEntry(spellId); + } +} + +PlayerbotHolder::~PlayerbotHolder() +{ + LogoutAllBots(); +} + + +void PlayerbotHolder::UpdateAIInternal(__attribute__((unused)) uint32 elapsed) +{ +} + +void PlayerbotHolder::UpdateSessions(__attribute__((unused)) uint32 elapsed) +{ + for (auto itr : playerBots) + { + Player* const bot = itr.second; + if (bot->IsBeingTeleported()) + { + bot->GetPlayerbotAI()->HandleTeleportAck(); + } + else if (bot->IsInWorld()) + { + bot->GetSession()->HandleBotPackets(); + } + } +} + +void PlayerbotHolder::LogoutAllBots() +{ + while (!playerBots.empty()) + { + LogoutPlayerBot(playerBots.begin()->second->GetObjectGuid().GetRawValue()); + } +} + +void PlayerbotHolder::LogoutPlayerBot(uint64 guid) +{ + Player* bot = GetBotByGUID(guid); + if (bot) + { + bot->GetPlayerbotAI()->TellMaster("Goodbye!"); + sPlayerbotDbStore.Save(bot->GetPlayerbotAI()); + DETAIL_LOG("Bot %s logged out", bot->GetName()); + + WorldSession * botWorldSessionPtr = bot->GetSession(); + playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object + delete botWorldSessionPtr; // finally delete the bot's WorldSession + } +} + +Player* PlayerbotHolder::GetBotByGUID(uint64 playerGuid) const +{ + auto bot = playerBots.find(playerGuid); + return (bot == playerBots.end()) ? 0 : bot->second; +} + +void PlayerbotHolder::OnBotLogin(Player * const bot) +{ + auto ai = new PlayerbotAI(bot); + bot->SetPlayerbotAI(ai); + OnBotLoginInternal(bot); + + playerBots[bot->GetObjectGuid().GetRawValue()] = bot; + + Player* master = ai->GetMaster(); + if (master) + { + ObjectGuid masterGuid = master->GetObjectGuid(); + if (master->GetGroup() && + ! master->GetGroup()->IsLeader(masterGuid)) + master->GetGroup()->ChangeLeader(masterGuid); + } + + Group *group = bot->GetGroup(); + if (group) + { + bool groupValid = false; + for (const auto& slot : group->GetMemberSlots()) + { + ObjectGuid member = slot.guid; + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(member); + if (!sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + groupValid = true; + break; + } + } + + if (!groupValid) + { + WorldPacket p; + string member = bot->GetName(); + p << uint32(PARTY_OP_LEAVE) << member << uint32(0); + bot->GetSession()->HandleGroupDisbandOpcode(p); + } + } + + ai->ResetStrategies(); + ai->TellMaster("Hello!"); + + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(bot->GetObjectGuid()); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + DETAIL_LOG("%zu/%d Bot %s logged in", playerBots.size(), sRandomPlayerbotMgr.GetMaxAllowedBotCount(), bot->GetName()); + } + else { + DETAIL_LOG("Bot %s logged in", bot->GetName()); + } +} + +string PlayerbotHolder::ProcessBotCommand(const string& cmd, ObjectGuid guid, bool admin, uint32 masterAccountId, uint32 masterGuildId) +{ + if (!sPlayerbotAIConfig.enabled || guid.IsEmpty()) + { + return "bot system is disabled"; + } + + uint32 botAccount = sObjectMgr.GetPlayerAccountIdByGUID(guid); + bool isRandomBot = sRandomPlayerbotMgr.IsRandomBot(guid); + bool isRandomAccount = sPlayerbotAIConfig.IsInRandomAccountList(botAccount); + bool isMasterAccount = (masterAccountId == botAccount); + + if (isRandomAccount && !isRandomBot && !admin) + { + if (sObjectMgr.GetPlayer(guid)->GetGuildId() != masterGuildId) + { + return "not in your guild"; + } + } + + if (!isRandomAccount && !isMasterAccount && !admin) + { + return "not in your account"; + } + + if (cmd == "add" || cmd == "login") + { + if (sObjectMgr.GetPlayer(guid)) + { + return "player already logged in"; + } + + AddPlayerBot(guid.GetRawValue(), masterAccountId); + return "ok"; + } + else if (cmd == "remove" || cmd == "logout" || cmd == "rm") + { + if (!sObjectMgr.GetPlayer(guid)) + { + return "player is offline"; + } + + if (!GetBotByGUID(guid.GetRawValue())) + { + return "not your bot"; + } + + LogoutPlayerBot(guid.GetRawValue()); + return "ok"; + } + + if (admin) + { + Player* bot = GetBotByGUID(guid.GetRawValue()); + if (!bot) + { + return "bot not found"; + } + + Player* master = bot->GetPlayerbotAI()->GetMaster(); + if (master) + { + if (cmd == "init=white" || cmd == "init=common") + { + PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_NORMAL); + factory.CleanRandomize(); + return "ok"; + } + else if (cmd == "init=green" || cmd == "init=uncommon") + { + PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_UNCOMMON); + factory.CleanRandomize(); + return "ok"; + } + else if (cmd == "init=blue" || cmd == "init=rare") + { + PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_RARE); + factory.CleanRandomize(); + return "ok"; + } + else if (cmd == "init=epic" || cmd == "init=purple") + { + PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_EPIC); + factory.CleanRandomize(); + return "ok"; + } + } + + if (cmd == "update") + { + PlayerbotFactory factory(bot, bot->getLevel()); + factory.Refresh(); + return "ok"; + } + else if (cmd == "random") + { + sRandomPlayerbotMgr.Randomize(bot); + return "ok"; + } + } + + return "unknown command"; +} + +bool PlayerbotMgr::HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args) +{ + if (!sPlayerbotAIConfig.enabled) + { + handler->PSendSysMessage("|cffff0000Playerbot system is currently disabled!"); + return false; + } + + WorldSession *m_session = handler->GetSession(); + + if (!m_session) + { + handler->PSendSysMessage("You may only add bots from an active session"); + return false; + } + + Player* player = m_session->GetPlayer(); + PlayerbotMgr* mgr = player->GetPlayerbotMgr(); + if (!mgr) + { + handler->PSendSysMessage("you cannot control bots yet"); + return false; + } + + list messages = mgr->HandlePlayerbotCommand(args, player); + if (messages.empty()) + { + return true; + } + + for (auto & message : messages) + { + handler->PSendSysMessage("%s", message.c_str()); + } + + return true; +} + +list PlayerbotHolder::HandlePlayerbotCommand(char const* args, Player* master) +{ + list messages; + + if (!*args) + { + messages.emplace_back("usage: list or add/init/remove PLAYERNAME"); + return messages; + } + + char *cmd = strtok(const_cast(args), " "); + char *charname = strtok(nullptr, " "); + if (!cmd) + { + messages.emplace_back("usage: list or add/init/remove PLAYERNAME"); + return messages; + } + + if (!strcmp(cmd, "list")) + { + messages.push_back(ListBots(master)); + return messages; + } + + if (!charname) + { + messages.emplace_back("usage: list or add/init/remove PLAYERNAME"); + return messages; + } + + std::string cmdStr = cmd; + std::string charnameStr = charname; + + set bots; + if (charnameStr == "*" && master) + { + Group* group = master->GetGroup(); + if (!group) + { + messages.emplace_back("you must be in group"); + return messages; + } + + Group::MemberSlotList slots = group->GetMemberSlots(); + for (const auto& slot : group->GetMemberSlots()) + { + ObjectGuid member = slot.guid; + + if (member.GetRawValue() == master->GetObjectGuid().GetRawValue()) + { + continue; + } + + string bot; + if (sObjectMgr.GetPlayerNameByGUID(member, bot)) + { + bots.insert(bot); + } + } + } + + if (charnameStr == "!" && master && master->GetSession()->GetSecurity() > SEC_GAMEMASTER) + { + for (auto i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + { + Player* bot = i->second; + if (bot && bot->IsInWorld()) + { + bots.insert(bot->GetName()); + } + } + } + + vector chars = split(charnameStr, ','); + for (const auto& s : chars) + { + uint32 accountId = GetAccountId(s); + if (!accountId) + { + bots.insert(s); + continue; + } + + QueryResult* results = CharacterDatabase.PQuery( + "SELECT name FROM characters WHERE account = '%u'", + accountId); + if (results) + { + do + { + Field* fields = results->Fetch(); + string charName = fields[0].GetString(); + bots.insert(charName); + } while (results->NextRow()); + + delete results; + } + } + + for (const auto& bot : bots) + { + ostringstream out; + out << cmdStr << ": " << bot << " - "; + + ObjectGuid member = sObjectMgr.GetPlayerGuidByName(bot); + if (!member) + { + out << "character not found"; + } + else if (master && member.GetRawValue() != master->GetObjectGuid().GetRawValue()) + { + out << ProcessBotCommand(cmdStr, member, + master->GetSession()->GetSecurity() >= SEC_GAMEMASTER, + master->GetSession()->GetAccountId(), + master->GetGuildId()); + } + else if (!master) + { + out << ProcessBotCommand(cmdStr, member, true, -1, -1); + } + + messages.push_back(out.str()); + } + + return messages; +} + +uint32 PlayerbotHolder::GetAccountId(const string& name) +{ + uint32 accountId = 0; + + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account WHERE username = '%s'", name.c_str()); + if(results) + { + Field* fields = results->Fetch(); + accountId = fields[0].GetUInt32(); + delete results; + } + + return accountId; +} + +string PlayerbotHolder::ListBots(Player* master) const +{ + set bots; + map classNames; + classNames[CLASS_DRUID] = "Druid"; + classNames[CLASS_DEATH_KNIGHT] = "Death Knight"; + classNames[CLASS_HUNTER] = "Hunter"; + classNames[CLASS_MAGE] = "Mage"; + classNames[CLASS_PALADIN] = "Paladin"; + classNames[CLASS_PRIEST] = "Priest"; + classNames[CLASS_ROGUE] = "Rogue"; + classNames[CLASS_SHAMAN] = "Shaman"; + classNames[CLASS_WARLOCK] = "Warlock"; + classNames[CLASS_WARRIOR] = "Warrior"; + + map online; + list names; + map classes; + + for (auto it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + string name = bot->GetName(); + bots.insert(name); + + names.push_back(name); + online[name] = "+"; + classes[name] = classNames[bot->getClass()]; + } + + if (master) + { + QueryResult* results = CharacterDatabase.PQuery("SELECT class,name FROM characters where account = '%u'", + master->GetSession()->GetAccountId()); + if (results) + { + do + { + Field* fields = results->Fetch(); + uint8 cls = fields[0].GetUInt8(); + string name = fields[1].GetString(); + if (bots.find(name) == bots.end() && name != master->GetSession()->GetPlayerName()) + { + names.push_back(name); + online[name] = "-"; + classes[name] = classNames[cls]; + } + } while (results->NextRow()); + delete results; + } + } + + names.sort(); + + ostringstream out; + bool first = true; + out << "Bot roster: "; + for (const auto& name : names) + { + if (first) + { + first = false; + } + else + { + out << ", "; + } + out << online[name] << name << " " << classes[name]; + } + + return out.str(); +} + + +PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master) +{ +} + +PlayerbotMgr::~PlayerbotMgr() +{ +} + +void PlayerbotMgr::UpdateAIInternal(__attribute__((unused)) uint32 elapsed) +{ + SetNextCheckDelay(sPlayerbotAIConfig.reactDelay); +} + +void PlayerbotMgr::HandleCommand(uint32 type, const string& text) +{ + Player *bmaster = GetMaster(); + if (!bmaster) + { + return; + } + + for (auto it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->HandleCommand(type, text, *bmaster); + } + + for (auto it = sRandomPlayerbotMgr.GetPlayerBotsBegin(); it != sRandomPlayerbotMgr.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetPlayerbotAI()->GetMaster() == bmaster) + { + bot->GetPlayerbotAI()->HandleCommand(type, text, *bmaster); + } + } +} + +void PlayerbotMgr::HandleMasterIncomingPacket(const WorldPacket& packet) +{ + for (auto it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->HandleMasterIncomingPacket(packet); + } + + for (auto it = sRandomPlayerbotMgr.GetPlayerBotsBegin(); it != sRandomPlayerbotMgr.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetPlayerbotAI()->GetMaster() == GetMaster()) + { + bot->GetPlayerbotAI()->HandleMasterIncomingPacket(packet); + } + } + + switch (packet.GetOpcode()) + { + // if master is logging out, log out all bots + case CMSG_LOGOUT_REQUEST: + { + LogoutAllBots(); + return; + } + } +} +void PlayerbotMgr::HandleMasterOutgoingPacket(const WorldPacket& packet) +{ + for (auto it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->GetPlayerbotAI()->HandleMasterOutgoingPacket(packet); + } + + for (auto it = sRandomPlayerbotMgr.GetPlayerBotsBegin(); it != sRandomPlayerbotMgr.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetPlayerbotAI()->GetMaster() == GetMaster()) + { + bot->GetPlayerbotAI()->HandleMasterOutgoingPacket(packet); + } + } +} + +void PlayerbotMgr::SaveToDB() +{ + for (auto it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + bot->SaveToDB(); + } + for (auto it = sRandomPlayerbotMgr.GetPlayerBotsBegin(); it != sRandomPlayerbotMgr.GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetPlayerbotAI()->GetMaster() == GetMaster()) + { + bot->SaveToDB(); + } + } +} + +void PlayerbotMgr::OnBotLoginInternal(Player * const bot) +{ + bot->GetPlayerbotAI()->SetMaster(master); + bot->GetPlayerbotAI()->ResetStrategies(); +} + +__attribute__((unused)) void PlayerbotMgr::OnPlayerLogin(Player* player) +{ + if (!sPlayerbotAIConfig.botAutologin) + { + return; + } + + uint32 accountId = player->GetSession()->GetAccountId(); + QueryResult* results = CharacterDatabase.PQuery( + "SELECT name FROM characters WHERE account = '%u'", + accountId); + if (results) + { + ostringstream out; out << "add "; + bool first = true; + do + { + Field* fields = results->Fetch(); + if (first) first = false; else out << ","; + { + out << fields[0].GetString(); + } + } while (results->NextRow()); + + delete results; + + HandlePlayerbotCommand(out.str().c_str(), player); + } +} diff --git a/src/modules/Bots/playerbot/PlayerbotMgr.h b/src/modules/Bots/playerbot/PlayerbotMgr.h new file mode 100644 index 000000000..2fc6c754e --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotMgr.h @@ -0,0 +1,74 @@ +#ifndef PLAYERBOTMGR_H +#define PLAYERBOTMGR_H + +#include "Common.h" +#include "PlayerbotAIBase.h" +#include "../botpch.h" + +class WorldPacket; +class Player; +class Unit; +class Object; +class Item; + +typedef map PlayerBotMap; + +class PlayerbotHolder : public PlayerbotAIBase +{ +public: + PlayerbotHolder(); + virtual ~PlayerbotHolder(); + + void AddPlayerBot(uint64 guid, uint32 masterAccountId); +// void HandlePlayerBotLoginCallback(QueryResult * dummy, SqlQueryHolder * holder); + + void LogoutPlayerBot(uint64 guid); + Player* GetBotByGUID (uint64 playerGuid) const; + PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return playerBots.begin(); } + PlayerBotMap::const_iterator GetPlayerBotsEnd() const { return playerBots.end(); } + + void UpdateAIInternal(__attribute__((unused)) uint32 elapsed) override; + void UpdateSessions(__attribute__((unused)) uint32 elapsed); + + void LogoutAllBots(); + void OnBotLogin(Player* bot); + + list HandlePlayerbotCommand(char const* args, Player* master = nullptr); + string ProcessBotCommand(const string& cmd, ObjectGuid guid, bool admin, uint32 masterAccountId, uint32 masterGuildId); + static uint32 GetAccountId(const string& name); + string ListBots(Player* master) const; + +protected: + __attribute__((unused)) virtual void OnBotLoginInternal(Player* bot) = 0; + +protected: + PlayerBotMap playerBots; +}; + +class PlayerbotMgr : public PlayerbotHolder +{ +public: + explicit PlayerbotMgr(Player* master); + ~PlayerbotMgr() override; + + static bool HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args); + void HandleMasterIncomingPacket(const WorldPacket& packet); + void HandleMasterOutgoingPacket(const WorldPacket& packet); + void HandleCommand(uint32 type, const string& text); + + __attribute__((unused)) void OnPlayerLogin(Player* player); + + void UpdateAIInternal(uint32 elapsed) override; + + Player* GetMaster() const { return master; }; + + void SaveToDB(); + +protected: + void OnBotLoginInternal(Player* bot) override; + +private: + Player* const master; +}; + +#endif diff --git a/src/modules/Bots/playerbot/PlayerbotSecurity.cpp b/src/modules/Bots/playerbot/PlayerbotSecurity.cpp new file mode 100644 index 000000000..d2cee24da --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotSecurity.cpp @@ -0,0 +1,222 @@ +#include "../botpch.h" +#include "PlayerbotMgr.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotAI.h" +#include "ChatHelper.h" + +PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot) +{ + if (bot) + { + account = sObjectMgr.GetPlayerAccountIdByGUID(bot->GetObjectGuid()); + } +} + +PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup) +{ + if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER) + { + return PLAYERBOT_SECURITY_ALLOW_ALL; + } + + if (bot->GetPlayerbotAI()->IsOpposing(from)) + { + if (reason) *reason = PLAYERBOT_DENY_OPPOSING; + { + return PLAYERBOT_SECURITY_DENY_ALL; + } + } + + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + if (bot->GetPlayerbotAI()->IsOpposing(from)) + { + if (reason) *reason = PLAYERBOT_DENY_OPPOSING; + { + return PLAYERBOT_SECURITY_DENY_ALL; + } + } + + Group* group = from->GetGroup(); + if (group) + { + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if (player == bot && !ignoreGroup) + { + return PLAYERBOT_SECURITY_ALLOW_ALL; + } + } + } + + if ((int)bot->getLevel() - (int)from->getLevel() > 5) + { + if (reason) *reason = PLAYERBOT_DENY_LOW_LEVEL; + { + return PLAYERBOT_SECURITY_TALK; + } + } + + int botGS = (int)bot->GetPlayerbotAI()->GetEquipGearScore(bot, false, false); + int fromGS = (int)bot->GetPlayerbotAI()->GetEquipGearScore(from, false, false); + if (botGS && bot->getLevel() > 15 && botGS > fromGS && (100 * (botGS - fromGS) / botGS) >= 12 * sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL) / from->getLevel()) + { + if (reason) *reason = PLAYERBOT_DENY_GEARSCORE; + { + return PLAYERBOT_SECURITY_TALK; + } + } + + if (bot->IsDead()) + { + if (reason) *reason = PLAYERBOT_DENY_DEAD; + { + return PLAYERBOT_SECURITY_TALK; + } + } + + group = bot->GetGroup(); + if (!group) + { + if (reason) *reason = PLAYERBOT_DENY_INVITE; + { + return PLAYERBOT_SECURITY_INVITE; + } + } + + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if (player == from) + { + return PLAYERBOT_SECURITY_ALLOW_ALL; + } + } + + if (group->IsFull()) + { + if (reason) *reason = PLAYERBOT_DENY_FULL_GROUP; + { + return PLAYERBOT_SECURITY_TALK; + } + } + + if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig.whisperDistance) + { + if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId()) + { + if (reason) *reason = PLAYERBOT_DENY_FAR; + { + return PLAYERBOT_SECURITY_TALK; + } + } + } + + if (reason) *reason = PLAYERBOT_DENY_INVITE; + { + return PLAYERBOT_SECURITY_INVITE; + } + } + + return PLAYERBOT_SECURITY_ALLOW_ALL; +} + +bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup) +{ + DenyReason reason = PLAYERBOT_DENY_NONE; + PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup); + if (realLevel >= level) + { + return true; + } + + if (silent || from->GetPlayerbotAI()) + { + return false; + } + + Player* master = bot->GetPlayerbotAI()->GetMaster(); + if (master && bot->GetPlayerbotAI() && bot->GetPlayerbotAI()->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER) + { + return false; + } + + ostringstream out; + switch (realLevel) + { + case PLAYERBOT_SECURITY_DENY_ALL: + out << "I'm kind of busy now"; + break; + case PLAYERBOT_SECURITY_TALK: + switch (reason) + { + case PLAYERBOT_DENY_NONE: + out << "I'll do it later"; + break; + case PLAYERBOT_DENY_LOW_LEVEL: + out << "You are too low level: |cffff0000" << (uint32)from->getLevel() << "|cffffffff/|cff00ff00" << (uint32)bot->getLevel(); + break; + case PLAYERBOT_DENY_GEARSCORE: + { + int botGS = (int)bot->GetPlayerbotAI()->GetEquipGearScore(bot, false, false); + int fromGS = (int)bot->GetPlayerbotAI()->GetEquipGearScore(from, false, false); + int diff = (100 * (botGS - fromGS) / botGS); + int req = 12 * sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL) / from->getLevel(); + out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS << " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%"; + } + break; + case PLAYERBOT_DENY_NOT_YOURS: + out << "I have a master already"; + break; + case PLAYERBOT_DENY_IS_BOT: + out << "You are a bot"; + break; + case PLAYERBOT_DENY_OPPOSING: + out << "You are the enemy"; + break; + case PLAYERBOT_DENY_DEAD: + out << "I'm dead. Will do it later"; + break; + case PLAYERBOT_DENY_INVITE: + out << "Invite me to your group first"; + break; + case PLAYERBOT_DENY_FAR: + { + out << "You must be closer to invite me to your group. I am in "; + + uint32 area = bot->GetAreaId(); + if (area) + { + const AreaTableEntry* entry = sAreaStore.LookupEntry(area); + if (entry) + { + out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)"; + } + } + } + break; + case PLAYERBOT_DENY_FULL_GROUP: + out << "I am in a full group. Will do it later"; + break; + default: + out << "I can't do that"; + break; + } + break; + case PLAYERBOT_SECURITY_INVITE: + out << "Invite me to your group first"; + break; + } + + string text = out.str(); + ObjectGuid guid = from->GetObjectGuid(); + time_t lastSaid = whispers[guid][text]; + if (!lastSaid || (time(0) - lastSaid) >= sPlayerbotAIConfig.repeatDelay / 1000) + { + whispers[guid][text] = time(0); + bot->Whisper(text, LANG_UNIVERSAL, guid); + } + return false; +} diff --git a/src/modules/Bots/playerbot/PlayerbotSecurity.h b/src/modules/Bots/playerbot/PlayerbotSecurity.h new file mode 100644 index 000000000..91d0b8e0d --- /dev/null +++ b/src/modules/Bots/playerbot/PlayerbotSecurity.h @@ -0,0 +1,43 @@ +#ifndef _PlayerbotSecurity_H +#define _PlayerbotSecurity_H + +using namespace std; + +enum PlayerbotSecurityLevel +{ + PLAYERBOT_SECURITY_DENY_ALL = 0, + PLAYERBOT_SECURITY_TALK = 1, + PLAYERBOT_SECURITY_INVITE = 2, + PLAYERBOT_SECURITY_ALLOW_ALL = 3 +}; + +enum DenyReason +{ + PLAYERBOT_DENY_NONE, + PLAYERBOT_DENY_LOW_LEVEL, + PLAYERBOT_DENY_GEARSCORE, + PLAYERBOT_DENY_NOT_YOURS, + PLAYERBOT_DENY_IS_BOT, + PLAYERBOT_DENY_OPPOSING, + PLAYERBOT_DENY_DEAD, + PLAYERBOT_DENY_FAR, + PLAYERBOT_DENY_INVITE, + PLAYERBOT_DENY_FULL_GROUP +}; + +class PlayerbotSecurity +{ + public: + PlayerbotSecurity(Player* const bot); + + public: + PlayerbotSecurityLevel LevelFor(Player* from, DenyReason* reason = NULL, bool ignoreGroup = false); + bool CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup = false); + + private: + Player* const bot; + uint32 account; + map > whispers; +}; + +#endif diff --git a/src/modules/Bots/playerbot/RandomItemMgr.cpp b/src/modules/Bots/playerbot/RandomItemMgr.cpp new file mode 100644 index 000000000..8b15647f7 --- /dev/null +++ b/src/modules/Bots/playerbot/RandomItemMgr.cpp @@ -0,0 +1,215 @@ +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "RandomItemMgr.h" + +#include "../../modules/Bots/ahbot/AhBot.h" +#include "DatabaseEnv.h" +#include "PlayerbotAI.h" + +#include "../../modules/Bots/ahbot/AhBotConfig.h" + +char * strstri (const char* str1, const char* str2); + +class RandomItemGuildTaskPredicate : public RandomItemPredicate +{ +public: + virtual bool Apply(ItemPrototype const* proto) + { + if (proto->Bonding == BIND_WHEN_PICKED_UP || + proto->Bonding == BIND_QUEST_ITEM || + proto->Bonding == BIND_WHEN_USE) + return false; + + if (proto->Quality < ITEM_QUALITY_NORMAL) + { + return false; + } + + if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) && proto->Quality >= ITEM_QUALITY_RARE) + { + return true; + } + + if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE) + { + return true; + } + + return false; + } +}; + +class RandomItemGuildTaskRewardPredicate : public RandomItemPredicate +{ +public: + RandomItemGuildTaskRewardPredicate(bool equip, bool rare) { this->equip = equip; this->rare = rare;} + + virtual bool Apply(ItemPrototype const* proto) + { + if (proto->Bonding == BIND_WHEN_PICKED_UP || + proto->Bonding == BIND_QUEST_ITEM || + proto->Bonding == BIND_WHEN_USE) + return false; + + if (proto->Class == ITEM_CLASS_QUEST) + { + return false; + } + + if (equip) + { + uint32 desiredQuality = rare ? ITEM_QUALITY_RARE : ITEM_QUALITY_UNCOMMON; + if (proto->Quality < desiredQuality) + { + return false; + } + + if (proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) + { + return true; + } + } + else + { + if (proto->Quality < ITEM_QUALITY_UNCOMMON) + { + return false; + } + + if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE) + { + return true; + } + } + + return false; + } + +private: + bool equip; + bool rare; +}; + +RandomItemMgr::RandomItemMgr() +{ + predicates[RANDOM_ITEM_GUILD_TASK] = new RandomItemGuildTaskPredicate(); + predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN] = new RandomItemGuildTaskRewardPredicate(true, false); + predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE] = new RandomItemGuildTaskRewardPredicate(true, true); + predicates[RANDOM_ITEM_GUILD_TASK_REWARD_TRADE] = new RandomItemGuildTaskRewardPredicate(false, false); +} + +RandomItemMgr::~RandomItemMgr() +{ + for (map::iterator i = predicates.begin(); i != predicates.end(); ++i) + { + delete i->second; + } + + predicates.clear(); +} + +bool RandomItemMgr::HandleConsoleCommand(ChatHandler* handler, char const* args) +{ + if (!args || !*args) + { + sLog.outError( "Usage: rnditem"); + return false; + } + + return false; +} + +RandomItemList RandomItemMgr::Query(RandomItemType type, RandomItemPredicate* predicate) +{ + RandomItemList &list = cache[type]; + if (list.empty()) + { + list = cache[type] = Query(type); + } + + RandomItemList result; + for (RandomItemList::iterator i = list.begin(); i != list.end(); ++i) + { + uint32 itemId = *i; + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (predicate && !predicate->Apply(proto)) + { + continue; + } + + result.push_back(itemId); + } + + return result; +} + +RandomItemList RandomItemMgr::Query(RandomItemType type) +{ + RandomItemList items; + + for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + continue; + } + + if (proto->Duration & 0x80000000) + { + continue; + } + + if (sAhBotConfig.ignoreItemIds.find(proto->ItemId) != sAhBotConfig.ignoreItemIds.end()) + { + continue; + } + + if (strstri(proto->Name1, "qa") || strstri(proto->Name1, "test") || strstri(proto->Name1, "deprecated")) + { + continue; + } + + if ((proto->RequiredLevel && proto->RequiredLevel > sAhBotConfig.maxRequiredLevel) || proto->ItemLevel > sAhBotConfig.maxItemLevel) + { + continue; + } + + if (predicates[type] && !predicates[type]->Apply(proto)) + { + continue; + } + + if (!auctionbot.GetSellPrice(proto)) + { + continue; + } + + items.push_back(itemId); + } + + if (items.empty()) + sLog.outError( "no items available for random item query %u", type); + + return items; +} + +uint32 RandomItemMgr::GetRandomItem(RandomItemType type, RandomItemPredicate* predicate) +{ + RandomItemList const& list = Query(type, predicate); + if (list.empty()) + { + return 0; + } + + uint32 index = urand(0, list.size() - 1); + uint32 itemId = list[index]; + + return itemId; +} diff --git a/src/modules/Bots/playerbot/RandomItemMgr.h b/src/modules/Bots/playerbot/RandomItemMgr.h new file mode 100644 index 000000000..035ab11c2 --- /dev/null +++ b/src/modules/Bots/playerbot/RandomItemMgr.h @@ -0,0 +1,52 @@ +#ifndef _RandomItemMgr_H +#define _RandomItemMgr_H + +#include "Common.h" +#include "PlayerbotAIBase.h" + +using namespace std; + +enum RandomItemType +{ + RANDOM_ITEM_GUILD_TASK, + RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE, + RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN, + RANDOM_ITEM_GUILD_TASK_REWARD_TRADE +}; + +class RandomItemPredicate +{ +public: + virtual bool Apply(ItemPrototype const* proto) = 0; +}; + +typedef vector RandomItemList; +typedef map RandomItemCache; + +class RandomItemMgr +{ + public: + RandomItemMgr(); + virtual ~RandomItemMgr(); + static RandomItemMgr& instance() + { + static RandomItemMgr instance; + return instance; + } + + public: + static bool HandleConsoleCommand(ChatHandler* handler, char const* args); + RandomItemList Query(RandomItemType type, RandomItemPredicate* predicate); + uint32 GetRandomItem(RandomItemType type, RandomItemPredicate* predicate = NULL); + + private: + RandomItemList Query(RandomItemType type); + + private: + RandomItemCache cache; + map predicates; +}; + +#define sRandomItemMgr RandomItemMgr::instance() + +#endif diff --git a/src/modules/Bots/playerbot/RandomPlayerbotFactory.cpp b/src/modules/Bots/playerbot/RandomPlayerbotFactory.cpp new file mode 100644 index 000000000..4ff89fa72 --- /dev/null +++ b/src/modules/Bots/playerbot/RandomPlayerbotFactory.cpp @@ -0,0 +1,421 @@ +#include "Config/Config.h" +#include "../botpch.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "AccountMgr.h" +#include "ObjectMgr.h" +#include "DatabaseEnv.h" +#include "Player.h" +#include "RandomPlayerbotFactory.h" +#include "SystemConfig.h" + +#include + +map > availableRaces = { + {CLASS_WARRIOR,{ + RACE_HUMAN, + RACE_ORC, + RACE_DWARF, + RACE_NIGHTELF, + RACE_UNDEAD, + RACE_TAUREN, + RACE_GNOME, + RACE_TROLL, + RACE_DRAENEI}}, + + {CLASS_PALADIN, { + RACE_HUMAN, + RACE_DWARF, + RACE_BLOODELF, + RACE_DRAENEI}}, + + {CLASS_HUNTER, { + RACE_ORC, + RACE_DWARF, + RACE_NIGHTELF, + RACE_TAUREN, + RACE_TROLL, + RACE_BLOODELF, + RACE_DRAENEI}}, + + {CLASS_ROGUE, { + RACE_HUMAN, + RACE_ORC, + RACE_DWARF, + RACE_NIGHTELF, + RACE_UNDEAD, + RACE_GNOME, + RACE_TROLL, + RACE_BLOODELF}}, + + {CLASS_PRIEST, { + RACE_HUMAN, + RACE_DWARF, + RACE_NIGHTELF, + RACE_UNDEAD, + RACE_TROLL, + RACE_BLOODELF, + RACE_DRAENEI}}, + + {CLASS_DEATH_KNIGHT, { + RACE_HUMAN, + RACE_ORC, + RACE_DWARF, + RACE_NIGHTELF, + RACE_UNDEAD, + RACE_TAUREN, + RACE_GNOME, + RACE_TROLL, + RACE_BLOODELF, + RACE_DRAENEI}}, + + {CLASS_SHAMAN, { + RACE_ORC, + RACE_TAUREN, + RACE_TROLL, + RACE_DRAENEI}}, + + {CLASS_MAGE, { + RACE_HUMAN, + RACE_UNDEAD, + RACE_GNOME, + RACE_TROLL, + RACE_BLOODELF, + RACE_DRAENEI}}, + + {CLASS_WARLOCK, { + RACE_HUMAN, + RACE_ORC, + RACE_UNDEAD, + RACE_GNOME, + RACE_BLOODELF}}, + + {CLASS_DRUID, { + RACE_NIGHTELF, + RACE_TAUREN}} +}; + +RandomPlayerbotFactory::RandomPlayerbotFactory(uint32 accountId) : accountId(accountId) +{ +} + +bool RandomPlayerbotFactory::CreateRandomBot(uint8 cls) const +{ + DETAIL_LOG("Creating new random bot for class %d", cls); + + uint8 gender = urand(GENDER_MALE, GENDER_FEMALE) ; + + uint8 race = availableRaces[cls][urand(0, availableRaces[cls].size() - 1)]; + string name; + + if (!CreateRandomBotName(name)) + { + return false; + } + + uint8 skin = urand(0, 7); + uint8 face = urand(0, 7); + uint8 hairStyle = urand(0, 7); + uint8 hairColor = urand(0, 7); + uint8 facialHair = urand(0, 7); + uint8 outfitId = 0; + + auto* session = new WorldSession(accountId, nullptr, SEC_PLAYER, MAX_EXPANSION, 0, LOCALE_enUS); + + auto *player = new Player(session); + if (!player->Create(sObjectMgr.GeneratePlayerLowGuid(), name, race, cls, gender, skin, face, hairStyle, hairColor, facialHair, outfitId)) + { + Player::DeleteFromDB(player->GetObjectGuid(), accountId, true, true); + delete session; + delete player; + sLog.outError("Unable to create random bot for account %d - name: \"%s\"; race: %u; class: %u; gender: %u; skin: %u; face: %u; hairStyle: %u; hairColor: %u; facialHair: %u; outfitId: %u", + accountId, name.c_str(), race, cls, gender, skin, face, hairStyle, hairColor, facialHair, outfitId); + return false; + } + + player->setCinematic(2); + player->SetAtLoginFlag(AT_LOGIN_NONE); + player->SaveToDB(); + + DETAIL_LOG("Random bot created for account %d - name: \"%s\"; race: %u; class: %u; gender: %u; skin: %u; face: %u; hairStyle: %u; hairColor: %u; facialHair: %u; outfitId: %u", + accountId, name.c_str(), race, cls, gender, skin, face, hairStyle, hairColor, facialHair, outfitId); + + return true; +} + + +bool RandomPlayerbotFactory::CreateRandomBotName(string& name) +{ + QueryResult *result = CharacterDatabase.Query("SELECT MAX(`name_id`) FROM `ai_playerbot_names`"); + if (!result) + { + sLog.outError("table `ai_playerbot_names` is empty. Check database"); + return false; + } + + Field *fields = result->Fetch(); + uint32 maxId = fields[0].GetUInt32(); + delete result; + + uint32 id = urand(0, maxId); + result = CharacterDatabase.PQuery("SELECT `n`.`name` FROM `ai_playerbot_names` n " + "LEFT OUTER JOIN `characters` e ON `e`.`name` = `n`.`name` " + "WHERE `e`.`guid` IS NULL AND `n`.`name_id` >= '%u' LIMIT 1", id); + if (!result) + { + sLog.outError("No more names left for random bots"); + return false; + } + + Field *nfields = result->Fetch(); + name = nfields[0].GetCppString(); + DEBUG_LOG("Created name %s for random bot.", name.c_str()); + delete result; + + return true; +} + + +void RandomPlayerbotFactory::CreateRandomBots() +{ + for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig.randomBotAccountCount; ++accountNumber) + { + string accountName = sPlayerbotAIConfig.randomBotAccountPrefix + to_string(accountNumber); + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account where username = '%s'", accountName.c_str()); + if (results) + { + delete results; + continue; + } + + uint8 password[MAX_PASSWORD_STR]; + switch (RAND_bytes(password, MAX_PASSWORD_STR)) + { + case -1: + sLog.outError("Can't create password for random bot account. Not supported by this system."); + break; + case 0: + sLog.outError("Password creation for random bot failed."); + break; + case 1: + sAccountMgr.CreateAccount(accountName, reinterpret_cast(password), MAX_EXPANSION); + DEBUG_LOG( "Account %s created for random bots", accountName.c_str()); + break; + } + } + + LoginDatabase.PExecute("UPDATE account SET expansion = '%u' WHERE expansion < '%u' AND username LIKE '%s%%'", MAX_EXPANSION, MAX_EXPANSION, sPlayerbotAIConfig.randomBotAccountPrefix.c_str()); + + uint32 totalRandomBotChars = 0; + for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig.randomBotAccountCount; ++accountNumber) + { + string accountName = sPlayerbotAIConfig.randomBotAccountPrefix + to_string(accountNumber); + + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account where username = '%s'", accountName.c_str()); + if (!results) + { + continue; + } + + Field* fields = results->Fetch(); + uint32 accountId = fields[0].GetUInt32(); + delete results; + + sPlayerbotAIConfig.randomBotAccounts.push_back(accountId); + + int count = sAccountMgr.GetCharactersCount(accountId); + if (count >= 10) + { + totalRandomBotChars += count; + continue; + } + + RandomPlayerbotFactory factory(accountId); + for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls) + { + if (cls != 10) + { + factory.CreateRandomBot(cls); + } + } + + totalRandomBotChars += sAccountMgr.GetCharactersCount(accountId); + DEBUG_LOG("Created %d bots for account %s.", sAccountMgr.GetCharactersCount(accountId) - count, accountName.c_str()); + } + + BASIC_LOG("%zu random bot accounts with %d characters available", sPlayerbotAIConfig.randomBotAccounts.size(), totalRandomBotChars); +} + + +void RandomPlayerbotFactory::CreateRandomGuilds() +{ + BASIC_LOG("Creating random bot guilds..."); + vector randomBots; + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account where username like '%s%%'", sPlayerbotAIConfig.randomBotAccountPrefix.c_str()); + if (results) + { + do + { + Field* fields = results->Fetch(); + uint32 accountId = fields[0].GetUInt32(); + + QueryResult* results2 = CharacterDatabase.PQuery("SELECT guid FROM characters where account = '%u'", accountId); + if (results2) + { + do + { + Field* fields2 = results2->Fetch(); + uint32 guid = fields2[0].GetUInt32(); + randomBots.push_back(guid); + } while (results2->NextRow()); + delete results2; + } + + } while (results->NextRow()); + delete results; + } + + uint32 guildNumber = 0; + vector availableLeaders; + for (uint32& i : randomBots) + { + ObjectGuid leader(HIGHGUID_PLAYER, i); + Guild* guild = sGuildMgr.GetGuildByLeader(leader); + if (guild) + { + ++guildNumber; + sPlayerbotAIConfig.randomBotGuilds.push_back(guild->GetId()); + } + else + { + Player* player = sObjectMgr.GetPlayer(leader); + if (player) + { + availableLeaders.push_back(leader); + } + } + } + + for (; guildNumber < sPlayerbotAIConfig.randomBotGuildCount; ++guildNumber) + { + string guildName; + if (!CreateRandomGuildName(guildName)) + { + break; + } + + if (availableLeaders.empty()) + { + sLog.outError("No leaders for random guilds available"); + break; + } + + uint32 index = urand(0, availableLeaders.size() - 1); + ObjectGuid leader = availableLeaders[index]; + Player* player = sObjectMgr.GetPlayer(leader); + if (!player) + { + sLog.outError("Cannot find player for guild leader %u", leader.GetEntry()); + break; + } + + auto* guild = new Guild(); + if (!guild->Create(player, guildName)) + { + sLog.outError("Error creating guild %s", guildName.c_str()); + delete guild; + break; + } + + sGuildMgr.AddGuild(guild); + sPlayerbotAIConfig.randomBotGuilds.push_back(guild->GetId()); + } + + BASIC_LOG("%d random bot guilds available", guildNumber); +} + +bool RandomPlayerbotFactory::CreateRandomGuildName(string& guildName) +{ + QueryResult* result = CharacterDatabase.Query("SELECT MAX(name_id) FROM ai_playerbot_guild_names"); + if (!result) + { + sLog.outError("No more names left for random guilds"); + return false; + } + + Field *fields = result->Fetch(); + uint32 maxId = fields[0].GetUInt32(); + delete result; + + uint32 id = urand(0, maxId); + result = CharacterDatabase.PQuery("SELECT n.name FROM ai_playerbot_guild_names n " + "LEFT OUTER JOIN guild e ON e.name = n.name " + "WHERE e.guildid IS NULL AND n.name_id >= '%u' LIMIT 1", id); + if (!result) + { + sLog.outError("No more names left for random guilds"); + return false; + } + + fields = result->Fetch(); + delete result; + guildName = fields[0].GetString(); + return true; +} + +void RandomPlayerbotFactory::DeleteRandomBots() +{ + BASIC_LOG("Deleting random bot accounts..."); + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account where username like '%s%%'", sPlayerbotAIConfig.randomBotAccountPrefix.c_str()); + if (results) + { + do + { + Field* fields = results->Fetch(); + sAccountMgr.DeleteAccount(fields[0].GetUInt32()); + } while (results->NextRow()); + delete results; + } + + CharacterDatabase.Execute("DELETE FROM ai_playerbot_random_bots"); + BASIC_LOG("Random bot accounts deleted"); +} + +void RandomPlayerbotFactory::DeleteRandomGuilds() +{ + BASIC_LOG("Deleting random bot guilds..."); + + vector randomBots; + QueryResult* results = LoginDatabase.PQuery("SELECT id FROM account where username like '%s%%'", sPlayerbotAIConfig.randomBotAccountPrefix.c_str()); + if (results) + { + do + { + Field* fields = results->Fetch(); + uint32 accountId = fields[0].GetUInt32(); + + QueryResult* results2 = CharacterDatabase.PQuery("SELECT guid FROM characters where account = '%u'", accountId); + if (results2) + { + do + { + Field* fields2 = results2->Fetch(); + uint32 guid = fields2[0].GetUInt32(); + randomBots.push_back(guid); + } while (results2->NextRow()); + delete results2; + } + + } while (results->NextRow()); + delete results; + } + + for (uint32& randomBot : randomBots) + { + ObjectGuid leader(HIGHGUID_PLAYER, randomBot); + Guild* guild = sGuildMgr.GetGuildByLeader(leader); + if (guild) guild->Disband(); + delete guild; + } + BASIC_LOG("Random bot guilds deleted"); +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/RandomPlayerbotFactory.h b/src/modules/Bots/playerbot/RandomPlayerbotFactory.h new file mode 100644 index 000000000..4084a48df --- /dev/null +++ b/src/modules/Bots/playerbot/RandomPlayerbotFactory.h @@ -0,0 +1,36 @@ +#ifndef RandomPlayerbotFactory_H +#define RandomPlayerbotFactory_H + +#include "Common.h" +#include "PlayerbotAIBase.h" + +class WorldPacket; +class Player; +class Unit; +class Object; +class Item; + +using namespace std; + +class RandomPlayerbotFactory +{ + public: + explicit RandomPlayerbotFactory(uint32 accountId); + virtual ~RandomPlayerbotFactory() = default; + + public: + bool CreateRandomBot(uint8 cls) const; + static void CreateRandomBots(); + static void CreateRandomGuilds(); + static void DeleteRandomBots(); + static void DeleteRandomGuilds(); + + private: + static bool CreateRandomBotName(string& name); + static bool CreateRandomGuildName(string& guildName); + + private: + uint32 accountId; +}; + +#endif diff --git a/src/modules/Bots/playerbot/RandomPlayerbotMgr.cpp b/src/modules/Bots/playerbot/RandomPlayerbotMgr.cpp new file mode 100644 index 000000000..9818f45f3 --- /dev/null +++ b/src/modules/Bots/playerbot/RandomPlayerbotMgr.cpp @@ -0,0 +1,992 @@ +#include "Config/Config.h" +#include "../botpch.h" +#include "PlayerbotAIConfig.h" +#include "PlayerbotFactory.h" +#include "AccountMgr.h" +#include "ObjectMgr.h" +#include "DatabaseEnv.h" +#include "PlayerbotAI.h" +#include "Player.h" +#include "AiFactory.h" +#include "GuildTaskMgr.h" +#include "PlayerbotCommandServer.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +INSTANTIATE_SINGLETON_1(RandomPlayerbotMgr); + +map > RandomPlayerbotMgr::locsPerLevelCache; + +RandomPlayerbotMgr::RandomPlayerbotMgr() : PlayerbotHolder() +{ + sPlayerbotCommandServer.Start(); + CharacterDatabase.PExecute("DELETE FROM ai_playerbot_random_bots WHERE `validIn` < (SELECT MAX(`time`) FROM ai_playerbot_random_bots)"); + CharacterDatabase.PExecute("UPDATE ai_playerbot_random_bots SET `validIn` = validIn - (SELECT MAX(`time`) FROM ai_playerbot_random_bots), `time` = '0' "); +} + +RandomPlayerbotMgr::~RandomPlayerbotMgr() += default; + +uint32 RandomPlayerbotMgr::GetMaxAllowedBotCount() +{ + return GetEventValue(0, "bot_count"); +} + +void RandomPlayerbotMgr::UpdateAIInternal(__attribute__((unused)) uint32 elapsed) +{ + SetNextCheckDelay(sPlayerbotAIConfig.randomBotUpdateInterval * 1000); + + if (!sPlayerbotAIConfig.randomBotAutologin || !sPlayerbotAIConfig.enabled) + { + DEBUG_LOG("Random bots are disabled. Check config."); + return; + } + + uint32 maxAllowedBotCount = GetMaxAllowedBotCount(); + if (!maxAllowedBotCount) + { + maxAllowedBotCount = urand(sPlayerbotAIConfig.minRandomBots, sPlayerbotAIConfig.maxRandomBots); + SetEventValue(0, "bot_count", maxAllowedBotCount, + urand(sPlayerbotAIConfig.randomBotCountChangeMinInterval, sPlayerbotAIConfig.randomBotCountChangeMaxInterval)); + } + + set bots; + GetBots(bots); + uint32 botCount = bots.size(); + DEBUG_LOG("%u random bots are currently available.", botCount); + + if (botCount < maxAllowedBotCount) + { + AddRandomBots(bots); + } + + botCount = bots.size(); + uint32 randomBotsPerInterval = sPlayerbotAIConfig.randomBotLoginAtStartup ? botCount : + urand(sPlayerbotAIConfig.minRandomBotsPerInterval, sPlayerbotAIConfig.maxRandomBotsPerInterval); + DEBUG_LOG("%u random bots are available, %u bots will be logged in.", botCount, randomBotsPerInterval); + + uint32 botProcessed = 0; + for (uint32 bot : bots) + { + if (botProcessed >= randomBotsPerInterval) + { + break; + } + + if (ProcessBot(bot)) + { + botProcessed++; + } + } + + string out = "Random bots are now scheduled to be processed in the background. Next re-schedule in " + + to_string(sPlayerbotAIConfig.randomBotUpdateInterval) + " seconds"; + DETAIL_LOG("%s", out.c_str()); + sWorld.SendWorldText(3, out.c_str()); + + if (sLog.GetLogLevel() >= LOG_LVL_DETAIL) { + PrintStats(); + } +} + +void RandomPlayerbotMgr::AddRandomBots(set &bots) +{ + DETAIL_LOG("Adding random bots..."); + + vector guids; + uint32 maxAllowedBotCount = GetMaxAllowedBotCount(); + for (auto accountId : sPlayerbotAIConfig.randomBotAccounts) + { + if (!sAccountMgr.GetCharactersCount(accountId)) + { + DEBUG_LOG("No character in rndbot account %u.", accountId); + continue; + } + + QueryResult* result = CharacterDatabase.PQuery("SELECT guid, race, class FROM characters WHERE account = '%u'", accountId); + if (!result) + { + DEBUG_LOG("Unable to fetch characters for rndbot account %u", accountId); + continue; + } + + do + { + Field* fields = result->Fetch(); + uint32 guid = fields[0].GetUInt32(); + uint8 race = fields[1].GetUInt8(); + uint8 cls = fields[2].GetUInt8(); + bool alliance = guids.size() % 2 == 0; + + if (cls == 6 && sPlayerbotAIConfig.randomBotMaxLevel < sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL)) + { + DEBUG_LOG("Max player level lower than minimum heroic level."); + continue; + } + + if (bots.find(guid) == bots.end() && + ((alliance && IsAlliance(race)) || ((!alliance && !IsAlliance(race))))) + { + guids.push_back(guid); + SetEventValue(guid, "add", 1, urand(sPlayerbotAIConfig.minRandomBotInWorldTime, sPlayerbotAIConfig.maxRandomBotInWorldTime)); + uint32 randomTime = 30 + urand(sPlayerbotAIConfig.randomBotUpdateInterval, sPlayerbotAIConfig.randomBotUpdateInterval * 3); + ScheduleRandomize(guid, randomTime); + bots.insert(guid); + DEBUG_LOG("Random bot %d is now available for login.", guid); + if (bots.size() >= maxAllowedBotCount) break; + } + } while (result->NextRow()); + delete result; + } +} + +void RandomPlayerbotMgr::ScheduleRandomize(uint32 bot, uint32 time) +{ + SetEventValue(bot, "randomize", 1, time); + SetEventValue(bot, "logout", 1, time + 30 + urand(sPlayerbotAIConfig.randomBotUpdateInterval, sPlayerbotAIConfig.randomBotUpdateInterval * 3)); +} + +void RandomPlayerbotMgr::ScheduleTeleport(uint32 bot, uint32 time) +{ + if (!time) + { + time = 60 + urand(sPlayerbotAIConfig.randomBotUpdateInterval, sPlayerbotAIConfig.randomBotUpdateInterval * 3); + } + SetEventValue(bot, "teleport", 1, time); +} + +bool RandomPlayerbotMgr::ProcessBot(uint32 bot) +{ + DEBUG_LOG("Processing bot %d", bot); + Player* player = GetBotByGUID(bot); + if (!IsRandomBot(bot)) + { + if (!player || !player->GetGroup()) + { + DETAIL_LOG("Bot %d expired", bot); + } + return true; + } + + if (!player) + { + DETAIL_LOG("Bot %d is not online. Logging in...", bot); + AddPlayerBot(bot, 0); + if (!GetEventValue(bot, "online")) + { + SetEventValue(bot, "online", 1, sPlayerbotAIConfig.minRandomBotInWorldTime); + ScheduleTeleport(bot, 30); + } + return true; + } + + PlayerbotAI* ai = player->GetPlayerbotAI(); + if (!ai) + { + return false; + } + + if (player->GetGroup()) + { + DEBUG_LOG("Skipping bot %d as it is in group", bot); + return false; + } + + ai->GetAiObjectContext()->GetValue("random bot update")->Set(true); + return true; +} + +bool RandomPlayerbotMgr::ProcessBot(Player* player) +{ + player->GetPlayerbotAI()->GetAiObjectContext()->GetValue("random bot update")->Set(false); + + uint32 bot = player->GetGUIDLow(); + DEBUG_LOG("Processing random bot %d", bot); + if (player->IsDead()) + { + if (!GetEventValue(bot, "dead")) + { + DEBUG_LOG("Setting dead flag for bot %d", bot); + uint32 randomTime = urand(sPlayerbotAIConfig.minRandomBotReviveTime, sPlayerbotAIConfig.maxRandomBotReviveTime); + SetEventValue(bot, "dead", 1, randomTime); + SetEventValue(bot, "revive", 1, randomTime - 60); + return false; + } + + if (!GetEventValue(bot, "revive")) + { + Revive(player); + return true; + } + + return false; + } + + if (player->GetGuildId()) + { + Guild* guild = sGuildMgr.GetGuildById(player->GetGuildId()); + if (guild->GetLeaderGuid().GetRawValue() == player->GetObjectGuid().GetRawValue()) { + for (auto & i : players) + { + sGuildTaskMgr.Update(i, player); + } + } + } + + if (!GetEventValue(bot, "randomize")) + { + DEBUG_LOG("Randomizing bot %d", bot); + Randomize(player); + ScheduleRandomize(bot, urand(sPlayerbotAIConfig.minRandomBotRandomizeTime, sPlayerbotAIConfig.maxRandomBotRandomizeTime)); + return true; + } + + if (!GetEventValue(bot, "logout")) + { + DEBUG_LOG("Logging out bot %d", bot); + LogoutPlayerBot(bot); + SetEventValue(bot, "logout", 1, sPlayerbotAIConfig.maxRandomBotInWorldTime); + return true; + } + + if (!GetEventValue(bot, "teleport")) + { + DEBUG_LOG("Random teleporting bot %d", bot); + RandomTeleportForLevel(player); + SetEventValue(bot, "teleport", 1, sPlayerbotAIConfig.maxRandomBotInWorldTime); + return true; + } + + return false; +} + +void RandomPlayerbotMgr::Revive(Player* player) +{ + uint32 bot = player->GetGUIDLow(); + DETAIL_LOG("Reviving dead bot %d", bot); + SetEventValue(bot, "dead", 0, 0); + SetEventValue(bot, "revive", 0, 0); + RandomTeleportForLevel(player); +} + + +void RandomPlayerbotMgr::RandomTeleport(Player* bot, vector &locs) +{ + if (bot->IsBeingTeleported()) + { + return; + } + + if (locs.empty()) + { + sLog.outError("Cannot teleport bot %s - no locations available", bot->GetName()); + return; + } + + for (auto attempts = 0; attempts < 10; ++attempts) + { + WorldLocation loc = locs[urand(0, locs.size() - 1)]; + float x = loc.coord_x + frand(0, sPlayerbotAIConfig.grindDistance) - sPlayerbotAIConfig.grindDistance / 2; + float y = loc.coord_y + frand(0, sPlayerbotAIConfig.grindDistance) - sPlayerbotAIConfig.grindDistance / 2; + float z = loc.coord_z; + + Map * map = sMapMgr.FindMap(loc.mapid); + if (!map) + { + continue; + } + + const TerrainInfo * terrain = map->GetTerrain(); + if (!terrain->IsOutdoors(x, y, z) || + +terrain->IsUnderWater(x, y, z) || + +terrain->IsInWater(x, y, z)) + continue; + + DETAIL_LOG("Random teleporting bot %s to %s %f,%f,%f", bot->GetName(), map->GetMapName(), x, y, z); + z = 0.05f + map->GetTerrain()->GetHeightStatic(x, y, 0.05f + z, true, MAX_HEIGHT); + bot->TeleportTo(loc.mapid, x, y, z, 0); + return; + } + + sLog.outError("Cannot teleport bot %s - no locations available", bot->GetName()); +} + +void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) +{ + if (locsPerLevelCache[bot->getLevel()].empty()) { + QueryResult *results = WorldDatabase.PQuery("select map, position_x, position_y, position_z " + "from (select map, position_x, position_y, position_z, avg(t.maxlevel), avg(t.minlevel), " + "(avg(t.maxlevel) + avg(t.minlevel)) / 2 - %u delta " + "from creature c inner join creature_template t on c.id = t.entry group by t.entry) q " + "where delta >= 0 and delta <= 1 and map in (%s)", + bot->getLevel(), sPlayerbotAIConfig.randomBotMapsAsString.c_str()); + if (results) { + do { + Field *fields = results->Fetch(); + uint32 mapId = fields[0].GetUInt32(); + float x = fields[1].GetFloat(); + float y = fields[2].GetFloat(); + float z = fields[3].GetFloat(); + WorldLocation loc(mapId, x, y, z, 0); + locsPerLevelCache[bot->getLevel()].push_back(loc); + } while (results->NextRow()); + delete results; + } + } + + RandomTeleport(bot, locsPerLevelCache[bot->getLevel()]); +} + +/*void RandomPlayerbotMgr::RandomTeleport(Player* bot, uint32 mapId, float teleX, float teleY, float teleZ) +{ + Refresh(bot); + + vector locs; + QueryResult * results = WorldDatabase.PQuery("select position_x, position_y, position_z from creature where map = '%u' and abs(position_x - '%f') < '%u' and abs(position_y - '%f') < '%u'", + +mapId, teleX, sPlayerbotAIConfig.randomBotTeleportDistance / 2, teleY, sPlayerbotAIConfig.randomBotTeleportDistance / 2); + if (results) + { + do + { + Field * fields = results->Fetch(); + float x = fields[0].GetFloat(); + float y = fields[1].GetFloat(); + float z = fields[2].GetFloat(); + WorldLocation loc(mapId, x, y, z, 0); + locs.push_back(loc); + } while (results->NextRow()); + delete results; + } + + RandomTeleport(bot, locs); + Refresh(bot); +}*/ + +void RandomPlayerbotMgr::Randomize(Player* bot) +{ + if (bot->getLevel() == 1 || + (bot->getClass() == 6 && bot->getLevel() == sWorld.getConfig(CONFIG_UINT32_START_HEROIC_PLAYER_LEVEL))) + { + RandomizeFirst(bot); + } + else + { + IncreaseLevel(bot); + } +} + +void RandomPlayerbotMgr::IncreaseLevel(Player* bot) +{ + DETAIL_LOG("Increasing level for bot %s", bot->GetName()); + uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel; + if (maxLevel > sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + maxLevel = sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL); + } + + uint32 level = min((uint32)(bot->getLevel() + 1), maxLevel); + PlayerbotFactory factory(bot, level); + if (bot->GetGuildId()) + { + factory.Refresh(); + } + else + { + factory.Randomize(); + } + RandomTeleportForLevel(bot); +} + +void RandomPlayerbotMgr::RandomizeFirst(Player* bot) +{ + uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel; + if (maxLevel > sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)) + { + maxLevel = sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL); + } + + for (int attempt = 0; attempt < 100; ++attempt) + { + uint32 index = urand(0, sPlayerbotAIConfig.randomBotMaps.size() - 1); + uint32 mapId = sPlayerbotAIConfig.randomBotMaps[index]; + + vector locs; + GameTeleMap const & teleMap = sObjectMgr.GetGameTeleMap(); + for (const auto & itr : teleMap) + { + GameTele const* tele = &itr.second; + if (tele->mapId == mapId) + { + locs.push_back(tele); + } + } + + index = urand(0, locs.size() - 1); + if (index >= locs.size()) + { + return; + } + GameTele const* tele = locs[index]; + uint32 level = GetZoneLevel(tele->mapId, tele->position_x, tele->position_y); + if (level > maxLevel + 5) + { + continue; + } + + level = min(level, maxLevel); + if (!level) level = 1; + + if (frand(0, 100) < 100 * sPlayerbotAIConfig.randomBotMaxLevelChance) + { + level = maxLevel; + } + + if (level < sPlayerbotAIConfig.randomBotMinLevel) + { + continue; + } + + PlayerbotFactory factory(bot, level); + factory.CleanRandomize(); + RandomTeleportForLevel(bot); + break; + } +} + +uint32 RandomPlayerbotMgr::GetZoneLevel(uint16 mapId, float teleX, float teleY) +{ + QueryResult* results = WorldDatabase.PQuery("select avg(t.minlevel) minlevel, avg(t.maxlevel) maxlevel from creature c " + "inner join creature_template t on c.id = t.entry " + "where map = '%u' and minlevel > 1 and abs(position_x - '%f') < '%u' and abs(position_y - '%f') < '%u'", + mapId, teleX, sPlayerbotAIConfig.randomBotTeleportDistance / 2, teleY, sPlayerbotAIConfig.randomBotTeleportDistance / 2); + + if (results) + { + Field* fields = results->Fetch(); + delete results; + + uint8 minLevel = fields[0].GetUInt8(); + uint8 maxLevel = min(static_cast(fields[1].GetUInt8()), sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)); + + return urand(minLevel, maxLevel); + + } + else + { + return urand(1, sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL)); + } +} + +/*void RandomPlayerbotMgr::Refresh(Player* bot) +{ + DETAIL_LOG("Refreshing bot %s", bot->GetName()); + if (bot->IsDead()) + { + bot->ResurrectPlayer(1.0f); + bot->SpawnCorpseBones(); + bot->GetPlayerbotAI()->ResetStrategies(); + } + + bot->GetPlayerbotAI()->Reset(); + + HostileReference *ref = bot->GetHostileRefManager().getFirst(); + while (ref) + { + ThreatManager *threatManager = ref->getSource(); + Unit *unit = threatManager->getOwner(); + + unit->RemoveAllAttackers(); + unit->ClearInCombat(); + + ref = ref->next(); + } + + bot->RemoveAllAttackers(); + bot->ClearInCombat(); + + bot->DurabilityRepairAll(false, 1.0f, false); + bot->SetHealthPercent(100); + bot->SetPvP(true); + + if (bot->GetMaxPower(POWER_MANA) > 0) + { + bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA)); + } + + if (bot->GetMaxPower(POWER_ENERGY) > 0) + { + bot->SetPower(POWER_ENERGY, bot->GetMaxPower(POWER_ENERGY)); + } +}*/ + + +bool RandomPlayerbotMgr::IsRandomBot(Player* bot) +{ + return IsRandomBot(bot->GetObjectGuid()); +} + +bool RandomPlayerbotMgr::IsRandomBot(uint32 bot) +{ + return GetEventValue(bot, "add"); +} + +void RandomPlayerbotMgr::GetBots(set &bots) +{ + QueryResult* results = CharacterDatabase.Query( + "select bot from ai_playerbot_random_bots where owner = 0 and event = 'add'"); + + if (results) + { + do + { + Field* fields = results->Fetch(); + uint32 bot = fields[0].GetUInt32(); + bots.insert(bot); + } while (results->NextRow()); + delete results; + } +} + +uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, const string& event) +{ + uint32 value = 0; + + QueryResult* results = CharacterDatabase.PQuery( + "SELECT `value`, `time`, `validIn` FROM `ai_playerbot_random_bots` WHERE `owner` = '0' AND `bot` = '%u' AND `event` = '%s'", + bot, event.c_str()); + + if (results) + { + Field* fields = results->Fetch(); + value = fields[0].GetUInt32(); + uint32 lastChangeTime = fields[1].GetUInt32(); + uint32 validIn = fields[2].GetUInt32(); + if (((GameTime::GetGameTimeMS() / 1000) - lastChangeTime) >= validIn) + { + value = 0; + } + delete results; + } + + return value; +} + +uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, const string& event, uint32 value, uint32 validIn) +{ + CharacterDatabase.PExecute("DELETE FROM `ai_playerbot_random_bots` WHERE `owner` = '0' AND `bot` = '%u' AND `event` = '%s'", + bot, event.c_str()); + if (value) + { + CharacterDatabase.PExecute( + "INSERT INTO `ai_playerbot_random_bots` (`owner`, `bot`, `time`, `validIn`, `event`, `value`) VALUES ('%u', '%u', '%u', '%u', '%s', '%u')", + 0, bot, GameTime::GetGameTimeMS() / 1000, validIn, event.c_str(), value); + } + + return value; +} + +bool RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(char const *args) +{ + DEBUG_LOG("Handling rnd bot command: %s", args); + if (!sPlayerbotAIConfig.enabled) + { + sLog.outError("Playerbot system is currently disabled!"); + return false; + } + + if (!args || !*args) + { + sLog.outError("Usage: rndbot stats/update/reset/init/refresh/add/remove"); + return false; + } + + string cmd = args; + + if (cmd == "reset") + { + CharacterDatabase.PExecute("delete from ai_playerbot_random_bots"); + sLog.outString("Random bots were reset for all players. Please restart the Server."); + return true; + } + else if (cmd == "stats") + { + sRandomPlayerbotMgr.PrintStats(); + return true; + } + else if (cmd == "update") + { + sRandomPlayerbotMgr.UpdateAIInternal(0); + return true; + } + else if (cmd == "init" || cmd == "refresh" || cmd == "teleport" || cmd == "revive") + { + sLog.outString("Randomizing bots for %zu accounts", sPlayerbotAIConfig.randomBotAccounts.size()); + list botIds; + for (auto account: sPlayerbotAIConfig.randomBotAccounts) + { + if (QueryResult* results = CharacterDatabase.PQuery("SELECT guid FROM characters where account = '%u'", account)) + { + do + { + Field* fields = results->Fetch(); + + uint32 botId = fields[0].GetUInt32(); + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, botId); + Player* bot = sObjectMgr.GetPlayer(guid); + if (!bot) + { + continue; + } + + botIds.push_back(botId); + } while (results->NextRow()); + delete results; + } + } + + int processed = 0; + for (auto botId : botIds) + { + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, botId); + Player* bot = sObjectMgr.GetPlayer(guid); + if (!bot) + { + continue; + } + + sLog.outString("[%u/%zu] Processing command '%s' for bot '%s'", + processed++, botIds.size(), cmd.c_str(), bot->GetName()); + + if (cmd == "init") + { + sRandomPlayerbotMgr.RandomizeFirst(bot); + } + else if (cmd == "teleport") + { + sRandomPlayerbotMgr.RandomTeleportForLevel(bot); + } + else if (cmd == "revive") + { + sRandomPlayerbotMgr.Revive(bot); + } + else + { + bot->SetLevel(bot->getLevel() - 1); + sRandomPlayerbotMgr.IncreaseLevel(bot); + } + uint32 randomTime = urand(sPlayerbotAIConfig.minRandomBotRandomizeTime, sPlayerbotAIConfig.maxRandomBotRandomizeTime); + CharacterDatabase.PExecute("UPDATE ai_playerbot_random_bots SET validIn = '%u' WHERE event = 'randomize' AND bot = '%u'", + randomTime, bot->GetGUIDLow()); + CharacterDatabase.PExecute("UPDATE ai_playerbot_random_bots SET validIn = '%u' WHERE event = 'logout' AND bot = '%u'", + sPlayerbotAIConfig.maxRandomBotInWorldTime, bot->GetGUIDLow()); + } + return true; + } + else + { + list messages = sRandomPlayerbotMgr.HandlePlayerbotCommand(args, nullptr); + for (auto & message : messages) + { + sLog.outError("%s", message.c_str()); + } + return true; + } +} + +void RandomPlayerbotMgr::HandleCommand(uint32 type, const string& text, Player& fromPlayer) +{ + for (auto it : playerBots) + { + Player* const bot = it.second; + bot->GetPlayerbotAI()->HandleCommand(type, text, fromPlayer); + } +} + +void RandomPlayerbotMgr::OnPlayerLogout(Player* player) +{ + for (auto it : playerBots) + { + Player* const bot = it.second; + PlayerbotAI* ai = bot->GetPlayerbotAI(); + if (player == ai->GetMaster()) + { + ai->SetMaster(nullptr); + ai->ResetStrategies(); + } + } + + auto i = find(players.begin(), players.end(), player); + if (i != players.end()) + { + players.erase(i); + } +} + +void RandomPlayerbotMgr::OnPlayerLogin(Player* player) +{ + for (auto it : playerBots) + { + Player* const bot = it.second; + if (player == bot || player->GetPlayerbotAI()) + { + continue; + } + + Group* group = bot->GetGroup(); + if (!group) + { + continue; + } + + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->getSource(); + PlayerbotAI* ai = bot->GetPlayerbotAI(); + if (member == player && (!ai->GetMaster() || ai->GetMaster()->GetPlayerbotAI())) + { + ai->SetMaster(player); + ai->ResetStrategies(); + ai->TellMaster("Hello"); + break; + } + } + } + + if (!IsRandomBot(player)) + { + players.push_back(player); + DEBUG_LOG("Including non-random bot player %s into random bot update", player->GetName()); + } +} + +Player* RandomPlayerbotMgr::GetRandomPlayer() +{ + if (players.empty()) + { + return nullptr; + } + + uint32 index = urand(0, players.size() - 1); + return players[index]; +} + +void RandomPlayerbotMgr::PrintStats() +{ + sLog.outString("%zu Random Bots online", playerBots.size()); + + map alliance, horde; + for (uint32 i = 0; i < 10; ++i) + { + alliance[i] = 0; + horde[i] = 0; + } + + map perRace, perClass; + for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race) + { + perRace[race] = 0; + } + for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls) + { + perClass[cls] = 0; + } + + uint32 dps = 0, heal = 0, tank = 0, active = 0; + for (auto & playerBot : playerBots) + { + Player* bot = playerBot.second; + if (IsAlliance(bot->getRace())) + { + alliance[bot->getLevel() / 10]++; + } + else + { + horde[bot->getLevel() / 10]++; + } + + perRace[bot->getRace()]++; + perClass[bot->getClass()]++; + + if (bot->GetPlayerbotAI()->IsActive()) + { + active++; + } + + int spec = AiFactory::GetPlayerSpecTab(bot); + switch (bot->getClass()) + { + case CLASS_DRUID: + if (spec == 2) + { + heal++; + } + else + { + dps++; + } + break; + case CLASS_PALADIN: + if (spec == 1) + { + tank++; + } + else if (spec == 0) + { + heal++; + } + else + { + dps++; + } + break; + case CLASS_PRIEST: + if (spec != 2) + { + heal++; + } + else + { + dps++; + } + break; + case CLASS_SHAMAN: + if (spec == 2) + { + heal++; + } + else + { + dps++; + } + break; + case CLASS_WARRIOR: + if (spec == 2) + { + tank++; + } + else + { + dps++; + } + break; + default: + dps++; + break; + } + } + + sLog.outString("Per level:"); + uint32 maxLevel = sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL); + for (uint32 i = 0; i < 10; ++i) + { + if (!alliance[i] && !horde[i]) + { + continue; + } + + uint32 from = i * 10; + uint32 to = min(from + 9, maxLevel); + if (!from) from = 1; + { + sLog.outString(" %d..%d: %d alliance, %d horde", from, to, alliance[i], horde[i]); + } + } + sLog.outString("Per race:"); + for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race) + { + if (perRace[race]) + { + sLog.outString(" %s: %d", ChatHelper::formatRace(race).c_str(), perRace[race]); + } + } + sLog.outString("Per class:"); + for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls) + { + if (perClass[cls]) + { + sLog.outString(" %s: %d", ChatHelper::formatClass(cls).c_str(), perClass[cls]); + } + } + sLog.outString("Per role:"); + sLog.outString(" tank: %d", tank); + sLog.outString(" heal: %d", heal); + sLog.outString(" dps: %d", dps); + + sLog.outString("Active bots: %d", active); +} + +double RandomPlayerbotMgr::GetBuyMultiplier(Player* bot) +{ + uint32 id = bot->GetGUIDLow(); + uint32 value = GetEventValue(id, "buymultiplier"); + if (!value) + { + value = urand(1, 120); + uint32 validIn = urand(sPlayerbotAIConfig.minRandomBotsPriceChangeInterval, sPlayerbotAIConfig.maxRandomBotsPriceChangeInterval); + SetEventValue(id, "buymultiplier", value, validIn); + } + + return static_cast(value) / 100.0; +} + +double RandomPlayerbotMgr::GetSellMultiplier(Player* bot) +{ + uint32 id = bot->GetGUIDLow(); + uint32 value = GetEventValue(id, "sellmultiplier"); + if (!value) + { + value = urand(80, 250); + uint32 validIn = urand(sPlayerbotAIConfig.minRandomBotsPriceChangeInterval, sPlayerbotAIConfig.maxRandomBotsPriceChangeInterval); + SetEventValue(id, "sellmultiplier", value, validIn); + } + + return static_cast(value) / 100.0; +} + +uint32 RandomPlayerbotMgr::GetLootAmount(Player* bot) +{ + uint32 id = bot->GetGUIDLow(); + return GetEventValue(id, "lootamount"); +} + +void RandomPlayerbotMgr::SetLootAmount(Player* bot, uint32 value) +{ + uint32 id = bot->GetGUIDLow(); + SetEventValue(id, "lootamount", value, 24 * 3600); +} + +uint32 RandomPlayerbotMgr::GetTradeDiscount(Player* bot) +{ + Group* group = bot->GetGroup(); + return GetLootAmount(bot) / (group ? group->GetMembersCount() : 10); +} + +string RandomPlayerbotMgr::HandleRemoteCommand(string request) +{ + string::iterator pos = find(request.begin(), request.end(), ','); + if (pos == request.end()) + { + return "invalid request: " + request; + } + + string command = string(request.begin(), pos); + uint64 guid = atoi(string(pos + 1, request.end()).c_str()); + Player* bot = GetBotByGUID(guid); + if (!bot) + { + return "invalid guid"; + } + + PlayerbotAI *ai = bot->GetPlayerbotAI(); + if (!ai) + { + return "invalid guid"; + } + + return ai->HandleRemoteCommand(command); +} diff --git a/src/modules/Bots/playerbot/RandomPlayerbotMgr.h b/src/modules/Bots/playerbot/RandomPlayerbotMgr.h new file mode 100644 index 000000000..c5bfae40d --- /dev/null +++ b/src/modules/Bots/playerbot/RandomPlayerbotMgr.h @@ -0,0 +1,76 @@ +#ifndef RandomPlayerbotMgr_H +#define RandomPlayerbotMgr_H + +#include "Common.h" +#include "PlayerbotAIBase.h" +#include "PlayerbotMgr.h" + +class WorldPacket; +class Player; +class Unit; +class Object; +class Item; + +using namespace std; + +class RandomPlayerbotMgr : public PlayerbotHolder +{ + public: + RandomPlayerbotMgr(); + ~RandomPlayerbotMgr() override; + static RandomPlayerbotMgr& instance() + { + static RandomPlayerbotMgr instance; + return instance; + } + + void UpdateAIInternal(__attribute__((unused)) uint32 elapsed) override; + + public: + static bool HandlePlayerbotConsoleCommand(char const *args); + static bool IsRandomBot(Player* bot); + static bool IsRandomBot(uint32 bot); + static void Randomize(Player* bot); + static void RandomizeFirst(Player* bot); + static void IncreaseLevel(Player* bot); + static void ScheduleTeleport(uint32 bot, uint32 time = 0); + void HandleCommand(uint32 type, const string& text, Player& fromPlayer); + string HandleRemoteCommand(string request); + void OnPlayerLogout(Player* player); + void OnPlayerLogin(Player* player); + Player* GetRandomPlayer(); + void PrintStats(); + static double GetBuyMultiplier(Player* bot); + static double GetSellMultiplier(Player* bot); + static uint32 GetLootAmount(Player* bot); + static void SetLootAmount(Player* bot, uint32 value); + static uint32 GetTradeDiscount(Player* bot); + // static void Refresh(Player* bot); + static void RandomTeleportForLevel(Player* bot); + // void RandomTeleport(Player * bot, uint32 mapId, float teleX, float teleY, float teleZ); + static uint32 GetMaxAllowedBotCount(); + bool ProcessBot(Player* player); + static void Revive(Player* player); + + protected: + void OnBotLoginInternal(__attribute__((unused)) Player * const bot) override {} + + private: + static uint32 GetEventValue(uint32 bot, const string& event); + static uint32 SetEventValue(uint32 bot, const string& event, uint32 value, uint32 validIn); + static void GetBots(set &bots); + static void AddRandomBots(set &bots); + bool ProcessBot(uint32 bot); + static void ScheduleRandomize(uint32 bot, uint32 time); + //void RandomTeleport(Player* bot); + static void RandomTeleport(Player* bot, vector &locs); + static uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY); + + private: + vector players; + static map > locsPerLevelCache; +}; + +#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance() + +#endif diff --git a/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in b/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in new file mode 100644 index 000000000..a8a94a39f --- /dev/null +++ b/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in @@ -0,0 +1,192 @@ +########################################## +# MANGOS Ai Playerbot Configuration file # +########################################## + +[AiPlayerbotConf] +ConfVersion=@MANGOS_WORLD_VER@ + +# Enable or disable AI Playerbot +#AiPlayerbot.Enabled = 0 + +# Warrior +#AiPlayerbot.RandomClassSpecProbability.1.0 = 20 +#AiPlayerbot.RandomClassSpecProbability.1.1 = 30 +#AiPlayerbot.RandomClassSpecProbability.1.2 = 50 +# Paladin +#AiPlayerbot.RandomClassSpecProbability.2.0 = 20 +#AiPlayerbot.RandomClassSpecProbability.2.1 = 50 +#AiPlayerbot.RandomClassSpecProbability.2.2 = 30 +# Hunter +#AiPlayerbot.RandomClassSpecProbability.3.0 = 25 +#AiPlayerbot.RandomClassSpecProbability.3.1 = 50 +#AiPlayerbot.RandomClassSpecProbability.3.2 = 25 +# Rogue +#AiPlayerbot.RandomClassSpecProbability.4.0 = 40 +#AiPlayerbot.RandomClassSpecProbability.4.1 = 50 +#AiPlayerbot.RandomClassSpecProbability.4.2 = 10 +# Priest +#AiPlayerbot.RandomClassSpecProbability.5.0 = 40 +#AiPlayerbot.RandomClassSpecProbability.5.1 = 40 +#AiPlayerbot.RandomClassSpecProbability.5.2 = 20 +# Shaman +#AiPlayerbot.RandomClassSpecProbability.7.0 = 10 +#AiPlayerbot.RandomClassSpecProbability.7.1 = 45 +#AiPlayerbot.RandomClassSpecProbability.7.2 = 45 +# Mage +#AiPlayerbot.RandomClassSpecProbability.8.0 = 20 +#AiPlayerbot.RandomClassSpecProbability.8.1 = 10 +#AiPlayerbot.RandomClassSpecProbability.8.2 = 70 +# Warlock +#AiPlayerbot.RandomClassSpecProbability.9.0 = 33 +#AiPlayerbot.RandomClassSpecProbability.9.1 = 33 +#AiPlayerbot.RandomClassSpecProbability.9.2 = 33 +# Druid +#AiPlayerbot.RandomClassSpecProbability.11.0 = 10 +#AiPlayerbot.RandomClassSpecProbability.11.1 = 45 +#AiPlayerbot.RandomClassSpecProbability.11.2 = 45 + +# +# All other parameters are optional but can be changed by uncommenting them here +# + +# Prefix for bot chat commands (e.g. follow, stay) +#AiPlayerbot.CommandPrefix = + +# Max AI iterations per tick +#AiPlayerbot.IterationsPerTick = 10 + +# Allow/deny bots from your guild +#AiPlayerbot.AllowGuildBots = 1 + +# Delay between two short-time spells cast +#AiPlayerbot.GlobalCooldown = 500 + +# Max wait time when moving +#AiPlayerbot.MaxWaitForMove = 5000 + +# Delay between two bot actions +#AiPlayerbot.ReactDelay = 100 + +# Inactivity delay +#AiPlayerbot.PassiveDelay = 3000 + +# Minimum delay between repeating actions (chat messages, emotes etc) +#AiPlayerbot.RepeatDelay = 3000 + +# Distances +#AiPlayerbot.SightDistance = 75.0 +#AiPlayerbot.SpellDistance = 30.0 +#AiPlayerbot.ReactDistance = 150.0 +#AiPlayerbot.GrindDistance = 100.0 +#AiPlayerbot.LootDistance = 20.0 +#AiPlayerbot.FleeDistance = 20.0 +#AiPlayerbot.TooCloseDistance = 7.0 +#AiPlayerbot.MeleeDistance = 1.0 +#AiPlayerbot.FollowDistance = 1.5 +#AiPlayerbot.WhisperDistance = 6000.0 +#AiPlayerbot.ContactDistance = 0.5 + +# Bot can flee for enemy +#AiPlayerbot.FleeingEnabled = 1 + +# Health/Mana levels +#AiPlayerbot.CriticalHealth = 25 +#AiPlayerbot.LowHealth = 45 +#AiPlayerbot.MediumHealth = 65 +#AiPlayerbot.AlmostFullHealth = 85 +#AiPlayerbot.LowMana = 15 +#AiPlayerbot.MediumMana = 40 + +# Enable random bot system +#AiPlayerbot.RandomBotAutologin = 0 + +# Random bot default strategies (applied after defaults) +#AiPlayerbot.RandomBotCombatStrategies = +dps,+attack weak +#AiPlayerbot.RandomBotNonCombatStrategies = +grind,+move random,+loot + +# Create random bot characters automatically +#AiPlayerbot.RandomBotAutoCreate = 1 + +# Random bot count +#AiPlayerbot.MinRandomBots = 50 +#AiPlayerbot.MaxRandomBots = 200 +#AiPlayerbot.RandomBotMinLevel = 1 +#AiPlayerbot.RandomBotMaxLevel = 255 (ignored if more than MaxPlayerLevel mangosd.conf value) + +# Accounts to create for random bots +#AiPlayerbot.RandomBotAccountPrefix = rndbot +#AiPlayerbot.RandomBotAccountCount = 50 + +# Delete all random bot accounts +#AiPlayerbot.DeleteRandomBotAccounts = 0 + +# Random bot guild count +#AiPlayerbot.RandomBotGuildCount = 50 + +# Guild Task system +#AiPlayerbot.EnableGuildTasks = 1 + +# How often tasks are changed +#AiPlayerbot.MinGuildTaskChangeTime = 172800 +#AiPlayerbot.MaxGuildTaskChangeTime = 432000 +# Mail spam interval +#AiPlayerbot.MinGuildTaskAdvertisementTime = 300 +#AiPlayerbot.MaxGuildTaskAdvertisementTime = 28800 +# Delay before reward is sent +#AiPlayerbot.MinGuildTaskRewardTime = 300 +#AiPlayerbot.MaxGuildTaskRewardTime = 3600 + +# Delete all random bot guilds +#AiPlayerbot.DeleteRandomBotGuilds = 0 + +# Maps to teleport random bots +#AiPlayerbot.RandomBotMaps = 0,1,530,571 + +# Change random bot has lower gear +#AiPlayerbot.RandomGearLoweringChance = 0.15 + +# Chance random bot has max level on first randomize +#AiPlayerbot.RandomBotMaxLevelChance = 0.4 + +# Quest items to leave (do not destroy) +#AiPlayerbot.RandomBotQuestItems = 6948,5175,5176,5177,5178 + +# Spells every random bot will learn on randomize (54197 - cold weather flying) +#AiPlayerbot.RandomBotSpellIds = 54197 + +# Enable LFG for random bots +#AiPlayerbot.RandomBotJoinLfg = 1 + +# Level diff between random bots and nearby creatures for random teleports +#AiPlayerbot.RandomBotTeleLevel = 3 + +# Intervals +#AiPlayerbot.RandomBotUpdateInterval = 60 +#AiPlayerbot.RandomBotCountChangeMinInterval = 86400 +#AiPlayerbot.RandomBotCountChangeMaxInterval = 259200 +#AiPlayerbot.MinRandomBotInWorldTime = 7200 +#AiPlayerbot.MaxRandomBotInWorldTime = 1209600 +#AiPlayerbot.MinRandomBotRandomizeTime = 7200 +#AiPlayerbot.MaxRandomRandomizeTime = 1209600 +#AiPlayerbot.MinRandomBotsPerInterval = 50 +#AiPlayerbot.MaxRandomBotsPerInterval = 100 +#AiPlayerbot.MinRandomBotsPriceChangeInterval = 7200 +#AiPlayerbot.MaxRandomBotsPriceChangeInterval = 172800 + +# Log on all random bots on start +#AiPlayerbot.RandomBotLoginAtStartup = 0 + +# How far random bots are teleported after death +#AiPlayerbot.RandomBotTeleportDistance = 1000 + +# Debug switches +#AiPlayerbot.SpellDump = 0 +#AiPlayerbot.LogInGroupOnly = 1 +#AiPlayerbot.LogValuesPerTick = 0 +#AiPlayerbot.RandomChangeMultiplier = 1 + +# Command server port, 0 - disabled +#AiPlayerbot.CommandServerPort = 8888 + +# auto-login all player alts as bots on player login +#AiPlayerbot.BotAutologin = 0 diff --git a/src/modules/Bots/playerbot/playerbot.h b/src/modules/Bots/playerbot/playerbot.h new file mode 100644 index 000000000..449e5a7af --- /dev/null +++ b/src/modules/Bots/playerbot/playerbot.h @@ -0,0 +1,28 @@ +#pragma once + +std::vector split(const std::string &s, char delim); +#ifndef WIN32 +int strcmpi(std::string s1, std::string s2); +#endif + +#include "Spell.h" +#include "WorldPacket.h" +#include "LootMgr.h" +#include "GossipDef.h" +#include "Chat.h" +#include "Common.h" +#include "World.h" +#include "SpellMgr.h" +#include "ObjectMgr.h" +#include "Unit.h" +#include "SharedDefines.h" +#include "MotionMaster.h" +#include "SpellAuras.h" +#include "Guild.h" + +#include "playerbotDefs.h" +#include "PlayerbotAIAware.h" +#include "PlayerbotMgr.h" +#include "RandomPlayerbotMgr.h" +#include "ChatHelper.h" +#include "PlayerbotAI.h" diff --git a/src/modules/Bots/playerbot/playerbotDefs.h b/src/modules/Bots/playerbot/playerbotDefs.h new file mode 100644 index 000000000..00c96b4c9 --- /dev/null +++ b/src/modules/Bots/playerbot/playerbotDefs.h @@ -0,0 +1,3 @@ +#pragma once + +#define CAST_ANGLE_IN_FRONT (2 * M_PI_F / 3) \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/Action.cpp b/src/modules/Bots/playerbot/strategy/Action.cpp new file mode 100644 index 000000000..d89e34acb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Action.cpp @@ -0,0 +1,113 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "AiObjectContext.h" +#include "Action.h" + +using namespace ai; + +int NextAction::size(NextAction** actions) +{ + if (!actions) + { + return 0; + } + + int size; + for (size=0; actions[size]; ) + { + size++; + } + return size; +} + +NextAction** NextAction::clone(NextAction** actions) +{ + if (!actions) + { + return NULL; + } + + int size = NextAction::size(actions); + + NextAction** res = new NextAction*[size + 1]; + for (int i=0; i* Action::GetTargetValue() +{ + return context->GetValue(GetTargetName()); +} + +Unit* Action::GetTarget() +{ + return GetTargetValue()->Get(); +} diff --git a/src/modules/Bots/playerbot/strategy/Action.h b/src/modules/Bots/playerbot/strategy/Action.h new file mode 100644 index 000000000..61b3f6bc0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Action.h @@ -0,0 +1,142 @@ +#pragma once +#include "Event.h" +#include "Value.h" +#include "AiObject.h" + +namespace ai +{ + class NextAction + { + public: + NextAction(string name, float relevance = 0.0f) + { + this->name = name; + this->relevance = relevance; + } + NextAction(const NextAction& o) + { + this->name = o.name; + this->relevance = o.relevance; + } + + public: + string getName() { return name; } + float getRelevance() {return relevance;} + + public: + static int size(NextAction** actions); + static NextAction** clone(NextAction** actions); + static NextAction** merge(NextAction** what, NextAction** with); + static NextAction** array(uint8 nil,...); + static void destroy(NextAction** actions); + + private: + float relevance; + std::string name; + }; + + //--------------------------------------------------------------------------------------------------------------------- + + class ActionBasket; + + enum ActionThreatType + { + ACTION_THREAT_NONE = 0, + ACTION_THREAT_SINGLE= 1, + ACTION_THREAT_AOE = 2 + }; + + class Action : public AiNamedObject + { + public: + Action(PlayerbotAI* ai, string name = "action") : verbose(false), AiNamedObject(ai, name) { } + virtual ~Action(void) {} + + public: + virtual bool Execute(Event event) { return true; } + virtual bool isPossible() { return true; } + virtual bool isUseful() { return true; } + virtual NextAction** getPrerequisites() { return NULL; } + virtual NextAction** getAlternatives() { return NULL; } + virtual NextAction** getContinuers() { return NULL; } + virtual ActionThreatType getThreatType() { return ACTION_THREAT_NONE; } + void Update() {} + void Reset() {} + virtual Unit* GetTarget(); + virtual Value* GetTargetValue(); + virtual string GetTargetName() { return "self target"; } + void MakeVerbose() { verbose = true; } + + protected: + bool verbose; + }; + + class ActionNode + { + public: + ActionNode(string name, NextAction** prerequisites = NULL, NextAction** alternatives = NULL, NextAction** continuers = NULL) + { + this->action = NULL; + this->name = name; + this->prerequisites = prerequisites; + this->alternatives = alternatives; + this->continuers = continuers; + } + virtual ~ActionNode() + { + NextAction::destroy(prerequisites); + NextAction::destroy(alternatives); + NextAction::destroy(continuers); + } + + public: + Action* getAction() { return action; } + void setAction(Action* action) { this->action = action; } + string getName() { return name; } + + public: + NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); } + NextAction** getAlternatives() { return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives()); } + NextAction** getPrerequisites() { return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites()); } + + private: + string name; + Action* action; + NextAction** continuers; + NextAction** alternatives; + NextAction** prerequisites; + }; + + //--------------------------------------------------------------------------------------------------------------------- + + class ActionBasket + { + public: + ActionBasket(ActionNode* action, float relevance, bool skipPrerequisites, Event event) : + action(action), relevance(relevance), skipPrerequisites(skipPrerequisites), event(event) { + created = time(0); + } + virtual ~ActionBasket(void) {} + public: + float getRelevance() {return relevance;} + ActionNode* getAction() {return action;} + Event getEvent() { return event; } + bool isSkipPrerequisites() { return skipPrerequisites; } + void AmendRelevance(float k) {relevance *= k; } + void setRelevance(float relevance) { this->relevance = relevance; } + bool isExpired(time_t secs) { return time(0) - created >= secs; } + private: + ActionNode* action; + float relevance; + bool skipPrerequisites; + Event event; + time_t created; + }; + + //--------------------------------------------------------------------------------------------------------------------- + + +} + +#define AI_VALUE(type, name) context->GetValue(name)->Get() +#define AI_VALUE2(type, name, param) context->GetValue(name, param)->Get() diff --git a/src/modules/Bots/playerbot/strategy/ActionBasket.cpp b/src/modules/Bots/playerbot/strategy/ActionBasket.cpp new file mode 100644 index 000000000..9b3c77edf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/ActionBasket.cpp @@ -0,0 +1,4 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "ActionBasket.h" + diff --git a/src/modules/Bots/playerbot/strategy/ActionBasket.h b/src/modules/Bots/playerbot/strategy/ActionBasket.h new file mode 100644 index 000000000..d85293b01 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/ActionBasket.h @@ -0,0 +1,5 @@ +#pragma once +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/AiObject.cpp b/src/modules/Bots/playerbot/strategy/AiObject.cpp new file mode 100644 index 000000000..86907f640 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/AiObject.cpp @@ -0,0 +1,16 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "AiObject.h" + +AiObject::AiObject(PlayerbotAI* ai) : + PlayerbotAIAware(ai), + bot(ai->GetBot()), + context(ai->GetAiObjectContext()), + chat(ai->GetChatHelper()) +{ +} + +Player* AiObject::GetMaster() +{ + return ai->GetMaster(); +} diff --git a/src/modules/Bots/playerbot/strategy/AiObject.h b/src/modules/Bots/playerbot/strategy/AiObject.h new file mode 100644 index 000000000..b16756c60 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/AiObject.h @@ -0,0 +1,33 @@ +#pragma once + +class PlayerbotAI; + +namespace ai +{ + class AiObjectContext; + class ChatHelper; + + class AiObject : public PlayerbotAIAware + { + public: + AiObject(PlayerbotAI* ai); + + protected: + Player* bot; + Player* GetMaster(); + AiObjectContext* context; + ChatHelper* chat; + }; + + class AiNamedObject : public AiObject + { + public: + AiNamedObject(PlayerbotAI* ai, string name) : AiObject(ai), name(name) {} + + public: + virtual string getName() { return name; } + + protected: + string name; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/AiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/AiObjectContext.cpp new file mode 100644 index 000000000..0c2586262 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/AiObjectContext.cpp @@ -0,0 +1,48 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "AiObjectContext.h" +#include "NamedObjectContext.h" +#include "StrategyContext.h" +#include "triggers/TriggerContext.h" +#include "actions/ActionContext.h" +#include "triggers/ChatTriggerContext.h" +#include "actions/ChatActionContext.h" +#include "triggers/WorldPacketTriggerContext.h" +#include "actions/WorldPacketActionContext.h" +#include "values/ValueContext.h" + +using namespace ai; + +AiObjectContext::AiObjectContext(PlayerbotAI* ai) : PlayerbotAIAware(ai) +{ + strategyContexts.Add(new StrategyContext()); + strategyContexts.Add(new MovementStrategyContext()); + strategyContexts.Add(new AssistStrategyContext()); + strategyContexts.Add(new QuestStrategyContext()); + + actionContexts.Add(new ActionContext()); + actionContexts.Add(new ChatActionContext()); + actionContexts.Add(new WorldPacketActionContext()); + + triggerContexts.Add(new TriggerContext()); + triggerContexts.Add(new ChatTriggerContext()); + triggerContexts.Add(new WorldPacketTriggerContext()); + + valueContexts.Add(new ValueContext()); +} + +void AiObjectContext::Update() +{ + strategyContexts.Update(); + triggerContexts.Update(); + actionContexts.Update(); + valueContexts.Update(); +} + +void AiObjectContext::Reset() +{ + strategyContexts.Reset(); + triggerContexts.Reset(); + actionContexts.Reset(); + valueContexts.Reset(); +} diff --git a/src/modules/Bots/playerbot/strategy/AiObjectContext.h b/src/modules/Bots/playerbot/strategy/AiObjectContext.h new file mode 100644 index 000000000..7c9d549bd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/AiObjectContext.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../PlayerbotAIAware.h" +#include "Action.h" +#include "Value.h" +#include "NamedObjectContext.h" +#include "Strategy.h" + +namespace ai +{ + class AiObjectContext : public PlayerbotAIAware + { + public: + AiObjectContext(PlayerbotAI* ai); + virtual ~AiObjectContext() {} + + public: + virtual Strategy* GetStrategy(string name) { return strategyContexts.GetObject(name, ai); } + virtual set GetSiblingStrategy(string name) { return strategyContexts.GetSiblings(name); } + virtual Trigger* GetTrigger(string name) { return triggerContexts.GetObject(name, ai); } + virtual Action* GetAction(string name) { return actionContexts.GetObject(name, ai); } + virtual UntypedValue* GetUntypedValue(string name) { return valueContexts.GetObject(name, ai); } + + template + Value* GetValue(string name) + { + return dynamic_cast*>(GetUntypedValue(name)); + } + + template + Value* GetValue(string name, string param) + { + return GetValue((string(name) + "::" + param)); + } + + template + Value* GetValue(string name, uint32 param) + { + ostringstream out; out << param; + return GetValue(name, out.str()); + } + + set GetSupportedStrategies() + { + return strategyContexts.supports(); + } + + string FormatValues() + { + ostringstream out; + set names = valueContexts.GetCreated(); + for (set::iterator i = names.begin(); i != names.end(); ++i, out << "|") + { + UntypedValue* value = GetUntypedValue(*i); + if (!value) + { + continue; + } + + string text = value->Format(); + if (text == "?") + { + continue; + } + + out << "{" << *i << "=" << text << "}"; + } + return out.str(); + } + + public: + virtual void Update(); + virtual void Reset(); + virtual void AddShared(NamedObjectContext* sharedValues) + { + valueContexts.Add(sharedValues); + } + + protected: + NamedObjectContextList strategyContexts; + NamedObjectContextList actionContexts; + NamedObjectContextList triggerContexts; + NamedObjectContextList valueContexts; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/CustomStrategy.cpp b/src/modules/Bots/playerbot/strategy/CustomStrategy.cpp new file mode 100644 index 000000000..54553853f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/CustomStrategy.cpp @@ -0,0 +1,122 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "CustomStrategy.h" +#include + +using namespace ai; + +map CustomStrategy::actionLinesCache; + +NextAction* toNextAction(string action) +{ + vector tokens = split(action, '!'); + if (tokens.size() == 2 && !tokens[0].empty()) + { + return new NextAction(tokens[0], atof(tokens[1].c_str())); + } + else if (tokens.size() == 1 && !tokens[0].empty()) + { + return new NextAction(tokens[0], ACTION_NORMAL); + } + + sLog.outError("Invalid action '%s'", action.c_str()); + return NULL; +} + +NextAction** toNextActionArray(const string& actions) +{ + vector tokens = split(actions, ','); + NextAction** res = new NextAction*[tokens.size() + 1]; + int index = 0; + for (vector::iterator i = tokens.begin(); i != tokens.end(); ++i) + { + NextAction* na = toNextAction(*i); + if (na) + { + res[index++] = na; + } + } + res[index++] = NULL; + return res; +} + +TriggerNode* toTriggerNode(string actionLine) +{ + vector tokens = split(actionLine, '>'); + if (tokens.size() == 2) + { + return new TriggerNode(tokens[0], toNextActionArray(tokens[1])); + } + + sLog.outError("Invalid action line '%s'", actionLine.c_str()); + return NULL; +} + +void CustomStrategy::InitTriggers(std::list &triggers) +{ + if (actionLines.empty()) + { + if (actionLinesCache[qualifier].empty()) + { + LoadActionLines((uint32)ai->GetBot()->GetGUIDLow()); + if (this->actionLines.empty()) + { + LoadActionLines(0); + } + } + else + { + vector tokens = split(actionLinesCache[qualifier], '\n'); + regex tpl("\\(NULL,\\s*'.+',\\s*'(.+)'\\)(,|;)"); + for (vector::iterator i = tokens.begin(); i != tokens.end(); ++i) + { + string line = *i; + for (sregex_iterator j = sregex_iterator(line.begin(), line.end(), tpl); j != sregex_iterator(); ++j) + { + smatch match = *j; + string actionLine = match[1].str(); + if (!actionLine.empty()) + { + this->actionLines.push_back(actionLine); + } + } + } + } + } + + for (list::iterator i = actionLines.begin(); i != actionLines.end(); ++i) + { + TriggerNode* tn = toTriggerNode(*i); + if (tn) + { + triggers.push_back(tn); + } + } +} + +void CustomStrategy::LoadActionLines(uint32 owner) +{ + QueryResult* results = CharacterDatabase.PQuery("SELECT action_line FROM ai_playerbot_custom_strategy WHERE name = '%s' and owner = '%u' order by idx", + qualifier.c_str(), owner); + if (results) + { + do + { + Field* fields = results->Fetch(); + string action = fields[0].GetString(); + this->actionLines.push_back(action); + } while (results->NextRow()); + + delete results; + } +} + +void CustomStrategy::Reset() +{ + actionLines.clear(); + actionLinesCache[qualifier].clear(); +} + +CustomStrategy::CustomStrategy(PlayerbotAI* ai) : Strategy(ai), Qualified() +{ +} diff --git a/src/modules/Bots/playerbot/strategy/CustomStrategy.h b/src/modules/Bots/playerbot/strategy/CustomStrategy.h new file mode 100644 index 000000000..710df6516 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/CustomStrategy.h @@ -0,0 +1,23 @@ +#pragma once +#include "Strategy.h" + +namespace ai +{ + class CustomStrategy : public Strategy, public Qualified + { + public: + CustomStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "custom::" + qualifier; } + void Reset(); + + private: + list actionLines; + void LoadActionLines(uint32 owner); + + public: + static map actionLinesCache; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/Engine.cpp b/src/modules/Bots/playerbot/strategy/Engine.cpp new file mode 100644 index 000000000..a827dd930 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Engine.cpp @@ -0,0 +1,616 @@ +#include "../../botpch.h" +#include "../playerbot.h" + +#include "Engine.h" +#include "../PlayerbotAIConfig.h" + +using namespace ai; +using namespace std; + +Engine::Engine(PlayerbotAI* ai, AiObjectContext *factory) : PlayerbotAIAware(ai), aiObjectContext(factory) +{ + lastRelevance = 0.0f; + testMode = false; +} + +bool ActionExecutionListeners::Before(Action* action, Event event) +{ + bool result = true; + for (list::iterator i = listeners.begin(); i!=listeners.end(); i++) + { + result &= (*i)->Before(action, event); + } + return result; +} + +void ActionExecutionListeners::After(Action* action, bool executed, Event event) +{ + for (list::iterator i = listeners.begin(); i!=listeners.end(); i++) + { + (*i)->After(action, executed, event); + } +} + +bool ActionExecutionListeners::OverrideResult(Action* action, bool executed, Event event) +{ + bool result = executed; + for (list::iterator i = listeners.begin(); i!=listeners.end(); i++) + { + result = (*i)->OverrideResult(action, result, event); + } + return result; +} + +bool ActionExecutionListeners::AllowExecution(Action* action, Event event) +{ + bool result = true; + for (list::iterator i = listeners.begin(); i!=listeners.end(); i++) + { + result &= (*i)->AllowExecution(action, event); + } + return result; +} + +ActionExecutionListeners::~ActionExecutionListeners() +{ + for (list::iterator i = listeners.begin(); i!=listeners.end(); i++) + { + delete *i; + } + listeners.clear(); +} + + +Engine::~Engine(void) +{ + Reset(); + + strategies.clear(); +} + +void Engine::Reset() +{ + ActionNode* action = NULL; + do + { + action = queue.Pop(); + if (!action) break; + { + delete action; + } + } while (true); + + for (list::iterator i = triggers.begin(); i != triggers.end(); i++) + { + TriggerNode* trigger = *i; + delete trigger; + } + triggers.clear(); + + for (list::iterator i = multipliers.begin(); i != multipliers.end(); i++) + { + Multiplier* multiplier = *i; + delete multiplier; + } + multipliers.clear(); +} + +void Engine::Init() +{ + Reset(); + + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + Strategy* strategy = i->second; + strategy->InitMultipliers(multipliers); + strategy->InitTriggers(triggers); + Event emptyEvent; + MultiplyAndPush(strategy->getDefaultActions(), 0.0f, false, emptyEvent, "default"); + } + + if (testMode) + { + FILE* file = fopen("test.log", "w"); + fprintf(file, "\n"); + fclose(file); + } +} + + +bool Engine::DoNextAction(Unit* unit, int depth) +{ + LogAction("--- AI Tick ---"); + if (sPlayerbotAIConfig.logValuesPerTick) + { + LogValues(); + } + + bool actionExecuted = false; + ActionBasket* basket = NULL; + + time_t currentTime = time(0); + aiObjectContext->Update(); + ProcessTriggers(); + + int iterations = 0; + int iterationsPerTick = queue.Size() * sPlayerbotAIConfig.iterationsPerTick; + do { + basket = queue.Peek(); + if (basket) { + float relevance = basket->getRelevance(); // just for reference + bool skipPrerequisites = basket->isSkipPrerequisites(); + Event event = basket->getEvent(); + // NOTE: queue.Pop() deletes basket + ActionNode* actionNode = queue.Pop(); + Action* action = InitializeAction(actionNode); + + if (!action) + { + LogAction("A:%s - UNKNOWN", actionNode->getName().c_str()); + } + else if (action->isUseful()) + { + for (list::iterator i = multipliers.begin(); i!= multipliers.end(); i++) + { + Multiplier* multiplier = *i; + relevance *= multiplier->GetValue(action); + if (!relevance) + { + LogAction("Multiplier %s made action %s useless", multiplier->getName().c_str(), action->getName().c_str()); + break; + } + } + + if (action->isPossible() && relevance) + { + if (!skipPrerequisites) + { + LogAction("A:%s - PREREQ", action->getName().c_str()); + if (MultiplyAndPush(actionNode->getPrerequisites(), relevance + 0.02, false, event, "prereq")) + { + PushAgain(actionNode, relevance + 0.01, event); + continue; + } + } + + actionExecuted = ListenAndExecute(action, event); + + if (actionExecuted) + { + LogAction("A:%s - OK", action->getName().c_str()); + MultiplyAndPush(actionNode->getContinuers(), 0, false, event, "cont"); + lastRelevance = relevance; + delete actionNode; + break; + } + else + { + LogAction("A:%s - FAILED", action->getName().c_str()); + MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.03, false, event, "alt"); + } + } + else + { + LogAction("A:%s - IMPOSSIBLE", action->getName().c_str()); + MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.03, false, event, "alt"); + } + } + else + { + lastRelevance = relevance; + LogAction("A:%s - USELESS", action->getName().c_str()); + } + delete actionNode; + } + } + while (basket && ++iterations <= iterationsPerTick); + + if (!basket) + { + lastRelevance = 0.0f; + PushDefaultActions(); + if (queue.Peek() && depth < 2) + { + return DoNextAction(unit, depth + 1); + } + } + + if (time(0) - currentTime > 1) { + { + LogAction("too long execution"); + } + } + + if (!actionExecuted) + { + LogAction("no actions executed"); + } + + queue.RemoveExpired(); + return actionExecuted; +} + +ActionNode* Engine::CreateActionNode(string name) +{ + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + Strategy* strategy = i->second; + ActionNode* node = strategy->GetAction(name); + if (node) + { + return node; + } + } + return new ActionNode (name, + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); +} + +bool Engine::MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event, const char* pushType) +{ + bool pushed = false; + if (actions) + { + for (int j=0; actions[j]; j++) + { + NextAction* nextAction = actions[j]; + if (nextAction) + { + ActionNode* action = CreateActionNode(nextAction->getName()); + InitializeAction(action); + + float k = nextAction->getRelevance(); + if (forceRelevance > 0.0f) + { + k = forceRelevance; + } + + if (k > 0) + { + LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType); + queue.Push(new ActionBasket(action, k, skipPrerequisites, event)); + pushed = true; + } + else + { + delete action; + } + + delete nextAction; + } + else + { + break; + } + } + delete actions; + } + return pushed; +} + +ActionResult Engine::ExecuteAction(const string name) +{ + bool result = false; + + ActionNode *actionNode = CreateActionNode(name); + if (!actionNode) + { + return ACTION_RESULT_UNKNOWN; + } + + Action* action = InitializeAction(actionNode); + if (!action) + { + delete actionNode; + return ACTION_RESULT_UNKNOWN; + } + + if (!action->isPossible()) + { + delete actionNode; + return ACTION_RESULT_IMPOSSIBLE; + } + + if (!action->isUseful()) + { + delete actionNode; + return ACTION_RESULT_USELESS; + } + + action->MakeVerbose(); + Event emptyEvent; + result = ListenAndExecute(action, emptyEvent); + MultiplyAndPush(action->getContinuers(), 0.0f, false, emptyEvent, "default"); + delete actionNode; + return result ? ACTION_RESULT_OK : ACTION_RESULT_FAILED; +} + +void Engine::addStrategy(string name) +{ + removeStrategy(name); + + Strategy* strategy = aiObjectContext->GetStrategy(name); + if (strategy) + { + set siblings = aiObjectContext->GetSiblingStrategy(name); + for (set::iterator i = siblings.begin(); i != siblings.end(); i++) + { + removeStrategy(*i); + } + + LogAction("S:+%s", strategy->getName().c_str()); + strategies[strategy->getName()] = strategy; + } + Init(); +} + +void Engine::addStrategies(string first, ...) +{ + addStrategy(first); + + va_list vl; + va_start(vl, first); + + const char* cur; + do + { + cur = va_arg(vl, const char*); + if (cur) + { + addStrategy(cur); + } + } + while (cur); + + va_end(vl); +} + +bool Engine::removeStrategy(string name) +{ + map::iterator i = strategies.find(name); + if (i == strategies.end()) + { + return false; + } + + LogAction("S:-%s", name.c_str()); + strategies.erase(i); + Init(); + return true; +} + +void Engine::removeAllStrategies() +{ + strategies.clear(); + Init(); +} + +void Engine::toggleStrategy(const string name) +{ + if (!removeStrategy(name)) + { + addStrategy(name); + } +} + +bool Engine::HasStrategy(string name) +{ + return strategies.find(name) != strategies.end(); +} + +void Engine::ProcessTriggers() +{ + map fires; + for (list::iterator i = triggers.begin(); i != triggers.end(); i++) + { + TriggerNode* node = *i; + if (!node) + { + continue; + } + + Trigger* trigger = node->getTrigger(); + if (!trigger) + { + trigger = aiObjectContext->GetTrigger(node->getName()); + node->setTrigger(trigger); + } + + if (!trigger) + { + continue; + } + + if (testMode || trigger->needCheck()) + { + Event event = trigger->Check(); + if (!event) + { + continue; + } + fires[trigger] = event; + LogAction("T:%s", trigger->getName().c_str()); + } + } + + for (list::iterator i = triggers.begin(); i != triggers.end(); i++) + { + TriggerNode* node = *i; + Trigger* trigger = node->getTrigger(); + Event event = fires[trigger]; + if (!event) + { + continue; + } + + MultiplyAndPush(node->getHandlers(), 0.0f, false, event, "trigger"); + } + + for (list::iterator i = triggers.begin(); i != triggers.end(); i++) + { + Trigger* trigger = (*i)->getTrigger(); + if (trigger) trigger->Reset(); + } +} + +void Engine::PushDefaultActions() +{ + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + Strategy* strategy = i->second; + Event emptyEvent; + MultiplyAndPush(strategy->getDefaultActions(), 0.0f, false, emptyEvent, "default"); + } +} + +string Engine::ListStrategies() +{ + string s = "Strategies: "; + + if (strategies.empty()) + { + return s; + } + + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + s.append(i->first); + s.append(", "); + } + return s.substr(0, s.length() - 2); +} + +list Engine::GetStrategies() +{ + list result; + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + result.push_back(i->first); + } + return result; +} + +void Engine::PushAgain(ActionNode* actionNode, float relevance, const Event& event) +{ + NextAction** nextAction = new NextAction*[2]; + nextAction[0] = new NextAction(actionNode->getName(), relevance); + nextAction[1] = NULL; + MultiplyAndPush(nextAction, relevance, true, event, "again"); + delete actionNode; +} + +bool Engine::ContainsStrategy(StrategyType type) +{ + for (map::iterator i = strategies.begin(); i != strategies.end(); i++) + { + Strategy* strategy = i->second; + if (strategy->GetType() & type) + { + return true; + } + } + return false; +} + +Action* Engine::InitializeAction(ActionNode* actionNode) +{ + Action* action = actionNode->getAction(); + if (!action) + { + action = aiObjectContext->GetAction(actionNode->getName()); + actionNode->setAction(action); + } + return action; +} + +bool Engine::ListenAndExecute(Action* action, Event event) +{ + bool actionExecuted = false; + + if (actionExecutionListeners.Before(action, event)) + { + actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true; + } + + actionExecuted = actionExecutionListeners.OverrideResult(action, actionExecuted, event); + actionExecutionListeners.After(action, actionExecuted, event); + return actionExecuted; +} + +void Engine::LogAction(const char* format, ...) +{ + char buf[1024]; + + va_list ap; + va_start(ap, format); + vsprintf(buf, format, ap); + va_end(ap); + lastAction += "|"; + lastAction += buf; + if (lastAction.size() > 512) + { + lastAction = lastAction.substr(512); + size_t pos = lastAction.find("|"); + lastAction = (pos == string::npos ? "" : lastAction.substr(pos)); + } + + if (testMode) + { + FILE* file = fopen("test.log", "a"); + fprintf(file, buf); + fprintf(file, "\n"); + fclose(file); + } + else + { + Player* bot = ai->GetBot(); + if (sPlayerbotAIConfig.logInGroupOnly && !bot->GetGroup()) + { + return; + } + + sLog.outDetail( "%s %s", bot->GetName(), buf); + } +} + +void Engine::ChangeStrategy(const string names) +{ + vector splitted = split(names, ','); + for (vector::iterator i = splitted.begin(); i != splitted.end(); i++) + { + const char* name = i->c_str(); + switch (name[0]) + { + case '+': + addStrategy(name+1); + break; + case '-': + removeStrategy(name+1); + break; + case '~': + toggleStrategy(name+1); + break; + case '?': + ai->TellMaster(ListStrategies()); + break; + } + } +} + +void Engine::LogValues() +{ + if (testMode) + { + return; + } + + Player* bot = ai->GetBot(); + if (sPlayerbotAIConfig.logInGroupOnly && !bot->GetGroup()) + { + return; + } + + string text = ai->GetAiObjectContext()->FormatValues(); + sLog.outDebug( "Values for %s: %s", bot->GetName(), text.c_str()); +} diff --git a/src/modules/Bots/playerbot/strategy/Engine.h b/src/modules/Bots/playerbot/strategy/Engine.h new file mode 100644 index 000000000..04d9f1349 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Engine.h @@ -0,0 +1,124 @@ +#pragma once + +#include "Action.h" +#include "Queue.h" +#include "Trigger.h" +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + +namespace ai +{ + class ActionExecutionListener + { + public: + virtual bool Before(Action* action, Event event) = 0; + virtual bool AllowExecution(Action* action, Event event) = 0; + virtual void After(Action* action, bool executed, Event event) = 0; + virtual bool OverrideResult(Action* action, bool executed, Event event) = 0; + }; + + // ----------------------------------------------------------------------------------------------------------------------- + + class ActionExecutionListeners : public ActionExecutionListener + { + public: + virtual ~ActionExecutionListeners(); + + // ActionExecutionListener + public: + virtual bool Before(Action* action, Event event); + virtual bool AllowExecution(Action* action, Event event); + virtual void After(Action* action, bool executed, Event event); + virtual bool OverrideResult(Action* action, bool executed, Event event); + + public: + void Add(ActionExecutionListener* listener) + { + listeners.push_back(listener); + } + void Remove(ActionExecutionListener* listener) + { + listeners.remove(listener); + } + + private: + std::list listeners; + }; + + // ----------------------------------------------------------------------------------------------------------------------- + + enum ActionResult + { + ACTION_RESULT_UNKNOWN, + ACTION_RESULT_OK, + ACTION_RESULT_IMPOSSIBLE, + ACTION_RESULT_USELESS, + ACTION_RESULT_FAILED + }; + + class Engine : public PlayerbotAIAware + { + public: + Engine(PlayerbotAI* ai, AiObjectContext *factory); + + void Init(); + void addStrategy(string name); + void addStrategies(string first, ...); + bool removeStrategy(string name); + bool HasStrategy(string name); + void removeAllStrategies(); + void toggleStrategy(string name); + std::string ListStrategies(); + list GetStrategies(); + bool ContainsStrategy(StrategyType type); + void ChangeStrategy(string names); + string GetLastAction() { return lastAction; } + + public: + virtual bool DoNextAction(Unit*, int depth = 0); + ActionResult ExecuteAction(string name); + + public: + void AddActionExecutionListener(ActionExecutionListener* listener) + { + actionExecutionListeners.Add(listener); + } + void removeActionExecutionListener(ActionExecutionListener* listener) + { + actionExecutionListeners.Remove(listener); + } + + public: + virtual ~Engine(void); + + private: + bool MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event, const char* pushType); + void Reset(); + void ProcessTriggers(); + void PushDefaultActions(); + void PushAgain(ActionNode* actionNode, float relevance, const Event& event); + ActionNode* CreateActionNode(string name); + Action* InitializeAction(ActionNode* actionNode); + bool ListenAndExecute(Action* action, Event event); + + private: + void LogAction(const char* format, ...); + void LogValues(); + + protected: + Queue queue; + std::list triggers; + std::list multipliers; + AiObjectContext* aiObjectContext; + std::map strategies; + float lastRelevance; + std::string lastAction; + + public: + bool testMode; + + private: + ActionExecutionListeners actionExecutionListeners; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/Event.cpp b/src/modules/Bots/playerbot/strategy/Event.cpp new file mode 100644 index 000000000..0fdf4ccee --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Event.cpp @@ -0,0 +1,22 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Event.h" + + +using namespace ai; + +ObjectGuid Event::getObject() +{ + if (packet.empty()) + { + return ObjectGuid(); + } + + WorldPacket p(packet); + p.rpos(0); + + ObjectGuid guid; + p >> guid; + + return guid; +} diff --git a/src/modules/Bots/playerbot/strategy/Event.h b/src/modules/Bots/playerbot/strategy/Event.h new file mode 100644 index 000000000..68d8b5186 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Event.h @@ -0,0 +1,36 @@ +#pragma once + +namespace ai +{ + class Event + { + public: + Event(Event const& other) + { + source = other.source; + param = other.param; + packet = other.packet; + owner = other.owner; + } + Event() {} + Event(string source) : source(source) {} + Event(string source, string param, Player* owner = NULL) : source(source), param(param), owner(owner) {} + Event(string source, WorldPacket &packet, Player* owner = NULL) : source(source), packet(packet), owner(owner) {} + virtual ~Event() {} + + public: + string getSource() { return source; } + string getParam() { return param; } + WorldPacket& getPacket() { return packet; } + ObjectGuid getObject(); + Player* getOwner() { return owner; } + bool operator! () const { return source.empty(); } + + protected: + string source; + string param; + WorldPacket packet; + ObjectGuid object; + Player* owner; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/ExternalEventHelper.h b/src/modules/Bots/playerbot/strategy/ExternalEventHelper.h new file mode 100644 index 000000000..092808c4c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/ExternalEventHelper.h @@ -0,0 +1,82 @@ +#pragma once + +#include "Trigger.h" + +namespace ai +{ + class ExternalEventHelper { + public: + ExternalEventHelper(AiObjectContext* aiObjectContext) : aiObjectContext(aiObjectContext) {} + + bool ParseChatCommand(string command, Player* owner = NULL) + { + if (HandleCommand(command, "", owner)) + { + return true; + } + + size_t i = string::npos; + while (true) + { + size_t found = command.rfind(" ", i); + if (found == string::npos || !found) + { + break; + } + + string name = command.substr(0, found); + string param = command.substr(found + 1); + + i = found - 1; + + if (HandleCommand(name, param, owner)) + { + return true; + } + } + + if (!ChatHelper::parseable(command)) + { + return false; + } + + HandleCommand("c", command, owner); + HandleCommand("t", command, owner); + return true; + } + + void HandlePacket(map &handlers, const WorldPacket &packet, Player* owner = NULL) + { + uint16 opcode = packet.GetOpcode(); + string name = handlers[opcode]; + if (name.empty()) + { + return; + } + + Trigger* trigger = aiObjectContext->GetTrigger(name); + if (!trigger) + { + return; + } + + WorldPacket p(packet); + trigger->ExternalEvent(p, owner); + } + + bool HandleCommand(string name, string param, Player* owner = NULL) + { + Trigger* trigger = aiObjectContext->GetTrigger(name); + if (!trigger) + { + return false; + } + + trigger->ExternalEvent(param, owner); + return true; + } + + private: + AiObjectContext* aiObjectContext; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/ItemVisitors.h b/src/modules/Bots/playerbot/strategy/ItemVisitors.h new file mode 100644 index 000000000..d0003b4a4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/ItemVisitors.h @@ -0,0 +1,284 @@ +#pragma once + +char * strstri (const char* str1, const char* str2); + +namespace ai +{ + class IterateItemsVisitor + { + public: + IterateItemsVisitor() {} + + virtual bool Visit(Item* item) = 0; + }; + + class FindItemVisitor : public IterateItemsVisitor { + public: + FindItemVisitor() : IterateItemsVisitor(), result(NULL) {} + + virtual bool Visit(Item* item) + { + if (!Accept(item->GetProto())) + { + return true; + } + + result.push_back(item); + return true; + } + + list& GetResult() { return result; } + + protected: + virtual bool Accept(const ItemPrototype* proto) = 0; + + private: + list result; + }; + + enum IterateItemsMask + { + ITERATE_ITEMS_IN_BAGS = 1, + ITERATE_ITEMS_IN_EQUIP = 2, + ITERATE_ALL_ITEMS = 255 + }; + + class FindUsableItemVisitor : public FindItemVisitor { + public: + FindUsableItemVisitor(Player* bot) : FindItemVisitor() + { + this->bot = bot; + } + + virtual bool Visit(Item* item) + { + if (bot->CanUseItem(item->GetProto()) == EQUIP_ERR_OK) + { + return FindItemVisitor::Visit(item); + } + + return true; + } + + private: + Player* bot; + }; + + + class FindItemsByQualityVisitor : public IterateItemsVisitor + { + public: + FindItemsByQualityVisitor(uint32 quality, int count) : IterateItemsVisitor() + { + this->quality = quality; + this->count = count; + } + + virtual bool Visit(Item* item) + { + if (item->GetProto()->Quality != quality) + { + return true; + } + + if (result.size() >= (size_t)count) + { + return false; + } + + result.push_back(item); + return true; + } + + list& GetResult() + { + return result; + } + + private: + uint32 quality; + int count; + list result; + }; + + class FindItemsToTradeByQualityVisitor : public FindItemsByQualityVisitor + { + public: + FindItemsToTradeByQualityVisitor(uint32 quality, int count) : FindItemsByQualityVisitor(quality, count) {} + + virtual bool Visit(Item* item) + { + if (item->IsSoulBound()) + { + return true; + } + + return FindItemsByQualityVisitor::Visit(item); + } + }; + + class FindItemsToTradeByClassVisitor : public IterateItemsVisitor + { + public: + FindItemsToTradeByClassVisitor(uint32 itemClass, uint32 itemSubClass, int count) + : IterateItemsVisitor(), count(count), itemClass(itemClass), itemSubClass(itemSubClass) {} + + virtual bool Visit(Item* item) + { + if (item->IsSoulBound()) + { + return true; + } + + if (item->GetProto()->Class != itemClass || item->GetProto()->SubClass != itemSubClass) + { + return true; + } + + if (result.size() >= (size_t)count) + { + return false; + } + + result.push_back(item); + return true; + } + + list& GetResult() + { + return result; + } + + private: + uint32 itemClass; + uint32 itemSubClass; + int count; + list result; + }; + + class QueryItemCountVisitor : public IterateItemsVisitor + { + public: + QueryItemCountVisitor(uint32 itemId) + { + count = 0; + this->itemId = itemId; + } + + virtual bool Visit(Item* item) + { + if (item->GetProto()->ItemId == itemId) + { + count += item->GetCount(); + } + + return true; + } + + int GetCount() { return count; } + + protected: + int count; + uint32 itemId; + }; + + + class QueryNamedItemCountVisitor : public QueryItemCountVisitor + { + public: + QueryNamedItemCountVisitor(string name) : QueryItemCountVisitor(0) + { + this->name = name; + } + + virtual bool Visit(Item* item) + { + const ItemPrototype* proto = item->GetProto(); + if (proto && !proto->Name1 && strstri(proto->Name1, name.c_str())) + { + count += item->GetCount(); + } + + return true; + } + + private: + string name; + }; + + class FindNamedItemVisitor : public FindItemVisitor { + public: + FindNamedItemVisitor(Player* bot, string name) : FindItemVisitor() + { + this->name = name; + } + + virtual bool Accept(const ItemPrototype* proto) + { + return proto && proto->Name1 && strstri(proto->Name1, name.c_str()); + } + + private: + string name; + }; + + class FindItemByIdVisitor : public FindItemVisitor { + public: + FindItemByIdVisitor(uint32 id) : FindItemVisitor() + { + this->id = id; + } + + virtual bool Accept(const ItemPrototype* proto) + { + return proto->ItemId == id; + } + + private: + uint32 id; + }; + + class ListItemsVisitor : public IterateItemsVisitor + { + public: + ListItemsVisitor() : IterateItemsVisitor() {} + + map items; + map soulbound; + + virtual bool Visit(Item* item) + { + uint32 id = item->GetProto()->ItemId; + + if (items.find(id) == items.end()) + { + items[id] = 0; + } + + items[id] += item->GetCount(); + soulbound[id] = item->IsSoulBound(); + return true; + } + }; + + class ItemCountByQuality : public IterateItemsVisitor + { + public: + ItemCountByQuality() : IterateItemsVisitor() + { + for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i) + { + count[i] = 0; + } + } + + virtual bool Visit(Item* item) + { + count[item->GetProto()->Quality]++; + return true; + } + + public: + map count; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/Multiplier.cpp b/src/modules/Bots/playerbot/strategy/Multiplier.cpp new file mode 100644 index 000000000..2c16b9ade --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Multiplier.cpp @@ -0,0 +1,5 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Multiplier.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/Multiplier.h b/src/modules/Bots/playerbot/strategy/Multiplier.h new file mode 100644 index 000000000..e86fa05c2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Multiplier.h @@ -0,0 +1,16 @@ +#pragma once +#include "Action.h" + +namespace ai +{ + class Multiplier : public AiNamedObject + { + public: + Multiplier(PlayerbotAI* ai, string name) : AiNamedObject(ai, name) {} + virtual ~Multiplier() {} + + public: + virtual float GetValue(Action* action) { return 1.0f; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/NamedObjectContext.h b/src/modules/Bots/playerbot/strategy/NamedObjectContext.h new file mode 100644 index 000000000..593d50100 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/NamedObjectContext.h @@ -0,0 +1,281 @@ +#pragma once + +namespace ai +{ + using namespace std; + + class Qualified + { + public: + Qualified() {}; + + public: + virtual void Qualify(string qualifier) { this->qualifier = qualifier; } + + protected: + string qualifier; + }; + + template class NamedObjectFactory + { + protected: + typedef T* (*ActionCreator) (PlayerbotAI* ai); + map creators; + + public: + T* create(string name, PlayerbotAI* ai) + { + size_t found = name.find("::"); + string qualifier; + if (found != string::npos) + { + qualifier = name.substr(found + 2); + name = name.substr(0, found); + } + + if (creators.find(name) == creators.end()) + { + return NULL; + } + + ActionCreator creator = creators[name]; + if (!creator) + { + return NULL; + } + + T *object = (*creator)(ai); + Qualified *q = dynamic_cast(object); + if (q) + { + q->Qualify(qualifier); + } + + return object; + } + + set supports() + { + set keys; + for (typename map::iterator it = creators.begin(); it != creators.end(); it++) + { + keys.insert(it->first); + } + return keys; + } + }; + + + template class NamedObjectContext : public NamedObjectFactory + { + public: + NamedObjectContext(bool shared = false, bool supportsSiblings = false) : + NamedObjectFactory(), shared(shared), supportsSiblings(supportsSiblings) {} + + T* create(string name, PlayerbotAI* ai) + { + if (created.find(name) == created.end()) + { + return created[name] = NamedObjectFactory::create(name, ai); + } + + return created[name]; + } + + virtual ~NamedObjectContext() + { + Clear(); + } + + void Clear() + { + for (typename map::iterator i = created.begin(); i != created.end(); i++) + { + if (i->second) + { + delete i->second; + } + } + + created.clear(); + } + + void Update() + { + for (typename map::iterator i = created.begin(); i != created.end(); i++) + { + if (i->second) + { + i->second->Update(); + } + } + } + + void Reset() + { + for (typename map::iterator i = created.begin(); i != created.end(); i++) + { + if (i->second) + { + i->second->Reset(); + } + } + } + + bool IsShared() { return shared; } + bool IsSupportsSiblings() { return supportsSiblings; } + + set GetCreated() + { + set keys; + for (typename map::iterator it = created.begin(); it != created.end(); it++) + { + keys.insert(it->first); + } + return keys; + } + + protected: + map created; + bool shared; + bool supportsSiblings; + }; + + template class NamedObjectContextList + { + public: + virtual ~NamedObjectContextList() + { + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + NamedObjectContext* context = *i; + if (!context->IsShared()) + { + delete context; + } + } + } + + void Add(NamedObjectContext* context) + { + contexts.push_back(context); + } + + T* GetObject(string name, PlayerbotAI* ai) + { + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + T* object = (*i)->create(name, ai); + if (object) return object; + } + return NULL; + } + + void Update() + { + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + if (!(*i)->IsShared()) + { + (*i)->Update(); + } + } + } + + void Reset() + { + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + (*i)->Reset(); + } + } + + set GetSiblings(string name) + { + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + if (!(*i)->IsSupportsSiblings()) + { + continue; + } + + set supported = (*i)->supports(); + set::iterator found = supported.find(name); + if (found == supported.end()) + { + continue; + } + + supported.erase(found); + return supported; + } + + return set(); + } + + set supports() + { + set result; + + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + set supported = (*i)->supports(); + + for (set::iterator j = supported.begin(); j != supported.end(); j++) + { + result.insert(*j); + } + } + return result; + } + + set GetCreated() + { + set result; + + for (typename list*>::iterator i = contexts.begin(); i != contexts.end(); i++) + { + set createdKeys = (*i)->GetCreated(); + + for (set::iterator j = createdKeys.begin(); j != createdKeys.end(); j++) + { + result.insert(*j); + } + } + return result; + } + + private: + list*> contexts; + }; + + template class NamedObjectFactoryList + { + public: + virtual ~NamedObjectFactoryList() + { + for (typename list*>::iterator i = factories.begin(); i != factories.end(); i++) + { + delete *i; + } + } + + void Add(NamedObjectFactory* context) + { + factories.push_front(context); + } + + T* GetObject(string name, PlayerbotAI* ai) + { + for (typename list*>::iterator i = factories.begin(); i != factories.end(); i++) + { + T* object = (*i)->create(name, ai); + if (object) return object; + } + return NULL; + } + + private: + list*> factories; + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/PassiveMultiplier.cpp b/src/modules/Bots/playerbot/strategy/PassiveMultiplier.cpp new file mode 100644 index 000000000..060805a9b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/PassiveMultiplier.cpp @@ -0,0 +1,53 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "PassiveMultiplier.h" + +using namespace ai; + +list PassiveMultiplier::allowedActions; +list PassiveMultiplier::allowedParts; + +PassiveMultiplier::PassiveMultiplier(PlayerbotAI* ai) : Multiplier(ai, "passive") +{ + if (allowedActions.empty()) + { + allowedActions.push_back("co"); + allowedActions.push_back("nc"); + allowedActions.push_back("reset ai"); + allowedActions.push_back("check mount state"); + } + + if (allowedParts.empty()) + { + allowedParts.push_back("follow"); + allowedParts.push_back("stay"); + allowedParts.push_back("chat shortcut"); + } +} + +float PassiveMultiplier::GetValue(Action* action) { + if (!action) + { + return 1.0f; + } + + string name = action->getName(); + + for (list::iterator i = allowedActions.begin(); i != allowedActions.end(); i++) + { + if (name == *i) + { + return 1.0f; + } + } + + for (list::iterator i = allowedParts.begin(); i != allowedParts.end(); i++) + { + if (name.find(*i) != string::npos) + { + return 1.0f; + } + } + + return 0; +} diff --git a/src/modules/Bots/playerbot/strategy/PassiveMultiplier.h b/src/modules/Bots/playerbot/strategy/PassiveMultiplier.h new file mode 100644 index 000000000..37985a06e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/PassiveMultiplier.h @@ -0,0 +1,20 @@ +#pragma once +#include "Action.h" +#include "Multiplier.h" + +namespace ai +{ + class PassiveMultiplier : public Multiplier + { + public: + PassiveMultiplier(PlayerbotAI* ai); + + public: + virtual float GetValue(Action* action); + + private: + static list allowedActions; + static list allowedParts; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/Queue.cpp b/src/modules/Bots/playerbot/strategy/Queue.cpp new file mode 100644 index 000000000..03d8395c0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Queue.cpp @@ -0,0 +1,115 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Action.h" +#include "Queue.h" + +#include "../PlayerbotAIConfig.h" +using namespace ai; + + +void Queue::Push(ActionBasket *action) +{ + if (action) + { + for (std::list::iterator iter = actions.begin(); iter != actions.end(); iter++) + { + ActionBasket* basket = *iter; + if (action->getAction()->getName() == basket->getAction()->getName()) + { + if (basket->getRelevance() < action->getRelevance()) + { + basket->setRelevance(action->getRelevance()); + } + ActionNode *actionNode = action->getAction(); + if (actionNode) + { + delete actionNode; + } + delete action; + return; + } + } + actions.push_back(action); + } +} + +void Queue::Push(ActionBasket **actions) +{ + if (actions) + { + for (int i=0; i::iterator iter = actions.begin(); iter != actions.end(); iter++) + { + ActionBasket* basket = *iter; + if (basket->getRelevance() > max) + { + max = basket->getRelevance(); + selection = basket; + } + } + if (selection != NULL) + { + ActionNode* action = selection->getAction(); + actions.remove(selection); + delete selection; + return action; + } + return NULL; +} + +ActionBasket* Queue::Peek() +{ + float max = -1; + ActionBasket* selection = NULL; + for (std::list::iterator iter = actions.begin(); iter != actions.end(); iter++) + { + ActionBasket* basket = *iter; + if (basket->getRelevance() > max) + { + max = basket->getRelevance(); + selection = basket; + } + } + return selection; +} + +int Queue::Size() +{ + return actions.size(); +} + +void Queue::RemoveExpired() +{ + list expired; + for (std::list::iterator iter = actions.begin(); iter != actions.end(); iter++) + { + ActionBasket* basket = *iter; + if (sPlayerbotAIConfig.expireActionTime && basket->isExpired(sPlayerbotAIConfig.expireActionTime / 1000)) + { + expired.push_back(basket); + } + } + + for (std::list::iterator iter = expired.begin(); iter != expired.end(); iter++) + { + ActionBasket* basket = *iter; + actions.remove(basket); + ActionNode* action = basket->getAction(); + if (action) + { + sLog.outDebug("Action %s is expired", action->getName().c_str()); + delete action; + } + delete basket; + } +} diff --git a/src/modules/Bots/playerbot/strategy/Queue.h b/src/modules/Bots/playerbot/strategy/Queue.h new file mode 100644 index 000000000..3811a02bf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Queue.h @@ -0,0 +1,22 @@ +#include "ActionBasket.h" + +#pragma once +namespace ai +{ +class Queue +{ +public: + Queue(void) {} +public: + ~Queue(void) {} +public: + void Push(ActionBasket *action); + void Push(ActionBasket **actions); + ActionNode* Pop(); + ActionBasket* Peek(); + int Size(); + void RemoveExpired(); +private: + std::list actions; +}; +} diff --git a/src/modules/Bots/playerbot/strategy/Strategy.cpp b/src/modules/Bots/playerbot/strategy/Strategy.cpp new file mode 100644 index 000000000..eca671304 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Strategy.cpp @@ -0,0 +1,117 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Strategy.h" +#include "NamedObjectContext.h" + +using namespace ai; +using namespace std; + + +class ActionNodeFactoryInternal : public NamedObjectFactory +{ +public: + ActionNodeFactoryInternal() + { + creators["melee"] = &melee; + creators["healthstone"] = &healthstone; + creators["be near"] = &follow_master_random; + creators["attack anything"] = &attack_anything; + creators["move random"] = &move_random; + creators["move to loot"] = &move_to_loot; + creators["food"] = &food; + creators["drink"] = &drink; + creators["mana potion"] = &mana_potion; + creators["healing potion"] = &healing_potion; + creators["flee"] = &flee; + } + +private: + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* healthstone(PlayerbotAI* ai) + { + return new ActionNode ("healthstone", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("healing potion"), NULL), + /*C*/ NULL); + } + static ActionNode* follow_master_random(PlayerbotAI* ai) + { + return new ActionNode ("be near", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("follow"), NULL), + /*C*/ NULL); + } + static ActionNode* attack_anything(PlayerbotAI* ai) + { + return new ActionNode ("attack anything", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* move_random(PlayerbotAI* ai) + { + return new ActionNode ("move random", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("stay line"), NULL), + /*C*/ NULL); + } + static ActionNode* move_to_loot(PlayerbotAI* ai) + { + return new ActionNode ("move to loot", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* food(PlayerbotAI* ai) + { + return new ActionNode ("food", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* drink(PlayerbotAI* ai) + { + return new ActionNode ("drink", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* mana_potion(PlayerbotAI* ai) + { + return new ActionNode ("mana potion", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("drink"), NULL), + /*C*/ NULL); + } + static ActionNode* healing_potion(PlayerbotAI* ai) + { + return new ActionNode ("healing potion", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("food"), NULL), + /*C*/ NULL); + } + static ActionNode* flee(PlayerbotAI* ai) + { + return new ActionNode ("flee", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } +}; + +Strategy::Strategy(PlayerbotAI* ai) : PlayerbotAIAware(ai) +{ + actionNodeFactories.Add(new ActionNodeFactoryInternal()); +} + +ActionNode* Strategy::GetAction(string name) +{ + return actionNodeFactories.GetObject(name, ai); +} + diff --git a/src/modules/Bots/playerbot/strategy/Strategy.h b/src/modules/Bots/playerbot/strategy/Strategy.h new file mode 100644 index 000000000..a2cbe40d1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Strategy.h @@ -0,0 +1,55 @@ +#pragma once +#include "Action.h" +#include "Multiplier.h" +#include "Trigger.h" +#include "NamedObjectContext.h" + +namespace ai +{ + enum StrategyType + { + STRATEGY_TYPE_GENERIC = 0, + STRATEGY_TYPE_COMBAT = 1, + STRATEGY_TYPE_NONCOMBAT = 2, + STRATEGY_TYPE_TANK = 4, + STRATEGY_TYPE_DPS = 8, + STRATEGY_TYPE_HEAL = 16, + STRATEGY_TYPE_RANGED = 32, + STRATEGY_TYPE_MELEE = 64 + }; + + enum ActionPriority + { + ACTION_IDLE = 0, + ACTION_NORMAL = 10, + ACTION_HIGH = 20, + ACTION_MOVE = 30, + ACTION_INTERRUPT = 40, + ACTION_DISPEL = 50, + ACTION_LIGHT_HEAL = 60, + ACTION_MEDIUM_HEAL = 70, + ACTION_CRITICAL_HEAL = 80, + ACTION_EMERGENCY = 90 + }; + + class Strategy : public PlayerbotAIAware + { + public: + Strategy(PlayerbotAI* ai); + virtual ~Strategy() {} + + public: + virtual NextAction** getDefaultActions() { return NULL; } + virtual void InitTriggers(std::list &triggers) {} + virtual void InitMultipliers(std::list &multipliers) {} + virtual string getName() = 0; + virtual int GetType() { return STRATEGY_TYPE_GENERIC; } + virtual ActionNode* GetAction(string name); + void Update() {} + void Reset() {} + + protected: + NamedObjectFactoryList actionNodeFactories; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/StrategyContext.h b/src/modules/Bots/playerbot/strategy/StrategyContext.h new file mode 100644 index 000000000..c6380ee35 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/StrategyContext.h @@ -0,0 +1,139 @@ +#pragma once + +#include "CustomStrategy.h" +#include "generic/NonCombatStrategy.h" +#include "generic/RacialsStrategy.h" +#include "generic/ChatCommandHandlerStrategy.h" +#include "generic/WorldPacketHandlerStrategy.h" +#include "generic/DeadStrategy.h" +#include "generic/QuestStrategies.h" +#include "generic/LootNonCombatStrategy.h" +#include "generic/DuelStrategy.h" +#include "generic/KiteStrategy.h" +#include "generic/FleeStrategy.h" +#include "generic/FollowMasterStrategy.h" +#include "generic/RunawayStrategy.h" +#include "generic/StayStrategy.h" +#include "generic/UseFoodStrategy.h" +#include "generic/ConserveManaStrategy.h" +#include "generic/EmoteStrategy.h" +#include "generic/TankAoeStrategy.h" +#include "generic/DpsAssistStrategy.h" +#include "generic/PassiveStrategy.h" +#include "generic/GrindingStrategy.h" +#include "generic/UsePotionsStrategy.h" +#include "generic/GuardStrategy.h" +#include "generic/CastTimeStrategy.h" +#include "generic/ThreatStrategy.h" +#include "generic/TellTargetStrategy.h" +#include "generic/AttackEnemyPlayersStrategy.h" +#include "generic/MoveRandomStrategy.h" + +namespace ai +{ + class StrategyContext : public NamedObjectContext + { + public: + StrategyContext() + { + creators["racials"] = &StrategyContext::racials; + creators["loot"] = &StrategyContext::loot; + creators["gather"] = &StrategyContext::gather; + creators["emote"] = &StrategyContext::emote; + creators["passive"] = &StrategyContext::passive; + creators["conserve mana"] = &StrategyContext::conserve_mana; + creators["food"] = &StrategyContext::food; + creators["chat"] = &StrategyContext::chat; + creators["default"] = &StrategyContext::world_packet; + creators["ready check"] = &StrategyContext::ready_check; + creators["dead"] = &StrategyContext::dead; + creators["flee"] = &StrategyContext::flee; + creators["duel"] = &StrategyContext::duel; + creators["kite"] = &StrategyContext::kite; + creators["potions"] = &StrategyContext::potions; + creators["cast time"] = &StrategyContext::cast_time; + creators["threat"] = &StrategyContext::threat; + creators["tell target"] = &StrategyContext::tell_target; + creators["pvp"] = &StrategyContext::pvp; + creators["move random"] = &StrategyContext::move_random; + creators["lfg"] = &StrategyContext::lfg; + creators["custom"] = &StrategyContext::custom; + creators["reveal"] = &StrategyContext::reveal; + } + + private: + static Strategy* tell_target(PlayerbotAI* ai) { return new TellTargetStrategy(ai); } + static Strategy* threat(PlayerbotAI* ai) { return new ThreatStrategy(ai); } + static Strategy* cast_time(PlayerbotAI* ai) { return new CastTimeStrategy(ai); } + static Strategy* potions(PlayerbotAI* ai) { return new UsePotionsStrategy(ai); } + static Strategy* kite(PlayerbotAI* ai) { return new KiteStrategy(ai); } + static Strategy* duel(PlayerbotAI* ai) { return new DuelStrategy(ai); } + static Strategy* flee(PlayerbotAI* ai) { return new FleeStrategy(ai); } + static Strategy* dead(PlayerbotAI* ai) { return new DeadStrategy(ai); } + static Strategy* racials(PlayerbotAI* ai) { return new RacialsStrategy(ai); } + static Strategy* loot(PlayerbotAI* ai) { return new LootNonCombatStrategy(ai); } + static Strategy* gather(PlayerbotAI* ai) { return new GatherStrategy(ai); } + static Strategy* emote(PlayerbotAI* ai) { return new EmoteStrategy(ai); } + static Strategy* passive(PlayerbotAI* ai) { return new PassiveStrategy(ai); } + static Strategy* conserve_mana(PlayerbotAI* ai) { return new ConserveManaStrategy(ai); } + static Strategy* food(PlayerbotAI* ai) { return new UseFoodStrategy(ai); } + static Strategy* chat(PlayerbotAI* ai) { return new ChatCommandHandlerStrategy(ai); } + static Strategy* world_packet(PlayerbotAI* ai) { return new WorldPacketHandlerStrategy(ai); } + static Strategy* ready_check(PlayerbotAI* ai) { return new ReadyCheckStrategy(ai); } + static Strategy* pvp(PlayerbotAI* ai) { return new AttackEnemyPlayersStrategy(ai); } + static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); } + static Strategy* lfg(PlayerbotAI* ai) { return new LfgStrategy(ai); } + static Strategy* custom(PlayerbotAI* ai) { return new CustomStrategy(ai); } + static Strategy* reveal(PlayerbotAI* ai) { return new RevealStrategy(ai); } + }; + + class MovementStrategyContext : public NamedObjectContext + { + public: + MovementStrategyContext() : NamedObjectContext(false, true) + { + creators["follow"] = &MovementStrategyContext::follow_master; + creators["stay"] = &MovementStrategyContext::stay; + creators["runaway"] = &MovementStrategyContext::runaway; + creators["flee from adds"] = &MovementStrategyContext::flee_from_adds; + creators["guard"] = &MovementStrategyContext::guard; + } + + private: + static Strategy* guard(PlayerbotAI* ai) { return new GuardStrategy(ai); } + static Strategy* follow_master(PlayerbotAI* ai) { return new FollowMasterStrategy(ai); } + static Strategy* stay(PlayerbotAI* ai) { return new StayStrategy(ai); } + static Strategy* runaway(PlayerbotAI* ai) { return new RunawayStrategy(ai); } + static Strategy* flee_from_adds(PlayerbotAI* ai) { return new FleeFromAddsStrategy(ai); } + }; + + class AssistStrategyContext : public NamedObjectContext + { + public: + AssistStrategyContext() : NamedObjectContext(false, true) + { + creators["dps assist"] = &AssistStrategyContext::dps_assist; + creators["tank aoe"] = &AssistStrategyContext::tank_aoe; + creators["grind"] = &AssistStrategyContext::grind; + } + + private: + static Strategy* dps_assist(PlayerbotAI* ai) { return new DpsAssistStrategy(ai); } + static Strategy* tank_aoe(PlayerbotAI* ai) { return new TankAoeStrategy(ai); } + static Strategy* grind(PlayerbotAI* ai) { return new GrindingStrategy(ai); } + }; + + class QuestStrategyContext : public NamedObjectContext + { + public: + QuestStrategyContext() : NamedObjectContext(false, true) + { + creators["quest"] = &QuestStrategyContext::quest; + creators["accept all quests"] = &QuestStrategyContext::accept_all_quests; + } + + private: + static Strategy* quest(PlayerbotAI* ai) { return new DefaultQuestStrategy(ai); } + static Strategy* accept_all_quests(PlayerbotAI* ai) { return new AcceptAllQuestsStrategy(ai); } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/Trigger.cpp b/src/modules/Bots/playerbot/strategy/Trigger.cpp new file mode 100644 index 000000000..b8ab30b34 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Trigger.cpp @@ -0,0 +1,27 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Trigger.h" +#include "Action.h" + +using namespace ai; + +Event Trigger::Check() +{ + if (IsActive()) + { + Event event(getName()); + return event; + } + Event event; + return event; +} + +Value* Trigger::GetTargetValue() +{ + return context->GetValue(GetTargetName()); +} + +Unit* Trigger::GetTarget() +{ + return GetTargetValue()->Get(); +} diff --git a/src/modules/Bots/playerbot/strategy/Trigger.h b/src/modules/Bots/playerbot/strategy/Trigger.h new file mode 100644 index 000000000..4ce3cbbaa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Trigger.h @@ -0,0 +1,89 @@ +#pragma once +#include "Action.h" +#include "Event.h" +#include "../PlayerbotAIAware.h" + +#define NEXT_TRIGGERS(name, relevance) \ + virtual NextAction* getNextAction() { return new NextAction(name, relevance); } + +#define BEGIN_TRIGGER(clazz, super) \ +class clazz : public super \ + { \ + public: \ + clazz(PlayerbotAI* ai) : super(ai) {} \ + public: \ + virtual bool IsActive(); + +#define END_TRIGGER() \ + }; + +namespace ai +{ + class Trigger : public AiNamedObject + { + public: + Trigger(PlayerbotAI* ai, string name = "trigger", int checkInterval = 1) : AiNamedObject(ai, name) { + this->checkInterval = checkInterval; + lastCheckTime = time(0) - rand() % checkInterval; + } + virtual ~Trigger() {} + + public: + virtual Event Check(); + virtual void ExternalEvent(string param, Player* owner = NULL) {} + virtual void ExternalEvent(WorldPacket &packet, Player* owner = NULL) {} + virtual bool IsActive() { return false; } + virtual NextAction** getHandlers() { return NULL; } + void Update() {} + virtual void Reset() { } + virtual Unit* GetTarget(); + virtual Value* GetTargetValue(); + virtual string GetTargetName() { return "self target"; } + + bool needCheck() { + if (checkInterval < 2) return true; + + time_t now = time(0); + if (!lastCheckTime || now - lastCheckTime >= checkInterval) { + { + lastCheckTime = now; + } + return true; + } + return false; + } + + protected: + int checkInterval; + time_t lastCheckTime; + }; + + + class TriggerNode + { + public: + TriggerNode(string name, NextAction** handlers = NULL) + { + this->name = name; + this->handlers = handlers; + this->trigger = NULL; + } + virtual ~TriggerNode() + { + NextAction::destroy(handlers); + } + + public: + Trigger* getTrigger() { return trigger; } + void setTrigger(Trigger* trigger) { this->trigger = trigger; } + string getName() { return name; } + + public: + NextAction** getHandlers() { return NextAction::merge(NextAction::clone(handlers), trigger->getHandlers()); } + + private: + Trigger* trigger; + NextAction** handlers; + std::string name; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/Value.cpp b/src/modules/Bots/playerbot/strategy/Value.cpp new file mode 100644 index 000000000..66a7f0d0b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Value.cpp @@ -0,0 +1,5 @@ +#include "../../botpch.h" +#include "../playerbot.h" +#include "Value.h" + +using namespace ai; diff --git a/src/modules/Bots/playerbot/strategy/Value.h b/src/modules/Bots/playerbot/strategy/Value.h new file mode 100644 index 000000000..a86f3cff9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/Value.h @@ -0,0 +1,177 @@ +#pragma once +#include "Action.h" +#include "Event.h" +#include "../PlayerbotAIAware.h" +#include "AiObject.h" + +namespace ai +{ + class UntypedValue : public AiNamedObject + { + public: + UntypedValue(PlayerbotAI* ai, string name) : AiNamedObject(ai, name) {} + virtual void Update() {} + virtual void Reset() {} + virtual string Format() { return "?"; } + }; + + template + class Value + { + public: + virtual T Get() = 0; + virtual void Set(T value) = 0; + operator T() { return Get(); } + }; + + template + class CalculatedValue : public UntypedValue, public Value + { + public: + CalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : UntypedValue(ai, name), + checkInterval(checkInterval) + { + lastCheckTime = time(0) - rand() % checkInterval; + } + virtual ~CalculatedValue() {} + + public: + virtual T Get() + { + time_t now = time(0); + if (!lastCheckTime || checkInterval < 2 || now - lastCheckTime >= checkInterval) { + { + lastCheckTime = now; + } + value = Calculate(); + } + return value; + } + virtual void Set(T value) { this->value = value; } + virtual void Update() { } + + protected: + virtual T Calculate() = 0; + + protected: + int checkInterval; + time_t lastCheckTime; + T value; + }; + + class Uint8CalculatedValue : public CalculatedValue + { + public: + Uint8CalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue(ai, name, checkInterval) {} + + virtual string Format() + { + ostringstream out; out << (int)Calculate(); + return out.str(); + } + }; + + class Uint32CalculatedValue : public CalculatedValue + { + public: + Uint32CalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue(ai, name, checkInterval) {} + + virtual string Format() + { + ostringstream out; out << (int)Calculate(); + return out.str(); + } + }; + + class FloatCalculatedValue : public CalculatedValue + { + public: + FloatCalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue(ai, name, checkInterval) {} + + virtual string Format() + { + ostringstream out; out << Calculate(); + return out.str(); + } + }; + + class BoolCalculatedValue : public CalculatedValue + { + public: + BoolCalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue(ai, name, checkInterval) {} + + virtual string Format() + { + return Calculate() ? "true" : "false"; + } + }; + + class UnitCalculatedValue : public CalculatedValue + { + public: + UnitCalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue(ai, name, checkInterval) {} + + virtual string Format() + { + Unit* unit = Calculate(); + return unit ? unit->GetName() : ""; + } + }; + + class ObjectGuidListCalculatedValue : public CalculatedValue > + { + public: + ObjectGuidListCalculatedValue(PlayerbotAI* ai, string name = "value", int checkInterval = 1) : + CalculatedValue >(ai, name, checkInterval) {} + + virtual string Format() + { + ostringstream out; out << "{"; + list guids = Calculate(); + for (list::iterator i = guids.begin(); i != guids.end(); ++i) + { + ObjectGuid guid = *i; + out << guid.GetRawValue() << ","; + } + out << "}"; + return out.str(); + } + }; + + template + class ManualSetValue : public UntypedValue, public Value + { + public: + ManualSetValue(PlayerbotAI* ai, T defaultValue, string name = "value") : + UntypedValue(ai, name), value(defaultValue), defaultValue(defaultValue) {} + virtual ~ManualSetValue() {} + + public: + virtual T Get() { return value; } + virtual void Set(T value) { this->value = value; } + virtual void Update() { } + virtual void Reset() { value = defaultValue; } + + protected: + T value; + T defaultValue; + }; + + class UnitManualSetValue : public ManualSetValue + { + public: + UnitManualSetValue(PlayerbotAI* ai, Unit* defaultValue, string name = "value") : + ManualSetValue(ai, defaultValue, name) {} + + virtual string Format() + { + Unit* unit = Get(); + return unit ? unit->GetName() : ""; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AcceptDuelAction.h b/src/modules/Bots/playerbot/strategy/actions/AcceptDuelAction.h new file mode 100644 index 000000000..461ea7e7b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AcceptDuelAction.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class AcceptDuelAction : public Action + { + public: + AcceptDuelAction(PlayerbotAI* ai) : Action(ai, "accept duel") + {} + + virtual bool Execute(Event event) + { + WorldPacket p(event.getPacket()); + + ObjectGuid flagGuid; + p >> flagGuid; + ObjectGuid playerGuid; + p >> playerGuid; + + WorldPacket* const packet = new WorldPacket(CMSG_DUEL_ACCEPTED, 8); + *packet << flagGuid; + bot->GetSession()->QueuePacket(packet); + + ai->ResetStrategies(); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AcceptInvitationAction.h b/src/modules/Bots/playerbot/strategy/actions/AcceptInvitationAction.h new file mode 100644 index 000000000..41191213b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AcceptInvitationAction.h @@ -0,0 +1,52 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class AcceptInvitationAction : public Action { + public: + AcceptInvitationAction(PlayerbotAI* ai) : Action(ai, "accept invitation") {} + + virtual bool Execute(Event event) + { + Player* master = GetMaster(); + + Group* grp = bot->GetGroupInvite(); + if (!grp) + { + return false; + } + + Player* inviter = sObjectMgr.GetPlayer(grp->GetLeaderGuid()); + if (!inviter) + { + return false; + } + + if (!ai->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter)) + { + WorldPacket data(SMSG_GROUP_DECLINE, 10); + data << bot->GetName(); + inviter->GetSession()->SendPacket(&data); + bot->UninviteFromGroup(); + return false; + } + + WorldPacket p; + uint32 roles_mask = 0; + p << roles_mask; + bot->GetSession()->HandleGroupAcceptOpcode(p); + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + bot->GetPlayerbotAI()->SetMaster(inviter); + } + + ai->ResetStrategies(); + ai->TellMaster("Hello"); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.cpp b/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.cpp new file mode 100644 index 000000000..8eb920caf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.cpp @@ -0,0 +1,137 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AcceptQuestAction.h" + +using namespace ai; + +void AcceptAllQuestsAction::ProcessQuest(Quest const* quest, WorldObject* questGiver) +{ + AcceptQuest(quest, questGiver->GetObjectGuid()); +} + +bool AcceptQuestAction::Execute(Event event) +{ + Player* master = GetMaster(); + + if (!master) + { + return false; + } + + Player *bot = ai->GetBot(); + uint64 guid = 0; + uint32 quest = 0; + + string text = event.getParam(); + PlayerbotChatHandler ch(master); + quest = ch.extractQuestId(text); + + if (event.getPacket().empty()) + { + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (unit && quest && unit->HasQuest(quest)) + { + guid = unit->GetObjectGuid().GetRawValue(); + break; + } + if (unit && text == "*" && bot->GetDistance(unit) <= INTERACTION_DISTANCE) + { + QuestAction::ProcessQuests(unit); + } + } + list gos = AI_VALUE(list, "nearest game objects"); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + GameObject* go = ai->GetGameObject(*i); + if (go && quest && go->HasQuest(quest)) + { + guid = go->GetObjectGuid().GetRawValue(); + break; + } + if (go && text == "*" && bot->GetDistance(go) <= INTERACTION_DISTANCE) + { + QuestAction::ProcessQuests(go); + } + } + } + else + { + WorldPacket& p = event.getPacket(); + p.rpos(0); + p >> guid >> quest; + } + + if (!quest || !guid) + { + return false; + } + + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest); + if (!qInfo) + { + return false; + } + + return AcceptQuest(qInfo, guid); +} + +bool AcceptQuestShareAction::Execute(Event event) +{ + Player* master = GetMaster(); + Player *bot = ai->GetBot(); + + WorldPacket& p = event.getPacket(); + p.rpos(0); + uint32 quest; + p >> quest; + Quest const* qInfo = sObjectMgr.GetQuestTemplate(quest); + + if (!qInfo || !bot->GetDividerGuid()) + { + return false; + } + + quest = qInfo->GetQuestId(); + if( !bot->CanTakeQuest( qInfo, false ) ) + { + // can't take quest + bot->SetDividerGuid( ObjectGuid() ); + ai->TellMaster("I can't take this quest"); + + return false; + } + + if( !bot->GetDividerGuid().IsEmpty() ) + { + // send msg to quest giving player + master->SendPushToPartyResponse( bot, QUEST_PARTY_MSG_ACCEPT_QUEST ); + bot->SetDividerGuid( ObjectGuid() ); + } + + if( bot->CanAddQuest( qInfo, false ) ) + { + bot->AddQuest( qInfo, master ); + + if( bot->CanCompleteQuest( quest ) ) + { + bot->CompleteQuest( quest ); + } + + // Runsttren: did not add typeid switch from WorldSession::HandleQuestgiverAcceptQuestOpcode! + // I think it's not needed, cause typeid should be TYPEID_PLAYER - and this one is not handled + // there and there is no default case also. + + if( qInfo->GetSrcSpell() > 0 ) + { + bot->CastSpell( bot, qInfo->GetSrcSpell(), true ); + } + + ai->TellMaster("Quest accepted"); + return true; + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.h b/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.h new file mode 100644 index 000000000..856c69718 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AcceptQuestAction.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class AcceptAllQuestsAction : public QuestAction { + public: + AcceptAllQuestsAction(PlayerbotAI* ai, string name = "accept all quests") : QuestAction(ai, name) {} + + protected: + virtual void ProcessQuest(Quest const* quest, WorldObject* questGiver); + }; + + class AcceptQuestAction : public AcceptAllQuestsAction { + public: + AcceptQuestAction(PlayerbotAI* ai) : AcceptAllQuestsAction(ai, "accept quest") {} + virtual bool Execute(Event event); + }; + + class AcceptQuestShareAction : public Action { + public: + AcceptQuestShareAction(PlayerbotAI* ai) : Action(ai, "accept quest share") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AcceptResurrectAction.h b/src/modules/Bots/playerbot/strategy/actions/AcceptResurrectAction.h new file mode 100644 index 000000000..b1cfd29ca --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AcceptResurrectAction.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class AcceptResurrectAction : public Action { + public: + AcceptResurrectAction(PlayerbotAI* ai) : Action(ai, "accept resurrect") {} + + virtual bool Execute(Event event) + { + if (bot->IsAlive()) + { + return false; + } + + WorldPacket p(event.getPacket()); + p.rpos(0); + ObjectGuid guid; + p >> guid; + + WorldPacket* const packet = new WorldPacket(CMSG_RESURRECT_RESPONSE, 8+1); + *packet << guid; + *packet << uint8(1); // accept + bot->GetSession()->QueuePacket(packet); // queue the packet to get around race condition + + ai->ChangeEngine(BOT_STATE_NON_COMBAT); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ActionContext.h b/src/modules/Bots/playerbot/strategy/actions/ActionContext.h new file mode 100644 index 000000000..e263a1bf0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ActionContext.h @@ -0,0 +1,133 @@ +#pragma once + +#include "GenericActions.h" +#include "NonCombatActions.h" +#include "EmoteAction.h" +#include "AddLootAction.h" +#include "LootAction.h" +#include "AddLootAction.h" +#include "StayActions.h" +#include "FollowActions.h" +#include "ChangeStrategyAction.h" +#include "ChooseTargetActions.h" +#include "SuggestWhatToDoAction.h" +#include "PositionAction.h" +#include "AttackAction.h" +#include "CheckMailAction.h" +#include "DelayAction.h" +#include "GreetAction.h" +#include "OutfitAction.h" +#include "RevealGatheringItemAction.h" +#include "SayAction.h" +#include "OutfitAction.h" +#include "RandomBotUpdateAction.h" + +namespace ai +{ + class ActionContext : public NamedObjectContext + { + public: + ActionContext() + { + creators["attack"] = &ActionContext::melee; + creators["melee"] = &ActionContext::melee; + creators["reach spell"] = &ActionContext::ReachSpell; + creators["reach melee"] = &ActionContext::ReachMelee; + creators["flee"] = &ActionContext::flee; + creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; + creators["shoot"] = &ActionContext::shoot; + creators["lifeblood"] = &ActionContext::lifeblood; + creators["arcane torrent"] = &ActionContext::arcane_torrent; + creators["end pull"] = &ActionContext::end_pull; + creators["healthstone"] = &ActionContext::healthstone; + creators["healing potion"] = &ActionContext::healing_potion; + creators["mana potion"] = &ActionContext::mana_potion; + creators["food"] = &ActionContext::food; + creators["drink"] = &ActionContext::drink; + creators["tank assist"] = &ActionContext::tank_assist; + creators["dps assist"] = &ActionContext::dps_assist; + creators["attack rti target"] = &ActionContext::attack_rti_target; + creators["loot"] = &ActionContext::loot; + creators["add loot"] = &ActionContext::add_loot; + creators["add gathering loot"] = &ActionContext::add_gathering_loot; + creators["add all loot"] = &ActionContext::add_all_loot; + creators["release loot"] = &ActionContext::release_loot; + creators["shoot"] = &ActionContext::shoot; + creators["follow"] = &ActionContext::follow; + creators["follow"] = &ActionContext::follow; + creators["runaway"] = &ActionContext::runaway; + creators["stay"] = &ActionContext::stay; + creators["attack anything"] = &ActionContext::attack_anything; + creators["attack least hp target"] = &ActionContext::attack_least_hp_target; + creators["attack enemy player"] = &ActionContext::enemy_player_target; + creators["emote"] = &ActionContext::emote; + creators["suggest what to do"] = &ActionContext::suggest_what_to_do; + creators["suggest trade"] = &ActionContext::suggest_trade; + creators["move random"] = &ActionContext::move_random; + creators["move to loot"] = &ActionContext::move_to_loot; + creators["open loot"] = &ActionContext::open_loot; + creators["guard"] = &ActionContext::guard; + creators["move out of enemy contact"] = &ActionContext::move_out_of_enemy_contact; + creators["set facing"] = &ActionContext::set_facing; + creators["attack duel opponent"] = &ActionContext::attack_duel_opponent; + creators["drop target"] = &ActionContext::drop_target; + creators["check mail"] = &ActionContext::check_mail; + creators["say"] = &ActionContext::say; + creators["reveal gathering item"] = &ActionContext::reveal_gathering_item; + creators["outfit"] = &ActionContext::outfit; + creators["random bot update"] = &ActionContext::random_bot_update; + creators["delay"] = &ActionContext::delay; + creators["greet"] = &ActionContext::greet; + } + + private: + static Action* greet(PlayerbotAI* ai) { return new GreetAction(ai); } + static Action* check_mail(PlayerbotAI* ai) { return new CheckMailAction(ai); } + static Action* drop_target(PlayerbotAI* ai) { return new DropTargetAction(ai); } + static Action* attack_duel_opponent(PlayerbotAI* ai) { return new AttackDuelOpponentAction(ai); } + static Action* guard(PlayerbotAI* ai) { return new GuardAction(ai); } + static Action* open_loot(PlayerbotAI* ai) { return new OpenLootAction(ai); } + static Action* move_to_loot(PlayerbotAI* ai) { return new MoveToLootAction(ai); } + static Action* move_random(PlayerbotAI* ai) { return new MoveRandomAction(ai); } + static Action* shoot(PlayerbotAI* ai) { return new CastShootAction(ai); } + static Action* melee(PlayerbotAI* ai) { return new MeleeAction(ai); } + static Action* ReachSpell(PlayerbotAI* ai) { return new ReachSpellAction(ai); } + static Action* ReachMelee(PlayerbotAI* ai) { return new ReachMeleeAction(ai); } + static Action* flee(PlayerbotAI* ai) { return new FleeAction(ai); } + static Action* gift_of_the_naaru(PlayerbotAI* ai) { return new CastGiftOfTheNaaruAction(ai); } + static Action* lifeblood(PlayerbotAI* ai) { return new CastLifeBloodAction(ai); } + static Action* arcane_torrent(PlayerbotAI* ai) { return new CastArcaneTorrentAction(ai); } + static Action* end_pull(PlayerbotAI* ai) { return new ChangeCombatStrategyAction(ai, "-pull"); } + + static Action* emote(PlayerbotAI* ai) { return new EmoteAction(ai); } + static Action* suggest_what_to_do(PlayerbotAI* ai) { return new SuggestWhatToDoAction(ai); } + static Action* suggest_trade(PlayerbotAI* ai) { return new SuggestTradeAction(ai); } + static Action* attack_anything(PlayerbotAI* ai) { return new AttackAnythingAction(ai); } + static Action* attack_least_hp_target(PlayerbotAI* ai) { return new AttackLeastHpTargetAction(ai); } + static Action* enemy_player_target(PlayerbotAI* ai) { return new AttackEnemyPlayerAction(ai); } + static Action* stay(PlayerbotAI* ai) { return new StayAction(ai); } + static Action* runaway(PlayerbotAI* ai) { return new RunAwayAction(ai); } + static Action* follow(PlayerbotAI* ai) { return new FollowAction(ai); } + static Action* add_gathering_loot(PlayerbotAI* ai) { return new AddGatheringLootAction(ai); } + static Action* add_loot(PlayerbotAI* ai) { return new AddLootAction(ai); } + static Action* add_all_loot(PlayerbotAI* ai) { return new AddAllLootAction(ai); } + static Action* loot(PlayerbotAI* ai) { return new LootAction(ai); } + static Action* release_loot(PlayerbotAI* ai) { return new ReleaseLootAction(ai); } + static Action* dps_assist(PlayerbotAI* ai) { return new DpsAssistAction(ai); } + static Action* attack_rti_target(PlayerbotAI* ai) { return new AttackRtiTargetAction(ai); } + static Action* tank_assist(PlayerbotAI* ai) { return new TankAssistAction(ai); } + static Action* drink(PlayerbotAI* ai) { return new DrinkAction(ai); } + static Action* food(PlayerbotAI* ai) { return new EatAction(ai); } + static Action* mana_potion(PlayerbotAI* ai) { return new UseManaPotion(ai); } + static Action* healing_potion(PlayerbotAI* ai) { return new UseHealingPotion(ai); } + static Action* healthstone(PlayerbotAI* ai) { return new UseItemAction(ai, "healthstone"); } + static Action* move_out_of_enemy_contact(PlayerbotAI* ai) { return new MoveOutOfEnemyContactAction(ai); } + static Action* set_facing(PlayerbotAI* ai) { return new SetFacingTargetAction(ai); } + static Action* say(PlayerbotAI* ai) { return new SayAction(ai); } + static Action* reveal_gathering_item(PlayerbotAI* ai) { return new RevealGatheringItemAction(ai); } + static Action* outfit(PlayerbotAI* ai) { return new OutfitAction(ai); } + static Action* random_bot_update(PlayerbotAI* ai) { return new RandomBotUpdateAction(ai); } + static Action* delay(PlayerbotAI* ai) { return new DelayAction(ai); } + }; + +}; diff --git a/src/modules/Bots/playerbot/strategy/actions/AddLootAction.cpp b/src/modules/Bots/playerbot/strategy/actions/AddLootAction.cpp new file mode 100644 index 000000000..0a4d14936 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AddLootAction.cpp @@ -0,0 +1,103 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AddLootAction.h" + +#include "../../LootObjectStack.h" +#include "../../PlayerbotAIConfig.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +using namespace ai; + +bool AddLootAction::Execute(Event event) +{ + ObjectGuid guid = event.getObject(); + if (!guid) + { + return false; + } + + return AI_VALUE(LootObjectStack*, "available loot")->Add(guid); +} + +bool AddAllLootAction::Execute(Event event) +{ + bool added = false; + + list gos = context->GetValue >("nearest game objects")->Get(); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + added |= AddLoot(*i); + } + + list corpses = context->GetValue >("nearest corpses")->Get(); + for (list::iterator i = corpses.begin(); i != corpses.end(); i++) + { + added |= AddLoot(*i); + } + + return added; +} + +bool AddLootAction::isUseful() +{ + return AI_VALUE(uint8, "bag space") < 80; +} + +bool AddAllLootAction::isUseful() +{ + return AI_VALUE(uint8, "bag space") < 80; +} + +bool AddAllLootAction::AddLoot(ObjectGuid guid) +{ + return AI_VALUE(LootObjectStack*, "available loot")->Add(guid); +} + +bool AddGatheringLootAction::AddLoot(ObjectGuid guid) +{ + LootObject loot(bot, guid); + + WorldObject *wo = loot.GetWorldObject(bot); + if (loot.IsEmpty() || !wo) + { + return false; + } + + if (!bot->IsWithinLOSInMap(wo)) + { + return false; + } + + if (loot.skillId == SKILL_NONE) + { + return false; + } + + if (!loot.IsLootPossible(bot)) + { + return false; + } + + if (bot->GetDistance2d(wo) > sPlayerbotAIConfig.tooCloseDistance) + { + list targets; + MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, sPlayerbotAIConfig.lootDistance); + MaNGOS::UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(wo, searcher, sPlayerbotAIConfig.spellDistance); + if (!targets.empty()) + { + ostringstream out; + out << "Kill that " << targets.front()->GetName() << " so I can loot freely"; + ai->TellMaster(out.str()); + return false; + } + } + + return AddAllLootAction::AddLoot(guid); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AddLootAction.h b/src/modules/Bots/playerbot/strategy/actions/AddLootAction.h new file mode 100644 index 000000000..402c50ad4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AddLootAction.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class AddLootAction : public Action { + public: + AddLootAction(PlayerbotAI* ai) : Action(ai, "add loot") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; + + class AddAllLootAction : public Action { + public: + AddAllLootAction(PlayerbotAI* ai, string name = "add all loot") : Action(ai, name) {} + virtual bool Execute(Event event); + virtual bool isUseful(); + + protected: + virtual bool AddLoot(ObjectGuid guid); + }; + + class AddGatheringLootAction : public AddAllLootAction { + public: + AddGatheringLootAction(PlayerbotAI* ai) : AddAllLootAction(ai, "add gathering loot") {} + + protected: + virtual bool AddLoot(ObjectGuid guid); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.cpp b/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.cpp new file mode 100644 index 000000000..0f64f68d8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AreaTriggerAction.h" +#include "../../PlayerbotAIConfig.h" + + +using namespace ai; + +bool ReachAreaTriggerAction::Execute(Event event) +{ + uint32 triggerId; + WorldPacket p(event.getPacket()); + p.rpos(0); + p >> triggerId; + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(triggerId); + if(!atEntry) + { + return false; + } + + AreaTrigger const* at = sObjectMgr.GetAreaTrigger(triggerId); + if (!at) + { + WorldPacket p1(CMSG_AREATRIGGER); + p1 << triggerId; + p1.rpos(0); + bot->GetSession()->HandleAreaTriggerOpcode(p1); + + return true; + } + + if (bot->GetMapId() != atEntry->mapid || bot->GetDistance(atEntry->x, atEntry->y, atEntry->z) > sPlayerbotAIConfig.sightDistance) + { + ai->TellMaster("I won't follow: too far away"); + return true; + } + + MotionMaster &mm = *bot->GetMotionMaster(); + mm.Clear(); + mm.MovePoint(atEntry->mapid, atEntry->x, atEntry->y, atEntry->z); + float distance = bot->GetDistance(atEntry->x, atEntry->y, atEntry->z); + float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay; + ai->TellMaster("Wait for me"); + ai->SetNextCheckDelay(delay); + context->GetValue("last area trigger")->Get().lastAreaTrigger = triggerId; + + return true; +} + + + +bool AreaTriggerAction::Execute(Event event) +{ + LastMovement& movement = context->GetValue("last area trigger")->Get(); + + uint32 triggerId = movement.lastAreaTrigger; + movement.lastAreaTrigger = 0; + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(triggerId); + if(!atEntry) + { + return false; + } + + AreaTrigger const* at = sObjectMgr.GetAreaTrigger(triggerId); + if (!at) + { + return true; + } + + WorldPacket p(CMSG_AREATRIGGER); + p << triggerId; + p.rpos(0); + bot->GetSession()->HandleAreaTriggerOpcode(p); + + ai->TellMaster("Hello"); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.h b/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.h new file mode 100644 index 000000000..de2b63aaa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AreaTriggerAction.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class ReachAreaTriggerAction : public MovementAction { + public: + ReachAreaTriggerAction(PlayerbotAI* ai) : MovementAction(ai, "reach area trigger") {} + + virtual bool Execute(Event event); + }; + + class AreaTriggerAction : public MovementAction { + public: + AreaTriggerAction(PlayerbotAI* ai) : MovementAction(ai, "area trigger") {} + + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/AttackAction.cpp b/src/modules/Bots/playerbot/strategy/actions/AttackAction.cpp new file mode 100644 index 000000000..5251efc76 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AttackAction.cpp @@ -0,0 +1,126 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AttackAction.h" +#include "MovementGenerator.h" +#include "CreatureAI.h" +#include "../../LootObjectStack.h" + +using namespace ai; + +bool AttackAction::Execute(Event event) +{ + Unit* target = GetTarget(); + + if (!target) + { + return false; + } + + return Attack(target); +} + +bool AttackMyTargetAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ObjectGuid guid = master->GetSelectionGuid(); + if (!guid) + { + if (verbose) ai->TellMaster("You have no target"); + { + return false; + } + } + + return Attack(ai->GetUnit(guid)); +} + +bool AttackAction::Attack(Unit* target) +{ + MotionMaster &mm = *bot->GetMotionMaster(); + if (mm.GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE || bot->IsTaxiFlying()) + { + if (verbose) ai->TellMaster("I cannot attack in flight"); + { + return false; + } + } + + if (!target) + { + if (verbose) ai->TellMaster("I have no target"); + { + return false; + } + } + + ostringstream msg; + msg << target->GetName(); + if (bot->IsFriendlyTo(target)) + { + msg << " is friendly to me"; + if (verbose) ai->TellMaster(msg.str()); + { + return false; + } + } + if (!bot->IsWithinLOSInMap(target)) + { + msg << " is not on my sight"; + if (verbose) ai->TellMaster(msg.str()); + { + return false; + } + } + if (target->IsDead()) + { + msg << " is dead"; + if (verbose) ai->TellMaster(msg.str()); + { + return false; + } + } + + if (bot->IsMounted()) + { + WorldPacket emptyPacket; + bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + } + + ObjectGuid guid = target->GetObjectGuid(); + bot->SetSelectionGuid(target->GetObjectGuid()); + + Unit* oldTarget = context->GetValue("current target")->Get(); + context->GetValue("old target")->Set(oldTarget); + + context->GetValue("current target")->Set(target); + context->GetValue("available loot")->Get()->Add(guid); + + Pet* pet = bot->GetPet(); + if (pet) + { + CreatureAI* creatureAI = ((Creature*)pet)->AI(); + if (creatureAI) + { + creatureAI->AttackStart(target); + } + } + + bot->Attack(target, true); + ai->ChangeEngine(BOT_STATE_COMBAT); + return true; +} + +bool AttackDuelOpponentAction::isUseful() +{ + return AI_VALUE(Unit*, "duel target"); +} + +bool AttackDuelOpponentAction::Execute(Event event) +{ + return Attack(AI_VALUE(Unit*, "duel target")); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/AttackAction.h b/src/modules/Bots/playerbot/strategy/actions/AttackAction.h new file mode 100644 index 000000000..e13eb4094 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/AttackAction.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class AttackAction : public MovementAction + { + public: + AttackAction(PlayerbotAI* ai, string name) : MovementAction(ai, name) {} + + public: + virtual bool Execute(Event event); + + protected: + bool Attack(Unit* target); + }; + + class AttackMyTargetAction : public AttackAction + { + public: + AttackMyTargetAction(PlayerbotAI* ai, string name = "attack my target") : AttackAction(ai, name) {} + + public: + virtual bool Execute(Event event); + }; + + class AttackDuelOpponentAction : public AttackAction + { + public: + AttackDuelOpponentAction(PlayerbotAI* ai, string name = "attack duel opponent") : AttackAction(ai, name) {} + + public: + virtual bool Execute(Event event); + virtual bool isUseful(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BankAction.cpp b/src/modules/Bots/playerbot/strategy/actions/BankAction.cpp new file mode 100644 index 000000000..814bf38c8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BankAction.cpp @@ -0,0 +1,184 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "BankAction.h" + +#include "../values/ItemCountValue.h" + +using namespace std; +using namespace ai; + +bool BankAction::Execute(Event event) +{ + string text = event.getParam(); + + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Unit* npc = ai->GetUnit(*i); + if (!npc || !npc->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_BANKER)) + { + continue; + } + + return Execute(text, npc); + } + + ai->TellMaster("Cannot find banker nearby"); + return false; +} + +bool BankAction::Execute(string text, Unit* bank) +{ + if (text.empty() || text == "?") + { + ListItems(); + return true; + } + + bool result = false; + if (text[0] == '-') + { + ItemIds found = chat->parseItems(text); + for (ItemIds::iterator i = found.begin(); i != found.end(); i++) + { + uint32 itemId = *i; + result &= Withdraw(itemId); + } + } + else + { + list found = parseItems(text); + if (found.empty()) + { + return false; + } + + for (list::iterator i = found.begin(); i != found.end(); i++) + { + Item* item = *i; + if (!item) + { + continue; + } + + result &= Deposit(item); + } + } + + return result; +} + +bool BankAction::Withdraw(const uint32 itemid) +{ + Item* pItem = FindItemInBank(itemid); + if (!pItem) + { + return false; + } + + ItemPosCountVec dest; + InventoryResult msg = bot->CanStoreItem(NULL_BAG, NULL_SLOT, dest, pItem, false); + if (msg != EQUIP_ERR_OK) + { + bot->SendEquipError(msg, pItem, NULL); + return false; + } + + bot->RemoveItem(pItem->GetBagSlot(), pItem->GetSlot(), true); + bot->StoreItem(dest, pItem, true); + + std::ostringstream out; + out << "got " << chat->formatItem(pItem->GetProto(), pItem->GetCount()) << " from bank"; + ai->TellMaster(out.str()); + return true; +} + +bool BankAction::Deposit(Item* pItem) +{ + std::ostringstream out; + + ItemPosCountVec dest; + InventoryResult msg = bot->CanBankItem(NULL_BAG, NULL_SLOT, dest, pItem, false); + if (msg != EQUIP_ERR_OK) + { + bot->SendEquipError(msg, pItem, NULL); + return false; + } + + bot->RemoveItem(pItem->GetBagSlot(), pItem->GetSlot(), true); + bot->BankItem(dest, pItem, true); + + out << "put " << chat->formatItem(pItem->GetProto(), pItem->GetCount()) << " to bank"; + ai->TellMaster(out.str()); + return true; +} + +void BankAction::ListItems() +{ + ai->TellMaster("=== Bank ==="); + + map items; + map soulbound; + for (int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + items[pItem->GetProto()->ItemId] += pItem->GetCount(); + soulbound[pItem->GetProto()->ItemId] = pItem->IsSoulBound(); + } + + for (int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + if (Item* pItem = pBag->GetItemByPos(j)) + { + items[pItem->GetProto()->ItemId] += pItem->GetCount(); + soulbound[pItem->GetProto()->ItemId] = pItem->IsSoulBound(); + } + + TellItems(items, soulbound); +} + +Item* BankAction::FindItemInBank(uint32 ItemId) +{ + for (uint8 slot = BANK_SLOT_ITEM_START; slot < BANK_SLOT_ITEM_END; slot++) + { + Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + { + continue; + } + + if (pItemProto->ItemId == ItemId) // have required item + { + return pItem; + } + } + } + + for (uint8 bag = BANK_SLOT_BAG_START; bag < BANK_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag *) bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + Item* const pItem = bot->GetItemByPos(bag, slot); + if (pItem) + { + const ItemPrototype* const pItemProto = pItem->GetProto(); + if (!pItemProto) + { + continue; + } + + if (pItemProto->ItemId == ItemId) + { + return pItem; + } + } + } + } + return NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BankAction.h b/src/modules/Bots/playerbot/strategy/actions/BankAction.h new file mode 100644 index 000000000..6b22095e5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BankAction.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class BankAction : public InventoryAction { + public: + BankAction(PlayerbotAI* ai) : InventoryAction(ai, "bank") {} + virtual bool Execute(Event event); + + private: + bool Execute(string text, Unit* bank); + void ListItems(); + bool Withdraw(const uint32 itemid); + bool Deposit(Item* pItem); + Item* FindItemInBank(uint32 ItemId); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BuffAction.cpp b/src/modules/Bots/playerbot/strategy/actions/BuffAction.cpp new file mode 100644 index 000000000..e86c1f506 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BuffAction.cpp @@ -0,0 +1,127 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "BuffAction.h" + +#include "../values/ItemCountValue.h" + +using namespace ai; + +class FindBuffVisitor : public IterateItemsVisitor { +public: + explicit FindBuffVisitor(Player* bot) : IterateItemsVisitor(), bot(bot) + { + } + + virtual bool Visit(Item* item) + { + if (bot->CanUseItem(item->GetProto()) != EQUIP_ERR_OK) + { + return true; + } + + const ItemPrototype* proto = item->GetProto(); + + if (proto->Class != ITEM_CLASS_CONSUMABLE) + { + return true; + } + + if (proto->SubClass != ITEM_SUBCLASS_ELIXIR && + proto->SubClass != ITEM_SUBCLASS_FLASK && + proto->SubClass != ITEM_SUBCLASS_SCROLL && + proto->SubClass != ITEM_SUBCLASS_FOOD && + proto->SubClass != ITEM_SUBCLASS_CONSUMABLE_OTHER && + proto->SubClass != ITEM_SUBCLASS_ITEM_ENHANCEMENT) + return true; + + for (int i=0; iSpells[i].SpellId; + if (!spellId) + { + continue; + } + + if (bot->HasAura(spellId)) + { + return true; + } + + Item* itemForSpell = *bot->GetPlayerbotAI()->GetAiObjectContext()->GetValue("item for spell", spellId); + if (itemForSpell && itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + return true; + } + + if (items.find(proto->SubClass) == items.end()) + { + items[proto->SubClass] = list(); + } + + items[proto->SubClass].push_back(item); + break; + } + + return true; + } + +public: + map > items; + +private: + Player* bot; +}; + +void BuffAction::TellHeader(uint32 subClass) +{ + switch (subClass) + { + case ITEM_SUBCLASS_ELIXIR: + ai->TellMaster("--- Elixir ---"); + return; + case ITEM_SUBCLASS_FLASK: + ai->TellMaster("--- Flask ---"); + return; + case ITEM_SUBCLASS_SCROLL: + ai->TellMaster("--- Scroll ---"); + return; + case ITEM_SUBCLASS_FOOD: + ai->TellMaster("--- Food ---"); + return; + case ITEM_SUBCLASS_ITEM_ENHANCEMENT: + ai->TellMaster("--- Enchant ---"); + return; + } +} + + +bool BuffAction::Execute(Event event) +{ + FindBuffVisitor visitor(bot); + IterateItems(&visitor); + + uint32 oldSubClass = -1; + for (map >::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i) + { + list items = i->second; + + uint32 subClass = i->first; + if (oldSubClass != subClass) + { + if (!items.empty()) + { + TellHeader(subClass); + } + oldSubClass = subClass; + } + for (list::iterator j = items.begin(); j != items.end(); ++j) + { + Item* item = *j; + ostringstream out; + out << chat->formatItem(item->GetProto(), item->GetCount()); + ai->TellMaster(out); + } + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BuffAction.h b/src/modules/Bots/playerbot/strategy/actions/BuffAction.h new file mode 100644 index 000000000..da8b6a625 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BuffAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class BuffAction : public InventoryAction { + public: + BuffAction(PlayerbotAI* ai) : InventoryAction(ai, "buff") {} + virtual bool Execute(Event event); + + private: + void TellHeader(uint32 subClass); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BuyAction.cpp b/src/modules/Bots/playerbot/strategy/actions/BuyAction.cpp new file mode 100644 index 000000000..7e715010b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BuyAction.cpp @@ -0,0 +1,60 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "BuyAction.h" +#include "../ItemVisitors.h" +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool BuyAction::Execute(Event event) +{ + string link = event.getParam(); + + ItemIds itemIds = chat->parseItems(link); + if (itemIds.empty()) + { + return false; + } + + Player* master = GetMaster(); + + if (!master) + { + return false; + } + + list vendors = ai->GetAiObjectContext()->GetValue >("nearest npcs")->Get(); + bool bought = false; + for (list::iterator i = vendors.begin(); i != vendors.end(); ++i) + { + ObjectGuid vendorguid = *i; + Creature *pCreature = bot->GetNPCIfCanInteractWith(vendorguid,UNIT_NPC_FLAG_VENDOR); + if (!pCreature) + { + continue; + } + + VendorItemData const* tItems = pCreature->GetVendorItems(); + if (!tItems) + { + continue; + } + + for (ItemIds::iterator i = itemIds.begin(); i != itemIds.end(); i++) + { + for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++) + { + const ItemPrototype* proto = sObjectMgr.GetItemPrototype(*i); + if (proto && tItems->GetItem(slot)->item == *i) + { + bot->BuyItemFromVendorSlot(vendorguid, 0, *i, 1, NULL_BAG, NULL_SLOT); + ostringstream out; out << "Buying " << ChatHelper::formatItem(proto); + ai->TellMaster(out.str()); + bought = true; + } + } + } + } + + return bought; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/BuyAction.h b/src/modules/Bots/playerbot/strategy/actions/BuyAction.h new file mode 100644 index 000000000..2e0822c22 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/BuyAction.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class BuyAction : public InventoryAction { + public: + BuyAction(PlayerbotAI* ai) : InventoryAction(ai, "buy") {} + virtual bool Execute(Event event); + + private: + bool TradeItem(FindItemVisitor *visitor, int8 slot); + bool TradeItem(const Item& item, int8 slot); + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.cpp b/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.cpp new file mode 100644 index 000000000..05d6b0e94 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.cpp @@ -0,0 +1,95 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CastCustomSpellAction.h" + +#include "../../PlayerbotAIConfig.h" +using namespace ai; + +bool CastCustomSpellAction::Execute(Event event) +{ + Unit* target = NULL; + + Player* master = GetMaster(); + if (master && master->GetSelectionGuid()) + { + target = ai->GetUnit(master->GetSelectionGuid()); + } + + if (!target) + { + target = bot; + } + + string text = event.getParam(); + int pos = text.find_last_of(" "); + int castCount = 1; + if (pos != string::npos) + { + castCount = atoi(text.substr(pos + 1).c_str()); + if (castCount > 0) + { + text = text.substr(0, pos); + } + } + + uint32 spell = AI_VALUE2(uint32, "spell id", text); + + ostringstream msg; + if (!spell) + { + msg << "Unknown spell " << text; + ai->TellMaster(msg.str()); + return false; + } + + SpellEntry const *pSpellInfo = sSpellStore.LookupEntry(spell); + if (!pSpellInfo) + { + msg << "Unknown spell " << text; + ai->TellMaster(msg.str()); + return false; + } + + if (target != bot && !bot->IsInFront(target, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT)) + { + bot->SetFacingTo(bot->GetAngle(target)); + ai->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown); + msg << "cast " << text; + ai->HandleCommand(CHAT_MSG_WHISPER, msg.str(), *master); + return true; + } + + + if (!ai->CanCastSpell(spell, target)) + { + msg << "Cannot cast " << ChatHelper::formatSpell(pSpellInfo) << " on " << target->GetName(); + ai->TellMaster(msg.str()); + return false; + } + + bool result = spell ? ai->CastSpell(spell, target) : ai->CastSpell(text, target); + if (result) + { + msg << "Casting " << ChatHelper::formatSpell(pSpellInfo); + if (target != bot) + { + msg << " on " << target->GetName(); + } + + if (castCount > 1) + { + ostringstream cmd; + cmd << "cast " << text << " " << (castCount - 1); + ai->HandleCommand(CHAT_MSG_WHISPER, cmd.str(), *master); + msg << "|cffffff00(x" << (castCount-1) << " left)|r"; + } + ai->TellMasterNoFacing(msg.str()); + } + else + { + msg << "Cast " << ChatHelper::formatSpell(pSpellInfo) << " on " << target->GetName() << " is failed"; + ai->TellMaster(msg.str()); + } + + return result; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.h b/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.h new file mode 100644 index 000000000..976a9e464 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CastCustomSpellAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class CastCustomSpellAction : public Action + { + public: + CastCustomSpellAction(PlayerbotAI* ai) : Action(ai, "cast custom spell") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.cpp new file mode 100644 index 000000000..d584a672a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.cpp @@ -0,0 +1,26 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ChangeChatAction.h" + + +using namespace ai; + + +bool ChangeChatAction::Execute(Event event) +{ + string text = event.getParam(); + ChatMsg parsed = chat->parseChat(text); + if (parsed == CHAT_MSG_SYSTEM) + { + ostringstream out; out << "Current chat is " << chat->formatChat(*context->GetValue("chat")); + ai->TellMaster(out); + } + else + { + context->GetValue("chat")->Set(parsed); + ostringstream out; out << "Chat set to " << chat->formatChat(parsed); + ai->TellMaster(out); + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.h b/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.h new file mode 100644 index 000000000..2d749c388 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeChatAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class ChangeChatAction : public Action { + public: + ChangeChatAction(PlayerbotAI* ai) : Action(ai, "chat") {} + virtual bool Execute(Event event); + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.cpp new file mode 100644 index 000000000..bcb0cbfc3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.cpp @@ -0,0 +1,38 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ChangeStrategyAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool ChangeCombatStrategyAction::Execute(Event event) +{ + string text = event.getParam(); + ai->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT); + return true; +} + +bool ChangeNonCombatStrategyAction::Execute(Event event) +{ + string text = event.getParam(); + + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(bot->GetObjectGuid()); + if (sPlayerbotAIConfig.IsInRandomAccountList(account) && ai->GetMaster() && ai->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER) + { + if (text.find("loot") != string::npos || text.find("gather") != string::npos) + { + ai->TellMaster("You can change any strategy except loot and gather"); + return false; + } + } + + ai->ChangeStrategy(text, BOT_STATE_NON_COMBAT); + return true; +} + +bool ChangeDeadStrategyAction::Execute(Event event) +{ + string text = event.getParam(); + ai->ChangeStrategy(text, BOT_STATE_DEAD); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.h b/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.h new file mode 100644 index 000000000..c1e53109c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeStrategyAction.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class ChangeCombatStrategyAction : public Action { + public: + ChangeCombatStrategyAction(PlayerbotAI* ai, string name = "co") : Action(ai, name) {} + + public: + virtual bool Execute(Event event); + }; + + class ChangeNonCombatStrategyAction : public Action { + public: + ChangeNonCombatStrategyAction(PlayerbotAI* ai) : Action(ai, "nc") {} + + public: + virtual bool Execute(Event event); + }; + + class ChangeDeadStrategyAction : public Action { + public: + ChangeDeadStrategyAction(PlayerbotAI* ai) : Action(ai, "dead") {} + + public: + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.cpp new file mode 100644 index 000000000..bddf747ea --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ChangeTalentsAction.h" + +using namespace ai; + +bool ChangeTalentsAction::Execute(Event event) +{ + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.h b/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.h new file mode 100644 index 000000000..01730c623 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChangeTalentsAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class ChangeTalentsAction : public Action { + public: + ChangeTalentsAction(PlayerbotAI* ai) : Action(ai, "talents") {} + + public: + virtual bool Execute(Event event); + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/ChatActionContext.h b/src/modules/Bots/playerbot/strategy/actions/ChatActionContext.h new file mode 100644 index 000000000..eab282de4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChatActionContext.h @@ -0,0 +1,206 @@ +#pragma once + +#include "ListQuestsActions.h" +#include "StatsAction.h" +#include "LeaveGroupAction.h" +#include "TellReputationAction.h" +#include "LogLevelAction.h" +#include "TellLosAction.h" +#include "DropQuestAction.h" +#include "QueryQuestAction.h" +#include "QueryItemUsageAction.h" +#include "LootStrategyAction.h" +#include "AddLootAction.h" +#include "ReleaseSpiritAction.h" +#include "TeleportAction.h" +#include "TaxiAction.h" +#include "RepairAllAction.h" +#include "UseItemAction.h" +#include "TellItemCountAction.h" +#include "RewardAction.h" +#include "BuyAction.h" +#include "SellAction.h" +#include "UnequipAction.h" +#include "EquipAction.h" +#include "TradeAction.h" +#include "ChangeTalentsAction.h" +#include "ListSpellsAction.h" +#include "ChangeStrategyAction.h" +#include "TrainerAction.h" +#include "ChangeChatAction.h" +#include "SetHomeAction.h" +#include "ResetAiAction.h" +#include "DestroyItemAction.h" +#include "BuffAction.h" +#include "AttackAction.h" +#include "HelpAction.h" +#include "GuildBankAction.h" +#include "ChatShortcutActions.h" +#include "GossipHelloAction.h" +#include "CastCustomSpellAction.h" +#include "InviteToGroupAction.h" +#include "TellCastFailedAction.h" +#include "RtiAction.h" +#include "ReviveFromCorpseAction.h" +#include "BankAction.h" +#include "PositionAction.h" +#include "TellTargetAction.h" +#include "UseMeetingStoneAction.h" +#include "WhoAction.h" +#include "SaveManaAction.h" +#include "../values/Formations.h" +#include "CustomStrategyEditAction.h" +#include "DebugAction.h" +#include "GoAction.h" +#include "MailAction.h" +#include "SendMailAction.h" +#include "ShareQuestAction.h" +#include "SkipSpellsListAction.h" +#include "CustomStrategyEditAction.h" + +namespace ai +{ + class ChatActionContext : public NamedObjectContext + { + public: + ChatActionContext() + { + creators["stats"] = &ChatActionContext::stats; + creators["quests"] = &ChatActionContext::quests; + creators["leave"] = &ChatActionContext::leave; + creators["reputation"] = &ChatActionContext::reputation; + creators["log"] = &ChatActionContext::log; + creators["los"] = &ChatActionContext::los; + creators["drop"] = &ChatActionContext::drop; + creators["share"] = &ChatActionContext::share; + creators["query quest"] = &ChatActionContext::query_quest; + creators["query item usage"] = &ChatActionContext::query_item_usage; + creators["ll"] = &ChatActionContext::ll; + creators["ss"] = &ChatActionContext::ss; + creators["add all loot"] = &ChatActionContext::add_all_loot; + creators["release"] = &ChatActionContext::release; + creators["teleport"] = &ChatActionContext::teleport; + creators["taxi"] = &ChatActionContext::taxi; + creators["repair"] = &ChatActionContext::repair; + creators["use"] = &ChatActionContext::use; + creators["item count"] = &ChatActionContext::item_count; + creators["equip"] = &ChatActionContext::equip; + creators["unequip"] = &ChatActionContext::unequip; + creators["sell"] = &ChatActionContext::sell; + creators["buy"] = &ChatActionContext::buy; + creators["reward"] = &ChatActionContext::reward; + creators["trade"] = &ChatActionContext::trade; + creators["talents"] = &ChatActionContext::talents; + creators["spells"] = &ChatActionContext::spells; + creators["co"] = &ChatActionContext::co; + creators["nc"] = &ChatActionContext::nc; + creators["dead"] = &ChatActionContext::dead; + creators["trainer"] = &ChatActionContext::trainer; + creators["attack my target"] = &ChatActionContext::attack_my_target; + creators["chat"] = &ChatActionContext::chat; + creators["home"] = &ChatActionContext::home; + creators["destroy"] = &ChatActionContext::destroy; + creators["reset ai"] = &ChatActionContext::reset_ai; + creators["buff"] = &ChatActionContext::buff; + creators["help"] = &ChatActionContext::help; + creators["gb"] = &ChatActionContext::gb; + creators["bank"] = &ChatActionContext::bank; + creators["follow chat shortcut"] = &ChatActionContext::follow_chat_shortcut; + creators["stay chat shortcut"] = &ChatActionContext::stay_chat_shortcut; + creators["flee chat shortcut"] = &ChatActionContext::flee_chat_shortcut; + creators["runaway chat shortcut"] = &ChatActionContext::runaway_chat_shortcut; + creators["grind chat shortcut"] = &ChatActionContext::grind_chat_shortcut; + creators["tank attack chat shortcut"] = &ChatActionContext::tank_attack_chat_shortcut; + creators["gossip hello"] = &ChatActionContext::gossip_hello; + creators["cast custom spell"] = &ChatActionContext::cast_custom_spell; + creators["invite"] = &ChatActionContext::invite; + creators["spell"] = &ChatActionContext::spell; + creators["rti"] = &ChatActionContext::rti; + creators["spirit healer"] = &ChatActionContext::spirit_healer; + creators["position"] = &ChatActionContext::position; + creators["tell target"] = &ChatActionContext::tell_target; + creators["summon"] = &ChatActionContext::summon; + creators["who"] = &ChatActionContext::who; + creators["save mana"] = &ChatActionContext::save_mana; + creators["max dps chat shortcut"] = &ChatActionContext::max_dps_chat_shortcut; + creators["tell attackers"] = &ChatActionContext::tell_attackers; + creators["formation"] = &ChatActionContext::formation; + creators["sendmail"] = &ChatActionContext::sendmail; + creators["mail"] = &ChatActionContext::mail; + creators["go"] = &ChatActionContext::go; + creators["debug"] = &ChatActionContext::debug; + creators["cs"] = &ChatActionContext::cs; + } + + private: + static Action* cs(PlayerbotAI* ai) { return new CustomStrategyEditAction(ai); } + static Action* debug(PlayerbotAI* ai) { return new DebugAction(ai); } + static Action* mail(PlayerbotAI* ai) { return new MailAction(ai); } + static Action* go(PlayerbotAI* ai) { return new GoAction(ai); } + static Action* sendmail(PlayerbotAI* ai) { return new SendMailAction(ai); } + static Action* formation(PlayerbotAI* ai) { return new SetFormationAction(ai); } + static Action* tell_attackers(PlayerbotAI* ai) { return new TellAttackersAction(ai); } + static Action* max_dps_chat_shortcut(PlayerbotAI* ai) { return new MaxDpsChatShortcutAction(ai); } + static Action* save_mana(PlayerbotAI* ai) { return new SaveManaAction(ai); } + static Action* who(PlayerbotAI* ai) { return new WhoAction(ai); } + static Action* summon(PlayerbotAI* ai) { return new SummonAction(ai); } + static Action* tell_target(PlayerbotAI* ai) { return new TellTargetAction(ai); } + static Action* position(PlayerbotAI* ai) { return new PositionAction(ai); } + static Action* spirit_healer(PlayerbotAI* ai) { return new SpiritHealerAction(ai); } + static Action* rti(PlayerbotAI* ai) { return new RtiAction(ai); } + static Action* invite(PlayerbotAI* ai) { return new InviteToGroupAction(ai); } + static Action* spell(PlayerbotAI* ai) { return new TellSpellAction(ai); } + static Action* cast_custom_spell(PlayerbotAI* ai) { return new CastCustomSpellAction(ai); } + static Action* tank_attack_chat_shortcut(PlayerbotAI* ai) { return new TankAttackChatShortcutAction(ai); } + static Action* grind_chat_shortcut(PlayerbotAI* ai) { return new GrindChatShortcutAction(ai); } + static Action* flee_chat_shortcut(PlayerbotAI* ai) { return new FleeChatShortcutAction(ai); } + static Action* runaway_chat_shortcut(PlayerbotAI* ai) { return new GoawayChatShortcutAction(ai); } + static Action* stay_chat_shortcut(PlayerbotAI* ai) { return new StayChatShortcutAction(ai); } + static Action* follow_chat_shortcut(PlayerbotAI* ai) { return new FollowChatShortcutAction(ai); } + static Action* gb(PlayerbotAI* ai) { return new GuildBankAction(ai); } + static Action* bank(PlayerbotAI* ai) { return new BankAction(ai); } + static Action* help(PlayerbotAI* ai) { return new HelpAction(ai); } + static Action* buff(PlayerbotAI* ai) { return new BuffAction(ai); } + static Action* destroy(PlayerbotAI* ai) { return new DestroyItemAction(ai); } + static Action* home(PlayerbotAI* ai) { return new SetHomeAction(ai); } + static Action* chat(PlayerbotAI* ai) { return new ChangeChatAction(ai); } + static Action* attack_my_target(PlayerbotAI* ai) { return new AttackMyTargetAction(ai); } + static Action* trainer(PlayerbotAI* ai) { return new TrainerAction(ai); } + static Action* co(PlayerbotAI* ai) { return new ChangeCombatStrategyAction(ai); } + static Action* nc(PlayerbotAI* ai) { return new ChangeNonCombatStrategyAction(ai); } + static Action* dead(PlayerbotAI* ai) { return new ChangeDeadStrategyAction(ai); } + static Action* spells(PlayerbotAI* ai) { return new ListSpellsAction(ai); } + static Action* talents(PlayerbotAI* ai) { return new ChangeTalentsAction(ai); } + + static Action* equip(PlayerbotAI* ai) { return new EquipAction(ai); } + static Action* unequip(PlayerbotAI* ai) { return new UnequipAction(ai); } + static Action* sell(PlayerbotAI* ai) { return new SellAction(ai); } + static Action* buy(PlayerbotAI* ai) { return new BuyAction(ai); } + static Action* reward(PlayerbotAI* ai) { return new RewardAction(ai); } + static Action* trade(PlayerbotAI* ai) { return new TradeAction(ai); } + + static Action* item_count(PlayerbotAI* ai) { return new TellItemCountAction(ai); } + static Action* use(PlayerbotAI* ai) { return new UseItemAction(ai); } + static Action* repair(PlayerbotAI* ai) { return new RepairAllAction(ai); } + static Action* taxi(PlayerbotAI* ai) { return new TaxiAction(ai); } + static Action* teleport(PlayerbotAI* ai) { return new TeleportAction(ai); } + static Action* release(PlayerbotAI* ai) { return new ReleaseSpiritAction(ai); } + static Action* query_item_usage(PlayerbotAI* ai) { return new QueryItemUsageAction(ai); } + static Action* query_quest(PlayerbotAI* ai) { return new QueryQuestAction(ai); } + static Action* drop(PlayerbotAI* ai) { return new DropQuestAction(ai); } + static Action* share(PlayerbotAI* ai) { return new ShareQuestAction(ai); } + static Action* stats(PlayerbotAI* ai) { return new StatsAction(ai); } + static Action* quests(PlayerbotAI* ai) { return new ListQuestsAction(ai); } + static Action* leave(PlayerbotAI* ai) { return new LeaveGroupAction(ai); } + static Action* reputation(PlayerbotAI* ai) { return new TellReputationAction(ai); } + static Action* log(PlayerbotAI* ai) { return new LogLevelAction(ai); } + static Action* los(PlayerbotAI* ai) { return new TellLosAction(ai); } + static Action* ll(PlayerbotAI* ai) { return new LootStrategyAction(ai); } + static Action* ss(PlayerbotAI* ai) { return new SkipSpellsListAction(ai); } + static Action* add_all_loot(PlayerbotAI* ai) { return new AddAllLootAction(ai); } + static Action* reset_ai(PlayerbotAI* ai) { return new ResetAiAction(ai); } + static Action* gossip_hello(PlayerbotAI* ai) { return new GossipHelloAction(ai); } + }; + + +}; diff --git a/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.cpp b/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.cpp new file mode 100644 index 000000000..f697d88a0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.cpp @@ -0,0 +1,124 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ChatShortcutActions.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool FollowChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("+follow,-passive", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("-follow,-passive", BOT_STATE_COMBAT); + if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance) + { + ai->TellMaster("I will not follow you - too far away"); + return true; + } + ai->TellMaster("Following"); + return true; +} + +bool StayChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("+stay,-passive", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("-follow,-passive", BOT_STATE_COMBAT); + ai->TellMaster("Staying"); + return true; +} + +bool FleeChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("+follow,+passive", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("+follow,+passive", BOT_STATE_COMBAT); + if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance) + { + ai->TellMaster("I will not flee with you - too far away"); + return true; + } + ai->TellMaster("Fleeing"); + return true; +} + +bool GoawayChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("+runaway", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("+runaway", BOT_STATE_COMBAT); + ai->TellMaster("Running away"); + return true; +} + +bool GrindChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("+grind,-passive", BOT_STATE_NON_COMBAT); + ai->TellMaster("Grinding"); + return true; +} + +bool TankAttackChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + if (!ai->IsTank(bot)) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("-passive", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("-passive", BOT_STATE_COMBAT); + ai->TellMaster("Attacking"); + return true; +} + +bool MaxDpsChatShortcutAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai->Reset(); + ai->ChangeStrategy("-threat,-conserve mana,-cast time,+dps debuff", BOT_STATE_COMBAT); + ai->TellMaster("Max DPS"); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.h b/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.h new file mode 100644 index 000000000..de9a86a52 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChatShortcutActions.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class FollowChatShortcutAction : public Action + { + public: + FollowChatShortcutAction(PlayerbotAI* ai) : Action(ai, "follow chat shortcut") {} + virtual bool Execute(Event event); + }; + + class StayChatShortcutAction : public Action + { + public: + StayChatShortcutAction(PlayerbotAI* ai) : Action(ai, "stay chat shortcut") {} + virtual bool Execute(Event event); + }; + + class FleeChatShortcutAction : public Action + { + public: + FleeChatShortcutAction(PlayerbotAI* ai) : Action(ai, "flee chat shortcut") {} + virtual bool Execute(Event event); + }; + + class GoawayChatShortcutAction : public Action + { + public: + GoawayChatShortcutAction(PlayerbotAI* ai) : Action(ai, "runaway chat shortcut") {} + virtual bool Execute(Event event); + }; + + class GrindChatShortcutAction : public Action + { + public: + GrindChatShortcutAction(PlayerbotAI* ai) : Action(ai, "grind chat shortcut") {} + virtual bool Execute(Event event); + }; + + class TankAttackChatShortcutAction : public Action + { + public: + TankAttackChatShortcutAction(PlayerbotAI* ai) : Action(ai, "tank attack chat shortcut") {} + virtual bool Execute(Event event); + }; + + class MaxDpsChatShortcutAction : public Action + { + public: + MaxDpsChatShortcutAction(PlayerbotAI* ai) : Action(ai, "max dps chat shortcut") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.cpp b/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.cpp new file mode 100644 index 000000000..77dcd875d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.cpp @@ -0,0 +1,95 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CheckMailAction.h" +#include "Mail.h" + +#include "../../GuildTaskMgr.h" +#include "../../PlayerbotAIConfig.h" +using namespace ai; + +bool CheckMailAction::Execute(Event event) +{ + WorldPacket p; + bot->GetSession()->HandleQueryNextMailTime(p); + + if (ai->GetMaster() || !bot->GetMailSize()) + { + return false; + } + + list ids; + for (PlayerMails::iterator i = bot->GetMailBegin(); i != bot->GetMailEnd(); ++i) + { + Mail* mail = *i; + + if (!mail || mail->state == MAIL_STATE_DELETED) + { + continue; + } + + Player* owner = sObjectMgr.GetPlayer((ObjectGuid)(uint64)mail->sender); + if (!owner) + { + continue; + } + + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(owner->GetObjectGuid()); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + continue; + } + + ProcessMail(mail, owner); + ids.push_back(mail->messageID); + mail->state = MAIL_STATE_DELETED; + } + + for (list::iterator i = ids.begin(); i != ids.end(); ++i) + { + uint32 id = *i; + bot->SendMailResult(id, MAIL_DELETED, MAIL_OK); + CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", id); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", id); + bot->RemoveMail(id); + } + + return true; +} + + +void CheckMailAction::ProcessMail(Mail* mail, Player* owner) +{ + if (!mail->HasItems()) + { + return; + } + + for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i) + { + Item *item = bot->GetMItem(i->item_guid); + if (!item) + { + continue; + } + + if (!sGuildTaskMgr.CheckItemTask(i->item_template, item->GetCount(), owner, bot, true)) + { + ostringstream body; + body << "Hello, " << owner->GetName() << ",\n"; + body << "\n"; + body << "Here are the item(s) you've sent me by mistake"; + body << "\n"; + body << "Thanks,\n"; + body << bot->GetName() << "\n"; + + MailDraft draft("Item(s) you've sent me", body.str()); + draft.AddItem(item); + bot->RemoveMItem(i->item_guid); + draft.SendMailTo(MailReceiver(owner), MailSender(bot)); + return; + } + + bot->RemoveMItem(i->item_guid); + item->DestroyForPlayer(bot); + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.h b/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.h new file mode 100644 index 000000000..9da88a978 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CheckMailAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class CheckMailAction : public Action + { + public: + CheckMailAction(PlayerbotAI* ai) : Action(ai, "check mail") {} + virtual bool Execute(Event event); + + private: + void ProcessMail(Mail* mail, Player* owner); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.cpp b/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.cpp new file mode 100644 index 000000000..513142952 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.cpp @@ -0,0 +1,84 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CheckMountStateAction.h" + +using namespace ai; + +uint64 extractGuid(WorldPacket& packet); + +bool CheckMountStateAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!bot->GetGroup() || !master) + { + return false; + } + + if (bot->IsTaxiFlying()) + { + return false; + } + + if (master->IsMounted() && !bot->IsMounted()) + { + return Mount(); + } + else if (!master->IsMounted() && bot->IsMounted()) + { + WorldPacket emptyPacket; + bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); + return true; + } + return false; +} + +bool CheckMountStateAction::Mount() +{ + Player* master = GetMaster(); + ai->RemoveShapeshift(); + + Unit::AuraList const& auras = master->GetAurasByType(SPELL_AURA_MOUNTED); + if (auras.empty()) return false; + + const SpellEntry* masterSpell = auras.front()->GetSpellProto(); + int32 masterSpeed = max(masterSpell->EffectBasePoints[1], masterSpell->EffectBasePoints[2]); + + map > spells; + for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + { + continue; + } + + const SpellEntry* spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo || spellInfo->EffectApplyAuraName[0] != SPELL_AURA_MOUNTED) + { + continue; + } + + int32 effect = max(spellInfo->EffectBasePoints[1], spellInfo->EffectBasePoints[2]); + if (effect < masterSpeed) + { + continue; + } + + spells[effect].push_back(spellId); + } + + for (map >::iterator i = spells.begin(); i != spells.end(); ++i) + { + vector& ids = i->second; + int index = urand(0, ids.size() - 1); + if (index >= ids.size()) + { + continue; + } + + ai->CastSpell(ids[index], bot); + return true; + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.h b/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.h new file mode 100644 index 000000000..c5b087e9b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CheckMountStateAction.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class CheckMountStateAction : public Action { + public: + CheckMountStateAction(PlayerbotAI* ai) : Action(ai, "check mount state") {} + + virtual bool Execute(Event event); + + private: + bool Mount(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ChooseTargetActions.h b/src/modules/Bots/playerbot/strategy/actions/ChooseTargetActions.h new file mode 100644 index 000000000..acc1511fd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ChooseTargetActions.h @@ -0,0 +1,82 @@ +#pragma once + +#include "../Action.h" +#include "AttackAction.h" + +namespace ai +{ + class DpsAssistAction : public AttackAction + { + public: + DpsAssistAction(PlayerbotAI* ai) : AttackAction(ai, "dps assist") {} + + virtual string GetTargetName() { return "dps target"; } + }; + + class TankAssistAction : public AttackAction + { + public: + TankAssistAction(PlayerbotAI* ai) : AttackAction(ai, "tank assist") {} + virtual string GetTargetName() { return "tank target"; } + }; + + class AttackAnythingAction : public AttackAction + { + public: + AttackAnythingAction(PlayerbotAI* ai) : AttackAction(ai, "attack anything") {} + virtual string GetTargetName() { return "grind target"; } + virtual bool Execute(Event event) + { + return AttackAction::Execute(event); + } + virtual bool isUseful() { + return GetTarget() && + (!AI_VALUE(list, "nearest non bot players").empty() && + AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.mediumHealth && + (!AI_VALUE2(uint8, "mana", "self target") || AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumMana) + ) || AI_VALUE2(bool, "combat", "self target") + ; + } + virtual bool isPossible() + { + return AttackAction::isPossible() && GetTarget(); + } + }; + + class AttackLeastHpTargetAction : public AttackAction + { + public: + AttackLeastHpTargetAction(PlayerbotAI* ai) : AttackAction(ai, "attack least hp target") {} + virtual string GetTargetName() { return "least hp target"; } + }; + + class AttackEnemyPlayerAction : public AttackAction + { + public: + AttackEnemyPlayerAction(PlayerbotAI* ai) : AttackAction(ai, "attack enemy player") {} + virtual string GetTargetName() { return "enemy player target"; } + }; + + class AttackRtiTargetAction : public AttackAction + { + public: + AttackRtiTargetAction(PlayerbotAI* ai) : AttackAction(ai, "attack rti target") {} + virtual string GetTargetName() { return "rti target"; } + }; + + class DropTargetAction : public Action + { + public: + DropTargetAction(PlayerbotAI* ai) : Action(ai, "drop target") {} + + virtual bool Execute(Event event) + { + context->GetValue("current target")->Set(NULL); + bot->SetSelectionGuid(ObjectGuid()); + ai->ChangeEngine(BOT_STATE_NON_COMBAT); + ai->InterruptSpell(); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.cpp b/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.cpp new file mode 100644 index 000000000..195ac050d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.cpp @@ -0,0 +1,123 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CustomStrategyEditAction.h" +#include "../CustomStrategy.h" + + +using namespace ai; + + +bool CustomStrategyEditAction::Execute(Event event) +{ + string text = event.getParam(); + int pos = text.find(" "); + if (pos == string::npos) return PrintHelp(); + + string name = text.substr(0, pos); + + text = text.substr(pos + 1); + + pos = text.find(" "); + if (pos == string::npos) + { + pos = text.size(); + } + string idx = text.substr(0, pos); + text = pos >= text.size() ? "" : text.substr(pos + 1); + + return idx == "?" ? Print(name) : Edit(name, atoi(idx.c_str()), text); +} + +bool CustomStrategyEditAction::PrintHelp() +{ + ai->TellMaster("=== Custom strategies ==="); + + uint32 owner = (uint32)ai->GetBot()->GetGUIDLow(); + QueryResult* results = CharacterDatabase.PQuery("SELECT distinct name FROM ai_playerbot_custom_strategy WHERE owner = '%u'", + owner); + if (results) + { + do + { + Field* fields = results->Fetch(); + string name = fields[0].GetString(); + ai->TellMaster(name); + } while (results->NextRow()); + + delete results; + } + ai->TellMaster("Usage: cs "); + return false; +} + +bool CustomStrategyEditAction::Print(string name) +{ + ostringstream out; out << "=== " << name << " ==="; + ai->TellMaster(out.str()); + + uint32 owner = (uint32)ai->GetBot()->GetGUIDLow(); + QueryResult* results = CharacterDatabase.PQuery("SELECT idx, action_line FROM ai_playerbot_custom_strategy WHERE name = '%s' and owner = '%u' order by idx", + name.c_str(), owner); + if (results) + { + do + { + Field* fields = results->Fetch(); + uint32 idx = fields[0].GetUInt32(); + string action = fields[1].GetString(); + + PrintActionLine(idx, action); + } while (results->NextRow()); + + delete results; + } + return true; +} + +bool CustomStrategyEditAction::Edit(string name, uint32 idx, string command) +{ + uint32 owner = (uint32)ai->GetBot()->GetGUIDLow(); + QueryResult* results = CharacterDatabase.PQuery("SELECT action_line FROM ai_playerbot_custom_strategy WHERE name = '%s' and owner = '%u' and idx = '%u'", + name.c_str(), owner, idx); + if (results) + { + if (command.empty()) + { + CharacterDatabase.DirectPExecute("DELETE FROM ai_playerbot_custom_strategy WHERE name = '%s' and owner = '%u' and idx = '%u'", + name.c_str(), owner, idx); + } + else + { + CharacterDatabase.DirectPExecute("UPDATE ai_playerbot_custom_strategy SET action_line = '%s' WHERE name = '%s' and owner = '%u' and idx = '%u'", + command.c_str(), name.c_str(), owner, idx); + } + delete results; + } + else + { + CharacterDatabase.DirectPExecute("INSERT INTO ai_playerbot_custom_strategy (name, owner, idx, action_line) VALUES ('%s', '%u', '%u', '%s')", + name.c_str(), owner, idx, command.c_str()); + } + + PrintActionLine(idx, command); + + ostringstream ss; ss << "custom::" << name; + Strategy* strategy = ai->GetAiObjectContext()->GetStrategy(ss.str()); + if (strategy) + { + CustomStrategy *cs = dynamic_cast(strategy); + if (cs) + { + cs->Reset(); + ai->ReInitCurrentEngine(); + } + } + return true; +} + +bool CustomStrategyEditAction::PrintActionLine(uint32 idx, string& command) +{ + ostringstream out; out << "#" << idx << " " << command; + ai->TellMaster(out.str()); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.h b/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.h new file mode 100644 index 000000000..b53c65d94 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/CustomStrategyEditAction.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class CustomStrategyEditAction : public Action { + public: + CustomStrategyEditAction(PlayerbotAI* ai) : Action(ai, "cs") {} + virtual bool Execute(Event event); + + private: + bool PrintHelp(); + bool PrintActionLine(uint32 idx, string& command); + bool Print(string name); + bool Edit(string name, uint32 idx, string command); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DebugAction.cpp b/src/modules/Bots/playerbot/strategy/actions/DebugAction.cpp new file mode 100644 index 000000000..48202c39e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DebugAction.cpp @@ -0,0 +1,20 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DebugAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool DebugAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + string text = event.getParam(); + string response = ai->HandleRemoteCommand(text); + ai->TellMaster(response); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DebugAction.h b/src/modules/Bots/playerbot/strategy/actions/DebugAction.h new file mode 100644 index 000000000..b3006c5cd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DebugAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class DebugAction : public Action + { + public: + DebugAction(PlayerbotAI* ai) : Action(ai, "Debug") {} + + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DelayAction.h b/src/modules/Bots/playerbot/strategy/actions/DelayAction.h new file mode 100644 index 000000000..48f51f209 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DelayAction.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../../PlayerbotAIConfig.h" +#include "../../RandomPlayerbotMgr.h" +#include "../Action.h" + +namespace ai +{ + class DelayAction : public Action + { + public: + DelayAction(PlayerbotAI* ai) : Action(ai, "delay") + {} + + virtual bool Execute(Event event) + { + if (!sRandomPlayerbotMgr.IsRandomBot(bot) || bot->GetGroup() || ai->GetMaster()) + { + return false; + } + + if (bot->IsInCombat()) + { + return true; + } + + ai->SetNextCheckDelay(sPlayerbotAIConfig.passiveDelay + sPlayerbotAIConfig.globalCoolDown); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.cpp b/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.cpp new file mode 100644 index 000000000..d6f756a1c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.cpp @@ -0,0 +1,34 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DestroyItemAction.h" + +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool DestroyItemAction::Execute(Event event) +{ + string text = event.getParam(); + ItemIds ids = chat->parseItems(text); + + for (ItemIds::iterator i =ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + DestroyItem(&visitor); + } + + return true; +} + +void DestroyItemAction::DestroyItem(FindItemVisitor* visitor) +{ + IterateItems(visitor); + list items = visitor->GetResult(); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* item = *i; + bot->DestroyItem(item->GetBagSlot(),item->GetSlot(), true); + ostringstream out; out << chat->formatItem(item->GetProto()) << " destroyed"; + ai->TellMaster(out); + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.h b/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.h new file mode 100644 index 000000000..ad1ab05b2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DestroyItemAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class DestroyItemAction : public InventoryAction { + public: + DestroyItemAction(PlayerbotAI* ai) : InventoryAction(ai, "destroy") {} + virtual bool Execute(Event event); + + private: + void DestroyItem(FindItemVisitor* visitor); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.cpp b/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.cpp new file mode 100644 index 000000000..10e327ec6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.cpp @@ -0,0 +1,47 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DropQuestAction.h" + + +using namespace ai; + +bool DropQuestAction::Execute(Event event) +{ + string link = event.getParam(); + if (!GetMaster()) + { + return false; + } + + PlayerbotChatHandler handler(GetMaster()); + uint32 entry = handler.extractQuestId(link); + if (!entry) + { + return false; + } + + Quest const* quest = sObjectMgr.GetQuestTemplate(entry); + if (!quest) + { + return false; + } + + // remove all quest entries for 'entry' from quest log + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 logQuest = bot->GetQuestSlotQuestId(slot); + if (logQuest == entry) + { + bot->SetQuestSlot(slot, 0); + + // we ignore unequippable quest items in this case, its' still be equipped + bot->TakeQuestSourceItem(logQuest, false); + } + } + + bot->SetQuestStatus(entry, QUEST_STATUS_NONE); + bot->getQuestStatusMap()[entry].m_rewarded = false; + + ai->TellMaster("Quest removed"); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.h b/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.h new file mode 100644 index 000000000..af5d4ca67 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/DropQuestAction.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class DropQuestAction : public Action { + public: + DropQuestAction(PlayerbotAI* ai) : Action(ai, "drop quest") {} + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/EmoteAction.cpp b/src/modules/Bots/playerbot/strategy/actions/EmoteAction.cpp new file mode 100644 index 000000000..2aa573773 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/EmoteAction.cpp @@ -0,0 +1,286 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "EmoteAction.h" + +#include "../../PlayerbotAIConfig.h" +using namespace ai; + +map EmoteAction::emotes; +map EmoteAction::textEmotes; + +bool EmoteAction::Execute(Event event) +{ + if (emotes.empty()) + { + InitEmotes(); + } + + uint32 emote = 0; + + ai->GetAiObjectContext()->GetValue("last emote", qualifier)->Set(time(0) + urand(1, 60)); + + string param = event.getParam(); + if (param.empty()) param = qualifier; + + if (!param.empty() && textEmotes.find(param) != textEmotes.end()) + { + return ai->PlaySound(textEmotes[param]); + } + else if (param.empty() || emotes.find(param) == emotes.end()) + { + int index = rand() % emotes.size(); + for (map::iterator i = emotes.begin(); i != emotes.end() && index; ++i, --index) + { + emote = i->second; + } + } + else + { + emote = emotes[param]; + } + + Player* master = GetMaster(); + if (master) + { + ObjectGuid masterSelection = master->GetSelectionGuid(); + if (masterSelection) + { + ObjectGuid oldSelection = bot->GetSelectionGuid(); + bot->SetSelectionGuid(masterSelection); + bot->HandleEmoteCommand(emote); + if (oldSelection) + { + bot->SetSelectionGuid(oldSelection); + } + return true; + } + } + + bot->HandleEmoteCommand(emote); + return true; +} + +void EmoteAction::InitEmotes() +{ + emotes["dance"] = EMOTE_ONESHOT_DANCE; + emotes["drown"] = EMOTE_ONESHOT_DROWN; + emotes["land"] = EMOTE_ONESHOT_LAND; + emotes["liftoff"] = EMOTE_ONESHOT_LIFTOFF; + emotes["loot"] = EMOTE_ONESHOT_LOOT; + emotes["no"] = EMOTE_ONESHOT_NO; + emotes["roar"] = EMOTE_STATE_ROAR; + emotes["salute"] = EMOTE_ONESHOT_SALUTE; + emotes["stomp"] = EMOTE_ONESHOT_STOMP; + emotes["train"] = EMOTE_ONESHOT_TRAIN; + emotes["yes"] = EMOTE_ONESHOT_YES; + emotes["applaud"] = EMOTE_ONESHOT_APPLAUD; + emotes["beg"] = EMOTE_ONESHOT_BEG; + emotes["bow"] = EMOTE_ONESHOT_BOW; + emotes["cheer"] = EMOTE_ONESHOT_CHEER; + emotes["chicken"] = EMOTE_ONESHOT_CHICKEN; + emotes["cry"] = EMOTE_ONESHOT_CRY; + emotes["dance"] = EMOTE_STATE_DANCE; + emotes["eat"] = EMOTE_ONESHOT_EAT; + emotes["exclamation"] = EMOTE_ONESHOT_EXCLAMATION; + emotes["flex"] = EMOTE_ONESHOT_FLEX; + emotes["kick"] = EMOTE_ONESHOT_KICK; + emotes["kiss"] = EMOTE_ONESHOT_KISS; + emotes["kneel"] = EMOTE_ONESHOT_KNEEL; + emotes["laugh"] = EMOTE_ONESHOT_LAUGH; + emotes["point"] = EMOTE_ONESHOT_POINT; + emotes["question"] = EMOTE_ONESHOT_QUESTION; + emotes["ready1h"] = EMOTE_ONESHOT_READY1H; + emotes["roar"] = EMOTE_ONESHOT_ROAR; + emotes["rude"] = EMOTE_ONESHOT_RUDE; + emotes["shout"] = EMOTE_ONESHOT_SHOUT; + emotes["shy"] = EMOTE_ONESHOT_SHY; + emotes["sleep"] = EMOTE_STATE_SLEEP; + emotes["talk"] = EMOTE_ONESHOT_TALK; + emotes["wave"] = EMOTE_ONESHOT_WAVE; + emotes["wound"] = EMOTE_ONESHOT_WOUND; + + textEmotes["agree"] = TEXTEMOTE_AGREE; + textEmotes["amaze"] = TEXTEMOTE_AMAZE; + textEmotes["angry"] = TEXTEMOTE_ANGRY; + textEmotes["apologize"] = TEXTEMOTE_APOLOGIZE; + textEmotes["applaud"] = TEXTEMOTE_APPLAUD; + textEmotes["bashful"] = TEXTEMOTE_BASHFUL; + textEmotes["beckon"] = TEXTEMOTE_BECKON; + textEmotes["beg"] = TEXTEMOTE_BEG; + textEmotes["bite"] = TEXTEMOTE_BITE; + textEmotes["bleed"] = TEXTEMOTE_BLEED; + textEmotes["blink"] = TEXTEMOTE_BLINK; + textEmotes["blush"] = TEXTEMOTE_BLUSH; + textEmotes["bonk"] = TEXTEMOTE_BONK; + textEmotes["bored"] = TEXTEMOTE_BORED; + textEmotes["bounce"] = TEXTEMOTE_BOUNCE; + textEmotes["brb"] = TEXTEMOTE_BRB; + textEmotes["bow"] = TEXTEMOTE_BOW; + textEmotes["burp"] = TEXTEMOTE_BURP; + textEmotes["bye"] = TEXTEMOTE_BYE; + textEmotes["cackle"] = TEXTEMOTE_CACKLE; + textEmotes["cheer"] = TEXTEMOTE_CHEER; + textEmotes["chicken"] = TEXTEMOTE_CHICKEN; + textEmotes["chuckle"] = TEXTEMOTE_CHUCKLE; + textEmotes["clap"] = TEXTEMOTE_CLAP; + textEmotes["confused"] = TEXTEMOTE_CONFUSED; + textEmotes["congratulate"] = TEXTEMOTE_CONGRATULATE; + textEmotes["cough"] = TEXTEMOTE_COUGH; + textEmotes["cower"] = TEXTEMOTE_COWER; + textEmotes["crack"] = TEXTEMOTE_CRACK; + textEmotes["cringe"] = TEXTEMOTE_CRINGE; + textEmotes["cry"] = TEXTEMOTE_CRY; + textEmotes["curious"] = TEXTEMOTE_CURIOUS; + textEmotes["curtsey"] = TEXTEMOTE_CURTSEY; + textEmotes["dance"] = TEXTEMOTE_DANCE; + textEmotes["drink"] = TEXTEMOTE_DRINK; + textEmotes["drool"] = TEXTEMOTE_DROOL; + textEmotes["eat"] = TEXTEMOTE_EAT; + textEmotes["eye"] = TEXTEMOTE_EYE; + textEmotes["fart"] = TEXTEMOTE_FART; + textEmotes["fidget"] = TEXTEMOTE_FIDGET; + textEmotes["flex"] = TEXTEMOTE_FLEX; + textEmotes["frown"] = TEXTEMOTE_FROWN; + textEmotes["gasp"] = TEXTEMOTE_GASP; + textEmotes["gaze"] = TEXTEMOTE_GAZE; + textEmotes["giggle"] = TEXTEMOTE_GIGGLE; + textEmotes["glare"] = TEXTEMOTE_GLARE; + textEmotes["gloat"] = TEXTEMOTE_GLOAT; + textEmotes["greet"] = TEXTEMOTE_GREET; + textEmotes["grin"] = TEXTEMOTE_GRIN; + textEmotes["groan"] = TEXTEMOTE_GROAN; + textEmotes["grovel"] = TEXTEMOTE_GROVEL; + textEmotes["guffaw"] = TEXTEMOTE_GUFFAW; + textEmotes["hail"] = TEXTEMOTE_HAIL; + textEmotes["happy"] = TEXTEMOTE_HAPPY; + textEmotes["hello"] = TEXTEMOTE_HELLO; + textEmotes["hug"] = TEXTEMOTE_HUG; + textEmotes["hungry"] = TEXTEMOTE_HUNGRY; + textEmotes["kiss"] = TEXTEMOTE_KISS; + textEmotes["kneel"] = TEXTEMOTE_KNEEL; + textEmotes["laugh"] = TEXTEMOTE_LAUGH; + textEmotes["laydown"] = TEXTEMOTE_LAYDOWN; + textEmotes["message"] = TEXTEMOTE_MESSAGE; + textEmotes["moan"] = TEXTEMOTE_MOAN; + textEmotes["moon"] = TEXTEMOTE_MOON; + textEmotes["mourn"] = TEXTEMOTE_MOURN; + textEmotes["no"] = TEXTEMOTE_NO; + textEmotes["nod"] = TEXTEMOTE_NOD; + textEmotes["nosepick"] = TEXTEMOTE_NOSEPICK; + textEmotes["panic"] = TEXTEMOTE_PANIC; + textEmotes["peer"] = TEXTEMOTE_PEER; + textEmotes["plead"] = TEXTEMOTE_PLEAD; + textEmotes["point"] = TEXTEMOTE_POINT; + textEmotes["poke"] = TEXTEMOTE_POKE; + textEmotes["pray"] = TEXTEMOTE_PRAY; + textEmotes["roar"] = TEXTEMOTE_ROAR; + textEmotes["rofl"] = TEXTEMOTE_ROFL; + textEmotes["rude"] = TEXTEMOTE_RUDE; + textEmotes["salute"] = TEXTEMOTE_SALUTE; + textEmotes["scratch"] = TEXTEMOTE_SCRATCH; + textEmotes["sexy"] = TEXTEMOTE_SEXY; + textEmotes["shake"] = TEXTEMOTE_SHAKE; + textEmotes["shout"] = TEXTEMOTE_SHOUT; + textEmotes["shrug"] = TEXTEMOTE_SHRUG; + textEmotes["shy"] = TEXTEMOTE_SHY; + textEmotes["sigh"] = TEXTEMOTE_SIGH; + textEmotes["sit"] = TEXTEMOTE_SIT; + textEmotes["sleep"] = TEXTEMOTE_SLEEP; + textEmotes["snarl"] = TEXTEMOTE_SNARL; + textEmotes["spit"] = TEXTEMOTE_SPIT; + textEmotes["stare"] = TEXTEMOTE_STARE; + textEmotes["surprised"] = TEXTEMOTE_SURPRISED; + textEmotes["surrender"] = TEXTEMOTE_SURRENDER; + textEmotes["talk"] = TEXTEMOTE_TALK; + textEmotes["talkex"] = TEXTEMOTE_TALKEX; + textEmotes["talkq"] = TEXTEMOTE_TALKQ; + textEmotes["tap"] = TEXTEMOTE_TAP; + textEmotes["thank"] = TEXTEMOTE_THANK; + textEmotes["threaten"] = TEXTEMOTE_THREATEN; + textEmotes["tired"] = TEXTEMOTE_TIRED; + textEmotes["victory"] = TEXTEMOTE_VICTORY; + textEmotes["wave"] = TEXTEMOTE_WAVE; + textEmotes["welcome"] = TEXTEMOTE_WELCOME; + textEmotes["whine"] = TEXTEMOTE_WHINE; + textEmotes["whistle"] = TEXTEMOTE_WHISTLE; + textEmotes["work"] = TEXTEMOTE_WORK; + textEmotes["yawn"] = TEXTEMOTE_YAWN; + textEmotes["boggle"] = TEXTEMOTE_BOGGLE; + textEmotes["calm"] = TEXTEMOTE_CALM; + textEmotes["cold"] = TEXTEMOTE_COLD; + textEmotes["comfort"] = TEXTEMOTE_COMFORT; + textEmotes["cuddle"] = TEXTEMOTE_CUDDLE; + textEmotes["duck"] = TEXTEMOTE_DUCK; + textEmotes["insult"] = TEXTEMOTE_INSULT; + textEmotes["introduce"] = TEXTEMOTE_INTRODUCE; + textEmotes["jk"] = TEXTEMOTE_JK; + textEmotes["lick"] = TEXTEMOTE_LICK; + textEmotes["listen"] = TEXTEMOTE_LISTEN; + textEmotes["lost"] = TEXTEMOTE_LOST; + textEmotes["mock"] = TEXTEMOTE_MOCK; + textEmotes["ponder"] = TEXTEMOTE_PONDER; + textEmotes["pounce"] = TEXTEMOTE_POUNCE; + textEmotes["praise"] = TEXTEMOTE_PRAISE; + textEmotes["purr"] = TEXTEMOTE_PURR; + textEmotes["puzzle"] = TEXTEMOTE_PUZZLE; + textEmotes["raise"] = TEXTEMOTE_RAISE; + textEmotes["ready"] = TEXTEMOTE_READY; + textEmotes["shimmy"] = TEXTEMOTE_SHIMMY; + textEmotes["shiver"] = TEXTEMOTE_SHIVER; + textEmotes["shoo"] = TEXTEMOTE_SHOO; + textEmotes["slap"] = TEXTEMOTE_SLAP; + textEmotes["smirk"] = TEXTEMOTE_SMIRK; + textEmotes["sniff"] = TEXTEMOTE_SNIFF; + textEmotes["snub"] = TEXTEMOTE_SNUB; + textEmotes["soothe"] = TEXTEMOTE_SOOTHE; + textEmotes["stink"] = TEXTEMOTE_STINK; + textEmotes["taunt"] = TEXTEMOTE_TAUNT; + textEmotes["tease"] = TEXTEMOTE_TEASE; + textEmotes["thirsty"] = TEXTEMOTE_THIRSTY; + textEmotes["veto"] = TEXTEMOTE_VETO; + textEmotes["snicker"] = TEXTEMOTE_SNICKER; + textEmotes["stand"] = TEXTEMOTE_STAND; + textEmotes["tickle"] = TEXTEMOTE_TICKLE; + textEmotes["violin"] = TEXTEMOTE_VIOLIN; + textEmotes["smile"] = TEXTEMOTE_SMILE; + textEmotes["rasp"] = TEXTEMOTE_RASP; + textEmotes["pity"] = TEXTEMOTE_PITY; + textEmotes["growl"] = TEXTEMOTE_GROWL; + textEmotes["bark"] = TEXTEMOTE_BARK; + textEmotes["scared"] = TEXTEMOTE_SCARED; + textEmotes["flop"] = TEXTEMOTE_FLOP; + textEmotes["love"] = TEXTEMOTE_LOVE; + textEmotes["moo"] = TEXTEMOTE_MOO; + textEmotes["commend"] = TEXTEMOTE_COMMEND; + textEmotes["openfire"] = TEXTEMOTE_OPENFIRE; + textEmotes["flirt"] = TEXTEMOTE_FLIRT; + textEmotes["joke"] = TEXTEMOTE_JOKE; + textEmotes["wink"] = TEXTEMOTE_WINK; + textEmotes["pat"] = TEXTEMOTE_PAT; + textEmotes["serious"] = TEXTEMOTE_SERIOUS; + textEmotes["goodluck"] = TEXTEMOTE_GOODLUCK; + textEmotes["blame"] = TEXTEMOTE_BLAME; + textEmotes["blank"] = TEXTEMOTE_BLANK; + textEmotes["brandish"] = TEXTEMOTE_BRANDISH; + textEmotes["breath"] = TEXTEMOTE_BREATH; + textEmotes["disagree"] = TEXTEMOTE_DISAGREE; + textEmotes["doubt"] = TEXTEMOTE_DOUBT; + textEmotes["embarrass"] = TEXTEMOTE_EMBARRASS; + textEmotes["encourage"] = TEXTEMOTE_ENCOURAGE; + textEmotes["enemy"] = TEXTEMOTE_ENEMY; + textEmotes["eyebrow"] = TEXTEMOTE_EYEBROW; + textEmotes["toast"] = TEXTEMOTE_TOAST; + textEmotes["oom"] = 323; + textEmotes["follow"] = 324; + textEmotes["wait"] = 325; + textEmotes["healme"] = 326; + textEmotes["openfire"] = 327; + textEmotes["helpme"] = 303; +} + + +bool EmoteAction::isUseful() +{ + time_t lastEmote = AI_VALUE2(time_t, "last emote", qualifier); + return (time(0) - lastEmote) >= sPlayerbotAIConfig.repeatDelay / 1000; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/EmoteAction.h b/src/modules/Bots/playerbot/strategy/actions/EmoteAction.h new file mode 100644 index 000000000..43f34564a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/EmoteAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class EmoteAction : public Action, public Qualified + { + public: + EmoteAction(PlayerbotAI* ai) : Action(ai, "emote"), Qualified() {} + virtual bool Execute(Event event); + bool isUseful(); + + private: + void InitEmotes(); + static map emotes; + static map textEmotes; + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/EquipAction.cpp b/src/modules/Bots/playerbot/strategy/actions/EquipAction.cpp new file mode 100644 index 000000000..1af5b0a24 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/EquipAction.cpp @@ -0,0 +1,53 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "EquipAction.h" + +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool EquipAction::Execute(Event event) +{ + string text = event.getParam(); + ItemIds ids = chat->parseItems(text); + EquipItems(ids); + return true; +} + +void EquipAction::EquipItems(ItemIds ids) +{ + for (ItemIds::iterator i =ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + EquipItem(&visitor); + } +} + +void EquipAction::EquipItem(FindItemVisitor* visitor) +{ + IterateItems(visitor); + list items = visitor->GetResult(); + if (!items.empty()) EquipItem(**items.begin()); +} + + +void EquipAction::EquipItem(Item& item) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + uint32 itemId = item.GetProto()->ItemId; + + if (item.GetProto()->InventoryType == INVTYPE_AMMO) + { + bot->SetAmmo(itemId); + } + else + { + WorldPacket* const packet = new WorldPacket(CMSG_AUTOEQUIP_ITEM, 2); + *packet << bagIndex << slot; + bot->GetSession()->QueuePacket(packet); + } + + ostringstream out; out << "equipping " << chat->formatItem(item.GetProto()); + ai->TellMaster(out); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/EquipAction.h b/src/modules/Bots/playerbot/strategy/actions/EquipAction.h new file mode 100644 index 000000000..6c9b59ceb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/EquipAction.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class EquipAction : public InventoryAction { + public: + EquipAction(PlayerbotAI* ai, string name = "equip") : InventoryAction(ai, name) {} + virtual bool Execute(Event event); + + protected: + void EquipItems(ItemIds ids); + + private: + void EquipItem(FindItemVisitor* visitor); + void EquipItem(Item& item); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/FollowActions.cpp b/src/modules/Bots/playerbot/strategy/actions/FollowActions.cpp new file mode 100644 index 000000000..3e9480b3a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/FollowActions.cpp @@ -0,0 +1,60 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "FollowActions.h" +#include "../../PlayerbotAIConfig.h" +#include "../values/Formations.h" + +using namespace ai; + + +bool FollowAction::Execute(Event event) +{ + Formation* formation = AI_VALUE(Formation*, "formation"); + string target = formation->GetTargetName(); + bool moved = false; + if (!target.empty()) + { + moved = Follow(AI_VALUE(Unit*, target)); + } + else + { + WorldLocation loc = formation->GetLocation(); + if (Formation::IsNullLocation(loc) || loc.mapid == -1) + { + return false; + } + + moved = MoveTo(loc.mapid, loc.coord_x, loc.coord_y, loc.coord_z); + } + + if (moved) ai->SetNextCheckDelay(sPlayerbotAIConfig.reactDelay); + { + return moved; + } +} + +bool FollowAction::isUseful() +{ + Formation* formation = AI_VALUE(Formation*, "formation"); + float distance = 0; + string target = formation->GetTargetName(); + + if (!target.empty()) + { + distance = AI_VALUE2(float, "distance", target); + } + else + { + WorldLocation loc = formation->GetLocation(); + if (Formation::IsNullLocation(loc) || bot->GetMapId() != loc.mapid) + { + return false; + } + + distance = bot->GetDistance2d(loc.coord_x, loc.coord_y); + } + + return distance > formation->GetMaxDistance() && + !AI_VALUE(bool, "can loot"); +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/FollowActions.h b/src/modules/Bots/playerbot/strategy/actions/FollowActions.h new file mode 100644 index 000000000..e15e6fac2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/FollowActions.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class FollowAction : public MovementAction { + public: + FollowAction(PlayerbotAI* ai) : MovementAction(ai, "follow") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GenericActions.cpp b/src/modules/Bots/playerbot/strategy/actions/GenericActions.cpp new file mode 100644 index 000000000..66a575556 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GenericActions.cpp @@ -0,0 +1,5 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericActions.h" + +using namespace ai; diff --git a/src/modules/Bots/playerbot/strategy/actions/GenericActions.h b/src/modules/Bots/playerbot/strategy/actions/GenericActions.h new file mode 100644 index 000000000..d72817528 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GenericActions.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Action.h" +#include "GenericSpellActions.h" +#include "ReachTargetActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" + +namespace ai +{ + class MeleeAction : public AttackAction + { + public: + MeleeAction(PlayerbotAI* ai) : AttackAction(ai, "melee") {} + + virtual string GetTargetName() { return "current target"; } + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.cpp b/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.cpp new file mode 100644 index 000000000..81848456d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.cpp @@ -0,0 +1,57 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericActions.h" + +using namespace ai; + +bool CastSpellAction::Execute(Event event) +{ + return ai->CastSpell(spell, GetTarget()); +} + +bool CastSpellAction::isPossible() +{ + return ai->CanCastSpell(spell, GetTarget()); +} + +bool CastSpellAction::isUseful() +{ + return GetTarget() && AI_VALUE2(bool, "spell cast useful", spell); +} + +bool CastAuraSpellAction::isUseful() +{ + return CastSpellAction::isUseful() && !ai->HasAura(spell, GetTarget()); +} + +bool CastEnchantItemAction::isPossible() +{ + if (!CastSpellAction::isPossible()) + { + return false; + } + + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + return spellId && AI_VALUE2(Item*, "item for spell", spellId); +} + +bool CastHealingSpellAction::isUseful() +{ + return CastAuraSpellAction::isUseful() && AI_VALUE2(uint8, "health", GetTargetName()) < (100 - estAmount); +} + +bool CastAoeHealSpellAction::isUseful() +{ + return CastSpellAction::isUseful() && AI_VALUE2(uint8, "aoe heal", "medium") > 0; +} + + +Value* CurePartyMemberAction::GetTargetValue() +{ + return context->GetValue("party member to dispel", dispelType); +} + +Value* BuffOnPartyAction::GetTargetValue() +{ + return context->GetValue("party member without aura", spell); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.h b/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.h new file mode 100644 index 000000000..ce2febdb4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GenericSpellActions.h @@ -0,0 +1,288 @@ +#pragma once + +#include "../Action.h" +#include "../../PlayerbotAIConfig.h" + +#define BEGIN_SPELL_ACTION(clazz, name) \ +class clazz : public CastSpellAction \ + { \ + public: \ + clazz(PlayerbotAI* ai) : CastSpellAction(ai, name) {} \ + + +#define END_SPELL_ACTION() \ + }; + +#define BEGIN_DEBUFF_ACTION(clazz, name) \ +class clazz : public CastDebuffSpellAction \ + { \ + public: \ + clazz(PlayerbotAI* ai) : CastDebuffSpellAction(ai, name) {} \ + +#define BEGIN_RANGED_SPELL_ACTION(clazz, name) \ +class clazz : public CastSpellAction \ + { \ + public: \ + clazz(PlayerbotAI* ai) : CastSpellAction(ai, name) {} \ + +#define BEGIN_MELEE_SPELL_ACTION(clazz, name) \ +class clazz : public CastMeleeSpellAction \ + { \ + public: \ + clazz(PlayerbotAI* ai) : CastMeleeSpellAction(ai, name) {} \ + + +#define END_RANGED_SPELL_ACTION() \ + }; + + +#define BEGIN_BUFF_ON_PARTY_ACTION(clazz, name) \ +class clazz : public BuffOnPartyAction \ + { \ + public: \ + clazz(PlayerbotAI* ai) : BuffOnPartyAction(ai, name) {} + +namespace ai +{ + class CastSpellAction : public Action + { + public: + CastSpellAction(PlayerbotAI* ai, string spell) : Action(ai, spell), + range(sPlayerbotAIConfig.spellDistance) + { + this->spell = spell; + } + + virtual string GetTargetName() { return "current target"; }; + virtual bool Execute(Event event); + virtual bool isPossible(); + virtual bool isUseful(); + virtual ActionThreatType getThreatType() { return ACTION_THREAT_SINGLE; } + + virtual NextAction** getPrerequisites() + { + if (range > sPlayerbotAIConfig.spellDistance) + { + return NULL; + } + else if (range > ATTACK_DISTANCE) + { + return NextAction::merge( NextAction::array(0, new NextAction("reach spell"), NULL), Action::getPrerequisites()); + } + else + { + return NextAction::merge( NextAction::array(0, new NextAction("reach melee"), NULL), Action::getPrerequisites()); + } + } + + protected: + string spell; + float range; + }; + + //--------------------------------------------------------------------------------------------------------------------- + class CastAuraSpellAction : public CastSpellAction + { + public: + CastAuraSpellAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) {} + + virtual bool isUseful(); + }; + + //--------------------------------------------------------------------------------------------------------------------- + class CastMeleeSpellAction : public CastSpellAction + { + public: + CastMeleeSpellAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) { + range = ATTACK_DISTANCE; + } + }; + + //--------------------------------------------------------------------------------------------------------------------- + class CastDebuffSpellAction : public CastAuraSpellAction + { + public: + CastDebuffSpellAction(PlayerbotAI* ai, string spell) : CastAuraSpellAction(ai, spell) {} + }; + + class CastDebuffSpellOnAttackerAction : public CastAuraSpellAction + { + public: + CastDebuffSpellOnAttackerAction(PlayerbotAI* ai, string spell) : CastAuraSpellAction(ai, spell) {} + Value* GetTargetValue() + { + return context->GetValue("attacker without aura", spell); + } + virtual string getName() { return spell + " on attacker"; } + virtual ActionThreatType getThreatType() { return ACTION_THREAT_AOE; } + }; + + class CastBuffSpellAction : public CastAuraSpellAction + { + public: + CastBuffSpellAction(PlayerbotAI* ai, string spell) : CastAuraSpellAction(ai, spell) + { + range = sPlayerbotAIConfig.spellDistance; + } + + virtual string GetTargetName() { return "self target"; } + }; + + class CastEnchantItemAction : public CastSpellAction + { + public: + CastEnchantItemAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) + { + range = sPlayerbotAIConfig.spellDistance; + } + + virtual bool isPossible(); + virtual string GetTargetName() { return "self target"; } + }; + + //--------------------------------------------------------------------------------------------------------------------- + + class CastHealingSpellAction : public CastAuraSpellAction + { + public: + CastHealingSpellAction(PlayerbotAI* ai, string spell, uint8 estAmount = 15.0f) : CastAuraSpellAction(ai, spell) + { + this->estAmount = estAmount; + range = sPlayerbotAIConfig.spellDistance; + } + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful(); + virtual ActionThreatType getThreatType() { return ACTION_THREAT_AOE; } + + protected: + uint8 estAmount; + }; + + class CastAoeHealSpellAction : public CastHealingSpellAction + { + public: + CastAoeHealSpellAction(PlayerbotAI* ai, string spell, uint8 estAmount = 15.0f) : CastHealingSpellAction(ai, spell, estAmount) {} + virtual string GetTargetName() { return "party member to heal"; } + virtual bool isUseful(); + }; + + class CastCureSpellAction : public CastSpellAction + { + public: + CastCureSpellAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) + { + range = sPlayerbotAIConfig.spellDistance; + } + + virtual string GetTargetName() { return "self target"; } + }; + + class PartyMemberActionNameSupport { + public: + PartyMemberActionNameSupport(string spell) + { + name = string(spell) + " on party"; + } + + virtual string getName() { return name; } + + private: + string name; + }; + + class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberActionNameSupport + { + public: + HealPartyMemberAction(PlayerbotAI* ai, string spell, uint8 estAmount = 15.0f) : + CastHealingSpellAction(ai, spell, estAmount), PartyMemberActionNameSupport(spell) {} + + virtual string GetTargetName() { return "party member to heal"; } + virtual string getName() { return PartyMemberActionNameSupport::getName(); } + }; + + class ResurrectPartyMemberAction : public CastSpellAction + { + public: + ResurrectPartyMemberAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) {} + + virtual string GetTargetName() { return "party member to resurrect"; } + }; + //--------------------------------------------------------------------------------------------------------------------- + + class CurePartyMemberAction : public CastSpellAction, public PartyMemberActionNameSupport + { + public: + CurePartyMemberAction(PlayerbotAI* ai, string spell, uint32 dispelType) : + CastSpellAction(ai, spell), PartyMemberActionNameSupport(spell) + { + this->dispelType = dispelType; + } + + virtual Value* GetTargetValue(); + virtual string getName() { return PartyMemberActionNameSupport::getName(); } + + protected: + uint32 dispelType; + }; + + //--------------------------------------------------------------------------------------------------------------------- + + class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport + { + public: + BuffOnPartyAction(PlayerbotAI* ai, string spell) : + CastBuffSpellAction(ai, spell), PartyMemberActionNameSupport(spell) {} + public: + virtual Value* GetTargetValue(); + virtual string getName() { return PartyMemberActionNameSupport::getName(); } + }; + + //--------------------------------------------------------------------------------------------------------------------- + + class CastShootAction : public CastSpellAction + { + public: + CastShootAction(PlayerbotAI* ai) : CastSpellAction(ai, "shoot") {} + virtual ActionThreatType getThreatType() { return ACTION_THREAT_NONE; } + }; + + class CastLifeBloodAction : public CastHealingSpellAction + { + public: + CastLifeBloodAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "lifeblood") {} + }; + + class CastGiftOfTheNaaruAction : public CastHealingSpellAction + { + public: + CastGiftOfTheNaaruAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "gift of the naaru") {} + }; + + class CastArcaneTorrentAction : public CastBuffSpellAction + { + public: + CastArcaneTorrentAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "arcane torrent") {} + }; + + class CastSpellOnEnemyHealerAction : public CastSpellAction + { + public: + CastSpellOnEnemyHealerAction(PlayerbotAI* ai, string spell) : CastSpellAction(ai, spell) {} + Value* GetTargetValue() + { + return context->GetValue("enemy healer target", spell); + } + virtual string getName() { return spell + " on enemy healer"; } + }; + + class CastSnareSpellAction : public CastDebuffSpellAction + { + public: + CastSnareSpellAction(PlayerbotAI* ai, string spell) : CastDebuffSpellAction(ai, spell) {} + Value* GetTargetValue() + { + return context->GetValue("snare target", spell); + } + virtual string getName() { return spell + " on snare target"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GoAction.cpp b/src/modules/Bots/playerbot/strategy/actions/GoAction.cpp new file mode 100644 index 000000000..f2d547e1b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GoAction.cpp @@ -0,0 +1,125 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GoAction.h" +#include "../../PlayerbotAIConfig.h" +#include "../values/Formations.h" +#include "../values/PositionValue.h" + +using namespace ai; + +vector split(const string &s, char delim); +char *strstri(const char *haystack, const char *needle); + +bool GoAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + string param = event.getParam(); + if (param == "?") + { + float x = bot->GetPositionX(); + float y = bot->GetPositionY(); + Map2ZoneCoordinates(x, y, bot->GetZoneId()); + ostringstream out; + out << "I am at " << x << "," << y; + ai->TellMaster(out.str()); + return true; + } + + list gos = ChatHelper::parseGameobjects(param); + if (!gos.empty()) + { + for (list::iterator i = gos.begin(); i != gos.end(); ++i) + { + GameObject* go = ai->GetGameObject(*i); + if (go && go->isSpawned()) + { + if (bot->GetDistance2d(go) > sPlayerbotAIConfig.reactDistance) + { + ai->TellMaster("It is too far away"); + return false; + } + + ostringstream out; out << "Moving to " << ChatHelper::formatGameobject(go); + ai->TellMasterNoFacing(out.str()); + return MoveNear(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ() + 0.5f, sPlayerbotAIConfig.followDistance); + } + } + return false; + } + + list units; + list npcs = AI_VALUE(list, "nearest npcs"); + units.insert(units.end(), npcs.begin(), npcs.end()); + list players = AI_VALUE(list, "nearest friendly players"); + units.insert(units.end(), players.begin(), players.end()); + for (list::iterator i = units.begin(); i != units.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (unit && strstri(unit->GetName(), param.c_str())) + { + ostringstream out; out << "Moving to " << unit->GetName(); + ai->TellMasterNoFacing(out.str()); + return MoveNear(bot->GetMapId(), unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ() + 0.5f, sPlayerbotAIConfig.followDistance); + } + } + + if (param.find(",") != string::npos) + { + vector coords = split(param, ','); + float x = atof(coords[0].c_str()); + float y = atof(coords[1].c_str()); + Zone2MapCoordinates(x, y, bot->GetZoneId()); + + Map* map = bot->GetMap(); + float z = bot->GetPositionZ(); + bot->UpdateGroundPositionZ(x, y, z); + + if (bot->GetDistance2d(x, y) > sPlayerbotAIConfig.reactDistance) + { + ai->TellMaster("It is too far away"); + return false; + } + + const TerrainInfo* terrain = map->GetTerrain(); + if (terrain->IsUnderWater(x, y, z) || terrain->IsInWater(x, y, z)) + { + ai->TellMaster("It is under water"); + return false; + } + + float ground = map->GetHeight(bot->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + ai->TellMaster("I can't go there"); + return false; + } + + float x1 = x, y1 = y; + Map2ZoneCoordinates(x1, y1, bot->GetZoneId()); + ostringstream out; out << "Moving to " << x1 << "," << y1; + ai->TellMasterNoFacing(out.str()); + return MoveNear(bot->GetMapId(), x, y, z + 0.5f, sPlayerbotAIConfig.followDistance); + } + + ai::Position pos = context->GetValue("position")->Get()[param]; + if (pos.isSet()) + { + if (bot->GetDistance2d(pos.x, pos.y) > sPlayerbotAIConfig.reactDistance) + { + ai->TellMaster("It is too far away"); + return false; + } + + ostringstream out; out << "Moving to position " << param; + ai->TellMasterNoFacing(out.str()); + return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z + 0.5f, sPlayerbotAIConfig.followDistance); + } + + ai->TellMaster("Whisper 'go x,y', 'go [game object]', 'go unit' or 'go position' and I will go there"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GoAction.h b/src/modules/Bots/playerbot/strategy/actions/GoAction.h new file mode 100644 index 000000000..54c28d8d3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GoAction.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class GoAction : public MovementAction { + public: + GoAction(PlayerbotAI* ai) : MovementAction(ai, "Go") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.cpp b/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.cpp new file mode 100644 index 000000000..4eeeb3a99 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.cpp @@ -0,0 +1,140 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GossipHelloAction.h" + + +using namespace ai; + +bool GossipHelloAction::Execute(Event event) +{ + ObjectGuid guid; + + WorldPacket &p = event.getPacket(); + if (p.empty()) + { + Player* master = GetMaster(); + if (master) + { + guid = master->GetSelectionGuid(); + } + } + else + { + p.rpos(0); + p >> guid; + } + + if (!guid) + { + return false; + } + + Creature *pCreature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE); + if (!pCreature) + { + DEBUG_LOG("[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO %s not found or you can't interact with him.", guid.GetString().c_str()); + return false; + } + + GossipMenuItemsMapBounds pMenuItemBounds = sObjectMgr.GetGossipMenuItemsMapBounds(pCreature->GetCreatureInfo()->GossipMenuId); + if (pMenuItemBounds.first == pMenuItemBounds.second) + { + return false; + } + + string text = event.getParam(); + if (text.empty()) + { + WorldPacket p1; + p1 << guid; + bot->GetSession()->HandleGossipHelloOpcode(p1); + bot->SetFacingToObject(pCreature); + + ostringstream out; out << "--- " << pCreature->GetName() << " ---"; + ai->TellMasterNoFacing(out.str()); + + TellGossipMenus(); + } + else if (!bot->PlayerTalkClass) + { + ai->TellMaster("I need to talk first"); + return false; + } + else + { + int menuToSelect = atoi(text.c_str()); + if (menuToSelect > 0) menuToSelect--; + { + ProcessGossip(menuToSelect); + } + } + + bot->TalkedToCreature(pCreature->GetEntry(), pCreature->GetObjectGuid()); + return true; +} + +void GossipHelloAction::TellGossipText(uint32 textId) +{ + if (!textId) + { + return; + } + + GossipText const* text = sObjectMgr.GetGossipText(textId); + if (text) + { + for (int i = 0; i < MAX_GOSSIP_TEXT_OPTIONS; i++) + { + string text0 = text->Options[i].Text_0; + if (!text0.empty()) + { + ai->TellMasterNoFacing(text0); + } + string text1 = text->Options[i].Text_1; + if (!text1.empty()) + { + ai->TellMasterNoFacing(text1); + } + } + } +} + +void GossipHelloAction::TellGossipMenus() +{ + if (!bot->PlayerTalkClass) + { + return; + } + + Creature *pCreature = bot->GetNPCIfCanInteractWith(GetMaster()->GetSelectionGuid(), UNIT_NPC_FLAG_NONE); + GossipMenu& menu = bot->PlayerTalkClass->GetGossipMenu(); + if (pCreature) + { + uint32 textId = bot->GetGossipTextId(menu.GetMenuId(), pCreature); + TellGossipText(textId); + } + + for (int i = 0; i < menu.MenuItemCount(); i++) + { + GossipMenuItem const& item = menu.GetItem(i); + ostringstream out; out << "[" << (i+1) << "] " << item.m_gMessage; + ai->TellMasterNoFacing(out.str()); + } +} + +bool GossipHelloAction::ProcessGossip(int menuToSelect) +{ + GossipMenu& menu = bot->PlayerTalkClass->GetGossipMenu(); + if (menuToSelect != -1 && menuToSelect >= menu.MenuItemCount()) + { + ai->TellMaster("Unknown gossip option"); + return false; + } + GossipMenuItem const& item = menu.GetItem(menuToSelect); + WorldPacket p; + std::string code; + p << GetMaster()->GetSelectionGuid() << menuToSelect << code; + bot->GetSession()->HandleGossipSelectOptionOpcode(p); + + TellGossipMenus(); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.h b/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.h new file mode 100644 index 000000000..f2b79e849 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GossipHelloAction.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class GossipHelloAction : public Action { + public: + GossipHelloAction(PlayerbotAI* ai) : Action(ai, "gossip hello") {} + virtual bool Execute(Event event); + + private: + void TellGossipMenus(); + bool ProcessGossip(int menuToSelect); + void TellGossipText(uint32 textId); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GreetAction.cpp b/src/modules/Bots/playerbot/strategy/actions/GreetAction.cpp new file mode 100644 index 000000000..9c61689c6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GreetAction.cpp @@ -0,0 +1,42 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GreetAction.h" + +#include "../../PlayerbotAIConfig.h" +using namespace ai; + +GreetAction::GreetAction(PlayerbotAI* ai) : Action(ai, "greet") +{ +} + +bool GreetAction::Execute(Event event) +{ + ObjectGuid guid = AI_VALUE(ObjectGuid, "new player nearby"); + if (!guid || !guid.IsPlayer()) return false; + + Player* player = dynamic_cast(ai->GetUnit(guid)); + if (!player) return false; + + if (!bot->IsInFront(player, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT) && !bot->IsTaxiFlying()) + { + bot->SetFacingToObject(player); + return true; + } + + ObjectGuid oldSel = bot->GetSelectionGuid(); + bot->SetSelectionGuid(guid); + bot->HandleEmote(EMOTE_ONESHOT_WAVE); + ai->PlaySound(TEXTEMOTE_HELLO); + bot->SetSelectionGuid(oldSel); + + set& alreadySeenPlayers = ai->GetAiObjectContext()->GetValue& >("already seen players")->Get(); + alreadySeenPlayers.insert(guid); + + list nearestPlayers = ai->GetAiObjectContext()->GetValue >("nearest friendly players")->Get(); + for (list::iterator i = nearestPlayers.begin(); i != nearestPlayers.end(); ++i) { + { + alreadySeenPlayers.insert(*i); + } + } + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GreetAction.h b/src/modules/Bots/playerbot/strategy/actions/GreetAction.h new file mode 100644 index 000000000..dd972a6ec --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GreetAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class GreetAction : public Action + { + public: + GreetAction(PlayerbotAI* ai); + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.cpp b/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.cpp new file mode 100644 index 000000000..b4fb75f82 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.cpp @@ -0,0 +1,44 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GuildAcceptAction.h" + +using namespace std; +using namespace ai; + +bool GuildAcceptAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + bool accept = true; + uint32 guildId = master->GetGuildId(); + if (!guildId) + { + ai->TellMaster("You are not in a guild"); + accept = false; + } + else if (bot->GetGuildId()) + { + ai->TellMaster("Sorry, I am in a guild already"); + accept = false; + } + else if (!ai->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, master, true)) + { + accept = false; + } + + WorldPacket packet; + if (accept) + { + bot->SetGuildIdInvited(guildId); + bot->GetSession()->HandleGuildAcceptOpcode(packet); + } + else + { + bot->GetSession()->HandleGuildDeclineOpcode(packet); + } + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.h b/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.h new file mode 100644 index 000000000..ab8910cc6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GuildAcceptAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class GuildAcceptAction : public Action { + public: + GuildAcceptAction(PlayerbotAI* ai) : Action(ai, "guild accept") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.cpp b/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.cpp new file mode 100644 index 000000000..bb69116aa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.cpp @@ -0,0 +1,15 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GuildBankAction.h" + +#include "../values/ItemCountValue.h" +#include "Guild.h" +#include "GuildMgr.h" + +using namespace std; +using namespace ai; + +bool GuildBankAction::Execute(Event event) +{ + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.h b/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.h new file mode 100644 index 000000000..d9b92a3ea --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/GuildBankAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class GuildBankAction : public InventoryAction { + public: + GuildBankAction(PlayerbotAI* ai) : InventoryAction(ai, "guild bank") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/HelpAction.cpp b/src/modules/Bots/playerbot/strategy/actions/HelpAction.cpp new file mode 100644 index 000000000..c980bfa12 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/HelpAction.cpp @@ -0,0 +1,59 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HelpAction.h" +#include "ChatActionContext.h" + +using namespace ai; + +HelpAction::HelpAction(PlayerbotAI* ai) : Action(ai, "help"), chatContext(new ChatActionContext()) +{ +} + +HelpAction::HelpAction(const HelpAction& other) : Action(ai, "help"), chatContext(new ChatActionContext()) +{ +} + +HelpAction::~HelpAction() +{ + delete chatContext; +} + +bool HelpAction::Execute(Event event) +{ + TellChatCommands(); + TellStrategies(); + return true; +} + +void HelpAction::TellChatCommands() +{ + ostringstream out; + out << "Whisper any of: "; + out << CombineSupported(chatContext->supports()); + out << ", [item], [quest] or [object] link"; + ai->TellMaster(out); +} + +void HelpAction::TellStrategies() +{ + ostringstream out; + out << "Possible strategies (co/nc/dead commands): "; + out << CombineSupported(ai->GetAiObjectContext()->GetSupportedStrategies()); + ai->TellMaster(out); +} + +string HelpAction::CombineSupported(set commands) +{ + ostringstream out; + + for (set::iterator i = commands.begin(); i != commands.end(); ) + { + out << *i; + if (++i != commands.end()) + { + out << ", "; + } + } + + return out.str(); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/HelpAction.h b/src/modules/Bots/playerbot/strategy/actions/HelpAction.h new file mode 100644 index 000000000..a258d46ef --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/HelpAction.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class HelpAction : public Action { + public: + HelpAction(PlayerbotAI* ai); + HelpAction(const HelpAction& other); + virtual ~HelpAction(); + virtual bool Execute(Event event); + + private: + void TellChatCommands(); + void TellStrategies(); + string CombineSupported(set commands); + + private: + NamedObjectContext* chatContext; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp new file mode 100644 index 000000000..430931078 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp @@ -0,0 +1,332 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "InventoryAction.h" + +#include "../values/ItemCountValue.h" + +using namespace ai; + + +class FindPotionVisitor : public FindUsableItemVisitor +{ +public: + FindPotionVisitor(Player* bot, uint32 effectId) : FindUsableItemVisitor(bot), effectId(effectId) {} + + virtual bool Accept(const ItemPrototype* proto) + { + if (proto->Class == ITEM_CLASS_CONSUMABLE && (proto->SubClass == ITEM_SUBCLASS_POTION || proto->SubClass == ITEM_SUBCLASS_FLASK)) + { + for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) + { + const SpellEntry* const spellInfo = sSpellStore.LookupEntry(proto->Spells[j].SpellId); + if (!spellInfo) + { + return false; + } + + for (int i = 0 ; i < 3; i++) + { + if (spellInfo->Effect[i] == effectId) + { + return true; + } + } + } + } + return false; + } + +private: + uint32 effectId; +}; + +class FindFoodVisitor : public FindUsableItemVisitor +{ +public: + FindFoodVisitor(Player* bot, uint32 spellCategory) : FindUsableItemVisitor(bot) + { + this->spellCategory = spellCategory; + } + + virtual bool Accept(const ItemPrototype* proto) + { + return proto->Class == ITEM_CLASS_CONSUMABLE && + (proto->SubClass == ITEM_SUBCLASS_CONSUMABLE || proto->SubClass == ITEM_SUBCLASS_FOOD) && + proto->Spells[0].SpellCategory == spellCategory; + } + +private: + uint32 spellCategory; +}; + +void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) +{ + if (mask & ITERATE_ITEMS_IN_BAGS) + { + IterateItemsInBags(visitor); + } + + if (mask & ITERATE_ITEMS_IN_EQUIP) + { + IterateItemsInEquip(visitor); + } +} + +void InventoryAction::IterateItemsInBags(IterateItemsVisitor* visitor) +{ + for(int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item *pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (!visitor->Visit(pItem)) + { + return; + } + + for(int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i) + if (Item *pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (!visitor->Visit(pItem)) + { + return; + } + + for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag *pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for(uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* pItem = pBag->GetItemByPos(j)) + { + if (!visitor->Visit(pItem)) + { + return; + } + } + } + } + } +} + +void InventoryAction::IterateItemsInEquip(IterateItemsVisitor* visitor) +{ + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) + { + Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if(!pItem) + { + continue; + } + + if (!visitor->Visit(pItem)) + { + return; + } + } +} + +bool compare_items(const ItemPrototype *proto1, const ItemPrototype *proto2) +{ + if (proto1->Class != proto2->Class) + { + return proto1->Class > proto2->Class; + } + + if (proto1->SubClass != proto2->SubClass) + { + return proto1->SubClass < proto2->SubClass; + } + + if (proto1->Quality != proto2->Quality) + { + return proto1->Quality < proto2->Quality; + } + + if (proto1->ItemLevel != proto2->ItemLevel) + { + return proto1->ItemLevel > proto2->ItemLevel; + } + + return false; +} + +bool compare_items_by_level(const Item* item1, const Item* item2) +{ + return compare_items(item1->GetProto(), item2->GetProto()); +} + +void InventoryAction::TellItems(map itemMap, map soulbound) +{ + list items; + for (map::iterator i = itemMap.begin(); i != itemMap.end(); i++) + { + items.push_back(sObjectMgr.GetItemPrototype(i->first)); + } + + items.sort(compare_items); + + uint32 oldClass = -1; + for (list::iterator i = items.begin(); i != items.end(); i++) + { + ItemPrototype const *proto = *i; + + if (proto->Class != oldClass) + { + oldClass = proto->Class; + switch (proto->Class) + { + case ITEM_CLASS_CONSUMABLE: + ai->TellMaster("--- consumable ---"); + break; + case ITEM_CLASS_CONTAINER: + ai->TellMaster("--- container ---"); + break; + case ITEM_CLASS_WEAPON: + ai->TellMaster("--- weapon ---"); + break; + case ITEM_CLASS_ARMOR: + ai->TellMaster("--- armor ---"); + break; + case ITEM_CLASS_REAGENT: + ai->TellMaster("--- reagent ---"); + break; + case ITEM_CLASS_PROJECTILE: + ai->TellMaster("--- projectile ---"); + break; + case ITEM_CLASS_TRADE_GOODS: + ai->TellMaster("--- trade goods ---"); + break; + case ITEM_CLASS_RECIPE: + ai->TellMaster("--- recipe ---"); + break; + case ITEM_CLASS_QUIVER: + ai->TellMaster("--- quiver ---"); + break; + case ITEM_CLASS_QUEST: + ai->TellMaster("--- quest items ---"); + break; + case ITEM_CLASS_KEY: + ai->TellMaster("--- keys ---"); + break; + case ITEM_CLASS_MISC: + ai->TellMaster("--- other ---"); + break; + } + } + + TellItem(proto, itemMap[proto->ItemId], soulbound[proto->ItemId]); + } +} + +void InventoryAction::TellItem(ItemPrototype const * proto, int count, bool soulbound) +{ + ostringstream out; + out << chat->formatItem(proto, count); + if (soulbound) + { + out << " (soulbound)"; + } + ai->TellMaster(out.str()); +} + +list InventoryAction::parseItems(string text) +{ + set found; + size_t pos = text.find(" "); + int count = pos!=string::npos ? atoi(text.substr(pos + 1).c_str()) : TRADE_SLOT_TRADED_COUNT; + if (count < 1) + { + count = 1; + } + else if (count > TRADE_SLOT_TRADED_COUNT) + { + count = TRADE_SLOT_TRADED_COUNT; + } + + if (text == "food") + { + FindFoodVisitor visitor(bot, 11); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + if (text == "drink" || text == "water") + { + FindFoodVisitor visitor(bot, 59); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + if (text == "mana potion") + { + FindPotionVisitor visitor(bot, SPELL_EFFECT_ENERGIZE); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + if (text == "healing potion") + { + FindPotionVisitor visitor(bot, SPELL_EFFECT_HEAL); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + FindNamedItemVisitor visitor(bot, text); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + + uint32 quality = chat->parseItemQuality(text); + if (quality != MAX_ITEM_QUALITY) + { + FindItemsToTradeByQualityVisitor visitor(quality, count); + IterateItems(&visitor); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + uint32 itemClass = MAX_ITEM_CLASS, itemSubClass = 0; + if (chat->parseItemClass(text, &itemClass, &itemSubClass)) + { + FindItemsToTradeByClassVisitor visitor(itemClass, itemSubClass, count); + IterateItems(&visitor); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + uint32 fromSlot = chat->parseSlot(text); + if (fromSlot != EQUIPMENT_SLOT_END) + { + Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, fromSlot); + if (item) + { + found.insert(item); + } + } + + ItemIds ids = chat->parseItems(text); + for (ItemIds::iterator i = ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + IterateItems(&visitor, ITERATE_ALL_ITEMS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + list result; + for (set::iterator i = found.begin(); i != found.end(); ++i) + { + result.push_back(*i); + } + + result.sort(compare_items_by_level); + + return result; +} + +uint32 InventoryAction::GetItemCount(FindItemVisitor* visitor, IterateItemsMask mask) +{ + IterateItems(visitor, mask); + uint32 count = 0; + list& items = visitor->GetResult(); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* item = *i; + count += item->GetCount(); + } + return count; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h new file mode 100644 index 000000000..bcf9510eb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Action.h" +#include "../ItemVisitors.h" + +namespace ai +{ + + + class InventoryAction : public Action { + public: + InventoryAction(PlayerbotAI* ai, string name) : Action(ai, name) {} + + protected: + void IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask = ITERATE_ITEMS_IN_BAGS); + void TellItems(map items, map soulbound); + void TellItem(ItemPrototype const * proto, int count, bool soulbound); + list parseItems(string text); + uint32 GetItemCount(FindItemVisitor* visitor, IterateItemsMask mask = ITERATE_ITEMS_IN_BAGS); + + private: + void IterateItemsInBags(IterateItemsVisitor* visitor); + void IterateItemsInEquip(IterateItemsVisitor* visitor); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.cpp b/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.cpp new file mode 100644 index 000000000..ea2f1c923 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.cpp @@ -0,0 +1,103 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "InventoryChangeFailureAction.h" + + +using namespace ai; + +map InventoryChangeFailureAction::messages; + +bool InventoryChangeFailureAction::Execute(Event event) +{ + if (!ai->GetMaster()) + { + return false; + } + + if (messages.empty()) + { + messages[EQUIP_ERR_CANT_EQUIP_LEVEL_I] = "My level is too low"; + messages[EQUIP_ERR_CANT_EQUIP_SKILL] = "My skill level is too low"; + messages[EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT] = "Invalid slot"; + messages[EQUIP_ERR_BAG_FULL] = "My bags are full"; + messages[EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG] = "This bag is not empty"; + messages[EQUIP_ERR_CANT_TRADE_EQUIP_BAGS] = "Cannot trade equipped bags"; + messages[EQUIP_ERR_ONLY_AMMO_CAN_GO_HERE] = "Invalid slot (only ammo is required)"; + messages[EQUIP_ERR_NO_REQUIRED_PROFICIENCY] = "I don't have necessary skill"; + messages[EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE] = "No equipment slot available"; + messages[EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM] = "I will never be able to use this"; + messages[EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM2] = "I will never be able to use this"; + messages[EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE2] = messages[EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE]; + messages[EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED] = "Cannot equip with two-handed weapon equipped"; + messages[EQUIP_ERR_CANT_DUAL_WIELD] = "I cannot dual-wield"; + messages[EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG] = "This item cannot go in this bag"; + messages[EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2] = "This item cannot go in this bag"; + messages[EQUIP_ERR_CANT_CARRY_MORE_OF_THIS] = "I can't carry anymore of those"; + messages[EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE3] = messages[EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE]; + messages[EQUIP_ERR_ITEM_CANT_STACK] = "Item cannot stack"; + messages[EQUIP_ERR_ITEM_CANT_BE_EQUIPPED] = "Item cannot be equipped"; + messages[EQUIP_ERR_ITEMS_CANT_BE_SWAPPED] = "Cannot swap these items"; + messages[EQUIP_ERR_SLOT_IS_EMPTY] = "Nothing to equip"; + messages[EQUIP_ERR_ITEM_NOT_FOUND] = "Cannot find the item"; + messages[EQUIP_ERR_CANT_DROP_SOULBOUND] = "Cannot drop soulbound items"; + messages[EQUIP_ERR_OUT_OF_RANGE] = "I am out of range"; + messages[EQUIP_ERR_TRIED_TO_SPLIT_MORE_THAN_COUNT] = "Invalid split number"; + messages[EQUIP_ERR_COULDNT_SPLIT_ITEMS] = "Cannot split this"; + messages[EQUIP_ERR_MISSING_REAGENT] = "Missing reagents"; + messages[EQUIP_ERR_NOT_ENOUGH_MONEY] = "Not enough money"; + messages[EQUIP_ERR_NOT_A_BAG] = "This is not a bag"; + messages[EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS] = "The bag is not empty"; + messages[EQUIP_ERR_DONT_OWN_THAT_ITEM] = "This is not my item"; + messages[EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER] = "Only quiver can be equipped"; + messages[EQUIP_ERR_MUST_PURCHASE_THAT_BAG_SLOT] = "I must purchase the slot before"; + messages[EQUIP_ERR_TOO_FAR_AWAY_FROM_BANK] = "I am too far away from bank"; + messages[EQUIP_ERR_ITEM_LOCKED] = "This item is locked"; + messages[EQUIP_ERR_YOU_ARE_STUNNED] = "I am stunned"; + messages[EQUIP_ERR_YOU_ARE_DEAD] = "I am dead"; + messages[EQUIP_ERR_CANT_DO_RIGHT_NOW] = "I can't do this right now"; + messages[EQUIP_ERR_INT_BAG_ERROR] = "Internal error"; + messages[EQUIP_ERR_CAN_EQUIP_ONLY1_BOLT] = "Only bolts are allowed"; + messages[EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH] = "Ammo poach is allowed"; + messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED] = "Item can't be wrapped"; + messages[EQUIP_ERR_EQUIPPED_CANT_BE_WRAPPED] = messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED]; + messages[EQUIP_ERR_WRAPPED_CANT_BE_WRAPPED] = messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED]; + messages[EQUIP_ERR_BOUND_CANT_BE_WRAPPED] = messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED]; + messages[EQUIP_ERR_UNIQUE_CANT_BE_WRAPPED] = messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED]; + messages[EQUIP_ERR_BAGS_CANT_BE_WRAPPED] = messages[EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED]; + messages[EQUIP_ERR_ALREADY_LOOTED] = "Already looted"; + messages[EQUIP_ERR_INVENTORY_FULL] = "My inventory is full"; + messages[EQUIP_ERR_BANK_FULL] = "My bank is full"; + messages[EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT] = "Item item is sold out"; + messages[EQUIP_ERR_BAG_FULL3] = messages[EQUIP_ERR_BANK_FULL]; + messages[EQUIP_ERR_ITEM_NOT_FOUND2] = messages[EQUIP_ERR_ITEM_NOT_FOUND]; + messages[EQUIP_ERR_ITEM_CANT_STACK2] = messages[EQUIP_ERR_ITEM_CANT_STACK]; + messages[EQUIP_ERR_BAG_FULL4] = messages[EQUIP_ERR_BAG_FULL]; + messages[EQUIP_ERR_ITEM_SOLD_OUT] = messages[EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT]; + messages[EQUIP_ERR_OBJECT_IS_BUSY] = "This object is busy"; + messages[EQUIP_ERR_NOT_IN_COMBAT] = "I am not in combat"; + messages[EQUIP_ERR_NOT_WHILE_DISARMED] = "Cannot do while disarmed"; + messages[EQUIP_ERR_BAG_FULL6] = messages[EQUIP_ERR_BAG_FULL]; + messages[EQUIP_ERR_CANT_EQUIP_RANK] = "Not enough rank"; + messages[EQUIP_ERR_CANT_EQUIP_REPUTATION] = "Not enough reputation"; + messages[EQUIP_ERR_TOO_MANY_SPECIAL_BAGS] = "Too many special bags"; + messages[EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW] = "Cannot loot this right now"; + } + + WorldPacket p(event.getPacket()); + p.rpos(0); + uint8 err; + p >> err; + if (err == EQUIP_ERR_OK) + { + return false; + } + + string msg = messages[(InventoryResult)err]; + if (!msg.empty()) + { + ai->TellMaster(msg); + return true; + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.h b/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.h new file mode 100644 index 000000000..d596d7fdc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryChangeFailureAction.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class InventoryChangeFailureAction : public Action { + public: + InventoryChangeFailureAction(PlayerbotAI* ai) : Action(ai, "inventory change failure") {} + virtual bool Execute(Event event); + + private: + static map messages; + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/InviteToGroupAction.h b/src/modules/Bots/playerbot/strategy/actions/InviteToGroupAction.h new file mode 100644 index 000000000..babbedb64 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/InviteToGroupAction.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class InviteToGroupAction : public Action + { + public: + InviteToGroupAction(PlayerbotAI* ai) : Action(ai, "invite") {} + + virtual bool Execute(Event event) + { + Player* master = event.getOwner(); + if (!master) + { + return false; + } + + WorldPacket p; + uint32 roles_mask = 0; + p << master->GetName(); + p << roles_mask; + bot->GetSession()->HandleGroupInviteOpcode(p); + + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LeaveGroupAction.h b/src/modules/Bots/playerbot/strategy/actions/LeaveGroupAction.h new file mode 100644 index 000000000..653fcd86c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LeaveGroupAction.h @@ -0,0 +1,87 @@ +#pragma once + +#include "../Action.h" +#include "../../RandomPlayerbotMgr.h" + +namespace ai +{ + class LeaveGroupAction : public Action { + public: + LeaveGroupAction(PlayerbotAI* ai, string name = "leave") : Action(ai, name) {} + + virtual bool Execute(Event event) + { + if (!bot->GetGroup()) + { + return false; + } + + ai->TellMaster("Goodbye!", PLAYERBOT_SECURITY_TALK); + + WorldPacket p; + string member = bot->GetName(); + p << uint32(PARTY_OP_LEAVE) << member << uint32(0); + bot->GetSession()->HandleGroupDisbandOpcode(p); + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + bot->GetPlayerbotAI()->SetMaster(NULL); + sRandomPlayerbotMgr.ScheduleTeleport(bot->GetObjectGuid()); + sRandomPlayerbotMgr.SetLootAmount(bot, 0); + } + + ai->ResetStrategies(); + return true; + } + }; + + class PartyCommandAction : public LeaveGroupAction { + public: + PartyCommandAction(PlayerbotAI* ai) : LeaveGroupAction(ai, "party command") {} + + virtual bool Execute(Event event) + { + WorldPacket& p = event.getPacket(); + p.rpos(0); + uint32 operation; + string member; + + p >> operation >> member; + + if (operation != PARTY_OP_LEAVE) + { + return false; + } + + Player* master = GetMaster(); + if (master && member == master->GetName()) + { + return LeaveGroupAction::Execute(event); + } + + return false; + } + }; + + class UninviteAction : public LeaveGroupAction { + public: + UninviteAction(PlayerbotAI* ai) : LeaveGroupAction(ai, "party command") {} + + virtual bool Execute(Event event) + { + WorldPacket& p = event.getPacket(); + p.rpos(0); + ObjectGuid guid; + + p >> guid; + + if (bot->GetObjectGuid() == guid) + { + return LeaveGroupAction::Execute(event); + } + + return false; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LfgActions.cpp b/src/modules/Bots/playerbot/strategy/actions/LfgActions.cpp new file mode 100644 index 000000000..a759fbc62 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LfgActions.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "LfgActions.h" +//#include "../../AiFactory.h" +//#include "../../PlayerbotAIConfig.h" +//#include "../ItemVisitors.h" +//#include "../../RandomPlayerbotMgr.h" +//#include "../../../../game/LFGMgr.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/LfgActions.h b/src/modules/Bots/playerbot/strategy/actions/LfgActions.h new file mode 100644 index 000000000..f11237243 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LfgActions.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.cpp b/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.cpp new file mode 100644 index 000000000..0893e82d9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.cpp @@ -0,0 +1,81 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ListQuestsActions.h" + + +using namespace ai; + +bool ListQuestsAction::Execute(Event event) +{ + if (event.getParam() == "completed") + { + ListQuests(QUEST_LIST_FILTER_COMPLETED); + } + else if (event.getParam() == "incompleted") + { + ListQuests(QUEST_LIST_FILTER_INCOMPLETED); + } + else if (event.getParam() == "all") + { + ListQuests(QUEST_LIST_FILTER_ALL); + } + else + { + ListQuests(QUEST_LIST_FILTER_SUMMARY); + } + return true; +} + +void ListQuestsAction::ListQuests(QuestListFilter filter) +{ + bool showIncompleted = filter & QUEST_LIST_FILTER_INCOMPLETED; + bool showCompleted = filter & QUEST_LIST_FILTER_COMPLETED; + + if (showIncompleted) + { + ai->TellMaster("--- Incomplete quests ---"); + } + int incompleteCount = ListQuests(false, !showIncompleted); + + if (showCompleted) + { + ai->TellMaster("--- Complete quests ---"); + } + int completeCount = ListQuests(true, !showCompleted); + + ai->TellMaster("--- Summary ---"); + std::ostringstream out; + out << "Total: " << (completeCount + incompleteCount) << " / 25 (incomplete: " << incompleteCount << ", complete: " << completeCount << ")"; + ai->TellMaster(out); +} + +int ListQuestsAction::ListQuests(bool completed, bool silent) +{ + int count = 0; + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (!questId) + { + continue; + } + + Quest const* pQuest = sObjectMgr.GetQuestTemplate(questId); + bool isCompletedQuest = bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE; + if (completed != isCompletedQuest) + { + continue; + } + + count++; + + if (silent) + { + continue; + } + + ai->TellMaster(chat->formatQuest(pQuest)); + } + + return count; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.h b/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.h new file mode 100644 index 000000000..961acb874 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ListQuestsActions.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + enum QuestListFilter { + QUEST_LIST_FILTER_SUMMARY = 0, + QUEST_LIST_FILTER_COMPLETED = 1, + QUEST_LIST_FILTER_INCOMPLETED = 2, + QUEST_LIST_FILTER_ALL = QUEST_LIST_FILTER_COMPLETED | QUEST_LIST_FILTER_INCOMPLETED + }; + + class ListQuestsAction : public Action { + public: + ListQuestsAction(PlayerbotAI* ai) : Action(ai, "quests") {} + virtual bool Execute(Event event); + + private: + int ListQuests(bool completed, bool silent); + void ListQuests(QuestListFilter filter); + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.cpp new file mode 100644 index 000000000..71430c9d9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.cpp @@ -0,0 +1,317 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ListSpellsAction.h" +#include "../ItemVisitors.h" + +using namespace ai; + +map ListSpellsAction::skillSpells; +set ListSpellsAction::vendorItems; + +bool CompareSpells(pair& s1, pair& s2) +{ + const SpellEntry* const si1 = sSpellStore.LookupEntry(s1.first); + const SpellEntry* const si2 = sSpellStore.LookupEntry(s2.first); + int p1 = si1->SchoolMask * 20000; + int p2 = si2->SchoolMask * 20000; + + uint32 skill1 = 0, skill2 = 0; + uint32 skillValue1 = 0, skillValue2 = 0; + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + { + SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j); + if (skillLine && skillLine->spellId == s1.first) + { + skill1 = skillLine->skillId; + skillValue1 = skillLine->min_value; + } + if (skillLine && skillLine->spellId == s2.first) + { + skill2 = skillLine->skillId; + skillValue2 = skillLine->min_value; + } + if (skill1 && skill2) break; + } + + p1 += skill1 * 500; + p2 += skill2 * 500; + + p1 += skillValue1; + p2 += skillValue2; + + if (p1 == p2) + { + return strcmp(si1->SpellName[0], si1->SpellName[1]) > 0; + } + + return p1 > p2; +} + +bool ListSpellsAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + if (skillSpells.empty()) + { + for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j) + { + SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j); + if (skillLine) + { + skillSpells[skillLine->spellId] = skillLine; + } + } + } + + if (vendorItems.empty()) + { + QueryResult* results = WorldDatabase.PQuery("SELECT item FROM npc_vendor where maxcount = 0"); + if (results != NULL) + { + do + { + Field* fields = results->Fetch(); + vendorItems.insert(fields[0].GetUInt32()); + } while (results->NextRow()); + + delete results; + } + } + + std::ostringstream posOut; + std::ostringstream negOut; + + string filter = event.getParam(); + uint32 skill = 0; + + vector ss = split(filter, ' '); + if (!ss.empty()) + { + skill = chat->parseSkill(ss[0]); + if (skill != SKILL_NONE) + { + filter = ss.size() > 1 ? filter = ss[1] : ""; + } + + if (ss[0] == "first" && ss[1] == "aid") + { + skill = SKILL_FIRST_AID; + filter = ss.size() > 2 ? filter = ss[2] : ""; + } + } + + + const std::string ignoreList = ",Opening,Closing,Stuck,Remove Insignia,Opening - No Text,Grovel,Duel,Honorless Target,"; + std::string alreadySeenList = ","; + + int minLevel = 0, maxLevel = 0; + if (filter.find("-") != string::npos) + { + vector ff = split(filter, '-'); + minLevel = atoi(ff[0].c_str()); + maxLevel = atoi(ff[1].c_str()); + filter = ""; + } + + bool craftableOnly = false; + if (filter.find("+") != string::npos) + { + craftableOnly = true; + filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end()); + } + + uint32 slot = chat->parseSlot(filter); + if (slot != EQUIPMENT_SLOT_END) + { + filter = ""; + } + + list > spells; + for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) + { + const uint32 spellId = itr->first; + + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + { + continue; + } + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + SkillLineAbilityEntry const* skillLine = skillSpells[spellId]; + if (skill != SKILL_NONE && (!skillLine || skillLine->skillId != skill)) + { + continue; + } + + string comp = pSpellInfo->SpellName[0]; + if (!(ignoreList.find(comp) == std::string::npos && alreadySeenList.find(comp) == std::string::npos)) + { + continue; + } + + if (!filter.empty() && !strstri(pSpellInfo->SpellName[0], filter.c_str())) + { + continue; + } + + bool first = true; + int craftCount = -1; + ostringstream materials; + for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x) + { + if (pSpellInfo->Reagent[x] <= 0) + { + continue; + } + + uint32 itemid = pSpellInfo->Reagent[x]; + uint32 reagentsRequired = pSpellInfo->ReagentCount[x]; + if (itemid) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemid); + if (proto) + { + if (first) + { + materials << ": "; + first = false; + } + else + { + materials << ", "; + } + materials << chat->formatItem(proto, reagentsRequired); + + FindItemByIdVisitor visitor(itemid); + uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor); + bool buyable = (vendorItems.find(itemid) != vendorItems.end()); + if (!buyable) + { + uint32 craftable = reagentsInInventory / reagentsRequired; + if (craftCount < 0 || craftCount > craftable) + { + craftCount = craftable; + } + } + + if (reagentsInInventory) + { + materials << "|cffffff00(x" << reagentsInInventory << ")|r "; + } + else if (buyable) + { + materials << "|cffffff00(buy)|r "; + } + } + } + } + + if (craftCount < 0) craftCount = 0; + + ostringstream out; + bool filtered = false; + if (skillLine) + { + for (int i = 0; i < 3; ++i) + { + if (pSpellInfo->Effect[i] == SPELL_EFFECT_CREATE_ITEM) + { + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(pSpellInfo->EffectItemType[i]); + if (proto) + { + if (craftCount) + { + out << "|cffffff00(x" << craftCount << ")|r "; + } + out << chat->formatItem(proto); + + if ((minLevel || maxLevel) && (!proto->RequiredLevel || proto->RequiredLevel < minLevel || proto->RequiredLevel > maxLevel)) + { + filtered = true; + break; + } + + if (slot != EQUIPMENT_SLOT_END && bot->FindEquipSlot(proto, slot, true) != slot) + { + filtered = true; + break; + } + } + } + } + } + + if (out.str().empty()) + { + out << chat->formatSpell(pSpellInfo); + } + + if (filtered) + { + continue; + } + + if (craftableOnly && !craftCount) + { + continue; + } + + out << materials.str(); + + if (skillLine && skillLine->skillId) + { + int GrayLevel = (int)skillLine->max_value, + GreenLevel = (int)(skillLine->max_value + skillLine->min_value) / 2, + YellowLevel = (int)skillLine->min_value, + SkillValue = (int)bot->GetBaseSkillValue(skillLine->skillId); + + out << " - "; + if (SkillValue >= GrayLevel) + { + out << " |cff808080gray"; + } + else if (SkillValue >= GreenLevel) + { + out << " |cff80be80green"; + } + else if (SkillValue >= YellowLevel) + { + out << " |cffffff00yellow"; + } + else + { + out << " |cffff8040orange"; + } + out << "|r"; + } + + if (out.str().empty()) + { + continue; + } + + spells.push_back(pair(spellId, out.str())); + alreadySeenList += pSpellInfo->SpellName[0]; + alreadySeenList += ","; + } + + ai->TellMaster("=== Spells ==="); + spells.sort(CompareSpells); + + for (list >::iterator i = spells.begin(); i != spells.end(); ++i) + { + ai->TellMaster(i->second); + } + + return true; +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.h b/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.h new file mode 100644 index 000000000..2cd086a67 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ListSpellsAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + + +namespace ai +{ + class ListSpellsAction : public InventoryAction { + public: + ListSpellsAction(PlayerbotAI* ai) : InventoryAction(ai, "spells") {} + + virtual bool Execute(Event event); + + private: + static map skillSpells; + static set vendorItems; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.cpp b/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.cpp new file mode 100644 index 000000000..a56e40223 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.cpp @@ -0,0 +1,59 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LogLevelAction.h" + + +using namespace ai; + +bool LogLevelAction::Execute(Event event) +{ + string param = event.getParam(); + Value *value = ai->GetAiObjectContext()->GetValue("log level"); + + ostringstream out; + if (param != "?") + { + value->Set(string2logLevel(param)); + out << "My log level set to " << logLevel2string(value->Get()); + } + else + { + out << "My log level is " << logLevel2string(value->Get()); + } + ai->TellMaster(out); + return true; +} + +string LogLevelAction::logLevel2string(LogLevel level) +{ + switch (level) + { + case LOG_LVL_BASIC: + return "basic"; + case LOG_LVL_MINIMAL: + return "minimal"; + case LOG_LVL_DETAIL: + return "detail"; + default: + return "debug"; + } +} +LogLevel LogLevelAction::string2logLevel(string& level) +{ + if (level == "debug") + { + return LOG_LVL_DEBUG; + } + else if (level == "minimal") + { + return LOG_LVL_MINIMAL; + } + else if (level == "detail") + { + return LOG_LVL_DETAIL; + } + else + { + return LOG_LVL_BASIC; + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.h b/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.h new file mode 100644 index 000000000..00bb5a5eb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LogLevelAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class LogLevelAction : public Action { + public: + LogLevelAction(PlayerbotAI* ai) : Action(ai, "log") {} + virtual bool Execute(Event event); + + public: + static string logLevel2string(LogLevel level); + static LogLevel string2logLevel(string& level); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/LootAction.cpp b/src/modules/Bots/playerbot/strategy/actions/LootAction.cpp new file mode 100644 index 000000000..dc2931ef6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootAction.cpp @@ -0,0 +1,418 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootAction.h" + +#include "../../LootObjectStack.h" +#include "../../PlayerbotAIConfig.h" +#include "../../../ahbot/AhBot.h" +#include "../../RandomPlayerbotMgr.h" +#include "../values/ItemUsageValue.h" +#include "../../GuildTaskMgr.h" +#include "../values/LootStrategyValue.h" + +using namespace ai; + +bool LootAction::Execute(Event event) +{ + if (!AI_VALUE(bool, "has available loot")) + { + return false; + } + + LootObject prevLoot = AI_VALUE(LootObject, "loot target"); + LootObject const& lootObject = AI_VALUE(LootObjectStack*, "available loot")->GetLoot(sPlayerbotAIConfig.lootDistance); + + if (!prevLoot.IsEmpty() && prevLoot.guid != lootObject.guid) + { + bot->GetSession()->DoLootRelease(prevLoot.guid); + } + + context->GetValue("loot target")->Set(lootObject); + return true; +} + +enum ProfessionSpells +{ + ALCHEMY = 2259, + BLACKSMITHING = 2018, + COOKING = 2550, + ENCHANTING = 7411, + ENGINEERING = 49383, + FIRST_AID = 3273, + FISHING = 7620, + HERB_GATHERING = 2366, + INSCRIPTION = 45357, + JEWELCRAFTING = 25229, + MINING = 2575, + SKINNING = 8613, + TAILORING = 3908 +}; + +bool OpenLootAction::Execute(Event event) +{ + LootObject lootObject = AI_VALUE(LootObject, "loot target"); + bool result = DoLoot(lootObject); + if (result) + { + AI_VALUE(LootObjectStack*, "available loot")->Remove(lootObject.guid); + context->GetValue("loot target")->Set(LootObject()); + } + return result; +} + +bool OpenLootAction::DoLoot(LootObject& lootObject) +{ + if (lootObject.IsEmpty()) + { + return false; + } + + Creature* creature = ai->GetCreature(lootObject.guid); + if (creature && bot->GetDistance(creature) > INTERACTION_DISTANCE) + { + return false; + } + + if (creature && creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE)) + { + bot->GetMotionMaster()->Clear(); + WorldPacket packet(CMSG_LOOT, 8); + packet << lootObject.guid; + bot->GetSession()->HandleLootOpcode(packet); + return true; + } + + if (creature) + { + SkillType skill = creature->GetCreatureInfo()->GetRequiredLootSkill(); + if (!CanOpenLock(skill, lootObject.reqSkillValue)) + { + return false; + } + + bot->GetMotionMaster()->Clear(); + switch (skill) + { + case SKILL_ENGINEERING: + return ai->HasSkill(SKILL_ENGINEERING) ? ai->CastSpell(ENGINEERING, creature) : false; + case SKILL_HERBALISM: + return ai->HasSkill(SKILL_HERBALISM) ? ai->CastSpell(32605, creature) : false; + case SKILL_MINING: + return ai->HasSkill(SKILL_MINING) ? ai->CastSpell(32606, creature) : false; + default: + return ai->HasSkill(SKILL_SKINNING) ? ai->CastSpell(SKINNING, creature) : false; + } + } + + GameObject* go = ai->GetGameObject(lootObject.guid); + if (go && bot->GetDistance(go) > INTERACTION_DISTANCE) + { + return false; + } + + bot->GetMotionMaster()->Clear(); + if (lootObject.skillId == SKILL_MINING) + { + return ai->HasSkill(SKILL_MINING) ? ai->CastSpell(MINING, bot) : false; + } + + if (lootObject.skillId == SKILL_HERBALISM) + { + return ai->HasSkill(SKILL_HERBALISM) ? ai->CastSpell(HERB_GATHERING, bot) : false; + } + + uint32 spellId = GetOpeningSpell(lootObject); + if (!spellId) + { + return false; + } + + return ai->CastSpell(spellId, bot); +} + +uint32 OpenLootAction::GetOpeningSpell(LootObject& lootObject) +{ + GameObject* go = ai->GetGameObject(lootObject.guid); + if (go && go->isSpawned()) + { + return GetOpeningSpell(lootObject, go); + } + + return 0; +} + +uint32 OpenLootAction::GetOpeningSpell(LootObject& lootObject, GameObject* go) +{ + for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + { + continue; + } + + if (spellId == MINING || spellId == HERB_GATHERING) + { + continue; + } + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + if (CanOpenLock(lootObject, pSpellInfo, go)) + { + return spellId; + } + } + + for (uint32 spellId = 0; spellId < sSpellStore.GetNumRows(); spellId++) + { + if (spellId == MINING || spellId == HERB_GATHERING) + { + continue; + } + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + if (CanOpenLock(lootObject, pSpellInfo, go)) + { + return spellId; + } + } + + return sPlayerbotAIConfig.openGoSpell; +} + +bool OpenLootAction::CanOpenLock(LootObject& lootObject, const SpellEntry* pSpellInfo, GameObject* go) +{ + for (int effIndex = 0; effIndex <= EFFECT_INDEX_2; effIndex++) + { + if (pSpellInfo->Effect[effIndex] != SPELL_EFFECT_OPEN_LOCK && pSpellInfo->Effect[effIndex] != SPELL_EFFECT_SKINNING) + { + return false; + } + + uint32 lockId = go->GetGOInfo()->GetLockId(); + if (!lockId) + { + return false; + } + + LockEntry const *lockInfo = sLockStore.LookupEntry(lockId); + if (!lockInfo) + { + return false; + } + + bool reqKey = false; // some locks not have reqs + + for(int j = 0; j < 8; ++j) + { + switch(lockInfo->Type[j]) + { + /* + case LOCK_KEY_ITEM: + return true; + */ + case LOCK_KEY_SKILL: + { + if(uint32(pSpellInfo->EffectMiscValue[effIndex]) != lockInfo->Index[j]) + { + continue; + } + + uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j])); + if (skillId == SKILL_NONE) + { + return true; + } + + if (CanOpenLock(skillId, lockInfo->Skill[j])) + { + return true; + } + } + } + } + } + + return false; +} + +bool OpenLootAction::CanOpenLock(uint32 skillId, uint32 reqSkillValue) +{ + uint32 skillValue = bot->GetSkillValue(skillId); + return skillValue >= reqSkillValue || !reqSkillValue; +} + +bool StoreLootAction::Execute(Event event) +{ + WorldPacket p(event.getPacket()); // (8+1+4+1+1+4+4+4+4+4+1) + ObjectGuid guid; + uint8 loot_type; + uint32 gold = 0; + uint8 items = 0; + + p.rpos(0); + p >> guid; // 8 corpse guid + p >> loot_type; // 1 loot type + + if (p.size() > 10) + { + p >> gold; // 4 money on corpse + p >> items; // 1 number of items on corpse + } + + if (gold > 0) + { + WorldPacket packet(CMSG_LOOT_MONEY, 0); + bot->GetSession()->HandleLootMoneyOpcode(packet); + } + + for (uint8 i = 0; i < items; ++i) + { + uint32 itemid; + uint32 itemcount; + uint8 lootslot_type; + uint8 itemindex; + bool grab = false; + + p >> itemindex; + p >> itemid; + p >> itemcount; + p.read_skip(); // display id + p.read_skip(); // randomSuffix + p.read_skip(); // randomPropertyId + p >> lootslot_type; // 0 = can get, 1 = look only, 2 = master get + + if (lootslot_type != LOOT_SLOT_NORMAL) + { + continue; + } + + if (loot_type != LOOT_SKINNING && !IsLootAllowed(itemid, ai)) + { + continue; + } + + ItemPrototype const *proto = sItemStorage.LookupEntry(itemid); + if (!proto) + { + continue; + } + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + uint32 price = itemcount * auctionbot.GetSellPrice(proto) * sRandomPlayerbotMgr.GetSellMultiplier(bot) + gold; + uint32 lootAmount = sRandomPlayerbotMgr.GetLootAmount(bot); + if (price) + { + sRandomPlayerbotMgr.SetLootAmount(bot, bot->GetGroup() ? lootAmount + price : 0); + } + + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference *ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if( ref->getSource() != bot) + { + sGuildTaskMgr.CheckItemTask(itemid, itemcount, ref->getSource(), bot); + } + } + } + } + + WorldPacket packet(CMSG_AUTOSTORE_LOOT_ITEM, 1); + packet << itemindex; + bot->GetSession()->HandleAutostoreLootItemOpcode(packet); + + ostringstream out; out << "Looting " << chat->formatItem(proto); + ai->TellMasterNoFacing(out.str()); + } + + AI_VALUE(LootObjectStack*, "available loot")->Remove(guid); + + // release loot + bot->GetSession()->DoLootRelease(guid); + return true; +} + +bool StoreLootAction::IsLootAllowed(uint32 itemid, PlayerbotAI *ai) +{ + AiObjectContext *context = ai->GetAiObjectContext(); + LootStrategy* lootStrategy = AI_VALUE(LootStrategy*, "loot strategy"); + + set& lootItems = AI_VALUE(set&, "always loot list"); + if (lootItems.find(itemid) != lootItems.end()) + { + return true; + } + + ItemPrototype const *proto = sObjectMgr.GetItemPrototype(itemid); + if (!proto) + { + return false; + } + + uint32 max = proto->MaxCount; + if (max > 0 && ai->GetBot()->HasItemCount(itemid, max, true)) + { + return false; + } + + if (proto->StartQuest) + { + return true; + } + + if (proto->Bonding == BIND_QUEST_ITEM || + proto->Bonding == BIND_QUEST_ITEM1 || + proto->Class == ITEM_CLASS_QUEST) + { + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 entry = ai->GetBot()->GetQuestSlotQuestId(slot); + Quest const* quest = sObjectMgr.GetQuestTemplate(entry); + if (!quest) + { + continue; + } + + for (int i = 0; i < 4; i++) + { + if (quest->ReqItemId[i] == itemid && AI_VALUE2(uint8, "item count", proto->Name1) < quest->ReqItemCount[i]) + { + return true; + } + } + } + } + + return lootStrategy->CanLoot(proto, context); +} + +bool ReleaseLootAction::Execute(Event event) +{ + list gos = context->GetValue >("nearest game objects")->Get(); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + bot->GetSession()->DoLootRelease(*i); + } + + list corpses = context->GetValue >("nearest corpses")->Get(); + for (list::iterator i = corpses.begin(); i != corpses.end(); i++) + { + bot->GetSession()->DoLootRelease(*i); + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LootAction.h b/src/modules/Bots/playerbot/strategy/actions/LootAction.h new file mode 100644 index 000000000..2712eaae3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootAction.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../Action.h" +#include "../../LootObjectStack.h" +#include "MovementActions.h" + +namespace ai +{ + class LootAction : public MovementAction + { + public: + LootAction(PlayerbotAI* ai) : MovementAction(ai, "loot") {} + virtual bool Execute(Event event); + }; + + class OpenLootAction : public MovementAction + { + public: + OpenLootAction(PlayerbotAI* ai) : MovementAction(ai, "open loot") {} + virtual bool Execute(Event event); + + private: + bool DoLoot(LootObject& lootObject); + uint32 GetOpeningSpell(LootObject& lootObject); + uint32 GetOpeningSpell(LootObject& lootObject, GameObject* go); + bool CanOpenLock(LootObject& lootObject, const SpellEntry* pSpellInfo, GameObject* go); + bool CanOpenLock(uint32 skillId, uint32 reqSkillValue); + }; + + class StoreLootAction : public MovementAction + { + public: + StoreLootAction(PlayerbotAI* ai) : MovementAction(ai, "store loot") {} + virtual bool Execute(Event event); + static bool IsLootAllowed(uint32 itemid, PlayerbotAI *ai); + }; + + class ReleaseLootAction : public MovementAction + { + public: + ReleaseLootAction(PlayerbotAI* ai) : MovementAction(ai, "release loot") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LootRollAction.cpp b/src/modules/Bots/playerbot/strategy/actions/LootRollAction.cpp new file mode 100644 index 000000000..72eee484c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootRollAction.cpp @@ -0,0 +1,59 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootRollAction.h" + + +using namespace ai; + +bool LootRollAction::Execute(Event event) +{ + Player *bot = QueryItemUsageAction::ai->GetBot(); + + WorldPacket p(event.getPacket()); //WorldPacket packet for CMSG_LOOT_ROLL, (8+4+1) + ObjectGuid guid; + uint32 slot; + uint8 rollType; + p.rpos(0); //reset packet pointer + p >> guid; //guid of the item rolled + p >> slot; //number of players invited to roll + p >> rollType; //need,greed or pass on roll + + Group* group = bot->GetGroup(); + if(!group) + { + return false; + } + + RollVote vote = ROLL_PASS; + for (vector::iterator i = group->GetRolls().begin(); i != group->GetRolls().end(); ++i) + { + if ((*i)->isValid() && (*i)->lootedTargetGUID == guid && (*i)->itemSlot == slot) + { + uint32 itemId = (*i)->itemid; + ItemPrototype const *proto = sItemStorage.LookupEntry(itemId); + if (!proto) + { + continue; + } + + if (IsLootAllowed(itemId, bot->GetPlayerbotAI())) + { + vote = ROLL_NEED; + break; + } + } + } + + switch (group->GetLootMethod()) + { + case MASTER_LOOT: + case FREE_FOR_ALL: + group->CountRollVote(bot, guid, slot, ROLL_PASS); + break; + default: + group->CountRollVote(bot, guid, slot, vote); + break; + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LootRollAction.h b/src/modules/Bots/playerbot/strategy/actions/LootRollAction.h new file mode 100644 index 000000000..05651a4e5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootRollAction.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../Action.h" +#include "QueryItemUsageAction.h" +#include "LootAction.h" + +namespace ai +{ + class LootRollAction : public QueryItemUsageAction, public StoreLootAction { + public: + LootRollAction(PlayerbotAI* ai) : QueryItemUsageAction(ai, "loot roll"), StoreLootAction(ai) {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.cpp b/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.cpp new file mode 100644 index 000000000..df0629daa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.cpp @@ -0,0 +1,92 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootStrategyAction.h" +#include "../values/LootStrategyValue.h" +#include "LootAction.h" + +using namespace ai; + + +bool LootStrategyAction::Execute(Event event) +{ + string strategy = event.getParam(); + + LootObjectStack* lootItems = AI_VALUE(LootObjectStack*, "available loot"); + set& alwaysLootItems = AI_VALUE(set&, "always loot list"); + Value* lootStrategy = context->GetValue("loot strategy"); + + if (strategy == "?") + { + { + ostringstream out; + out << "Loot strategy: "; + out << lootStrategy->Get()->GetName(); + ai->TellMaster(out); + } + + { + ostringstream out; + out << "Always loot items: "; + + for (set::iterator i = alwaysLootItems.begin(); i != alwaysLootItems.end(); i++) + { + ItemPrototype const *proto = sItemStorage.LookupEntry(*i); + if (!proto) + { + continue; + } + + out << chat->formatItem(proto); + } + ai->TellMaster(out); + } + } + else + { + ItemIds items = chat->parseItems(strategy); + + if (items.size() == 0) + { + lootStrategy->Set(LootStrategyValue::instance(strategy)); + ostringstream out; + out << "Loot strategy set to " << lootStrategy->Get()->GetName(); + ai->TellMaster(out); + return true; + } + + bool remove = strategy.size() > 1 && strategy.substr(0, 1) == "-"; + bool query = strategy.size() > 1 && strategy.substr(0, 1) == "?"; + for (ItemIds::iterator i = items.begin(); i != items.end(); i++) + { + uint32 itemid = *i; + if (query) + { + ItemPrototype const *proto = sObjectMgr.GetItemPrototype(itemid); + if (proto) + { + ostringstream out; + out << (StoreLootAction::IsLootAllowed(itemid, ai) ? "|cFF000000Will loot " : "|c00FF0000Won't loot ") << ChatHelper::formatItem(proto); + ai->TellMaster(out.str()); + } + } + else if (remove) + { + set::iterator j = alwaysLootItems.find(itemid); + if (j != alwaysLootItems.end()) + { + alwaysLootItems.erase(j); + } + + ai->TellMaster("Item(s) removed from always loot list"); + } + else + { + alwaysLootItems.insert(itemid); + ai->TellMaster("Item(s) added to always loot list"); + } + } + } + + return true; +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.h b/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.h new file mode 100644 index 000000000..67a254b42 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/LootStrategyAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "../../LootObjectStack.h" + +namespace ai +{ + class LootStrategyAction : public Action { + public: + LootStrategyAction(PlayerbotAI* ai) : Action(ai, "ll") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/MailAction.cpp b/src/modules/Bots/playerbot/strategy/actions/MailAction.cpp new file mode 100644 index 000000000..056239e50 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/MailAction.cpp @@ -0,0 +1,269 @@ +#include "botpch.h" +#include "Mail.h" +#include "../../playerbot.h" +#include "MailAction.h" +#include "../../PlayerbotAIConfig.h" +#include "../../Helpers.h" + +using namespace ai; + +map MailAction::processors; + +class TellMailProcessor : public MailProcessor +{ +public: + virtual bool Before(PlayerbotAI* ai) + { + ai->TellMaster("=== Mailbox ==="); + return true; + } + virtual bool Process(int index, Mail* mail, PlayerbotAI* ai) + { + Player* bot = ai->GetBot(); + time_t cur_time = time(0); + int days = (cur_time - mail->deliver_time) / 3600 / 24; + ostringstream out; + out << "#" << (index+1) << " "; + if (!mail->money && !mail->has_items) + { + out << "|cffffffff" << mail->subject; + } + + if (mail->money) + { + out << "|cffffff00" << ChatHelper::formatMoney(mail->money); + if (!mail->subject.empty()) out << " |cffa0a0a0(" << mail->subject << ")"; + } + + if (mail->has_items) + { + for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i) + { + Item* item = bot->GetMItem(i->item_guid); + int count = item ? item->GetCount() : 1; + ItemPrototype const *proto = sObjectMgr.GetItemPrototype(i->item_template); + if (proto) + { + out << ChatHelper::formatItem(proto, count); + if (!mail->subject.empty()) out << " |cffa0a0a0(" << mail->subject << ")"; + } + } + } + out << ", |cff00ff00" << days << " day(s)"; + ai->TellMaster(out.str()); + return true; + } + + static TellMailProcessor instance; +}; + +class TakeMailProcessor : public MailProcessor +{ +public: + virtual bool Process(int index, Mail* mail, PlayerbotAI* ai) + { + Player* bot = ai->GetBot(); + if (!CheckBagSpace(bot)) + { + ai->TellMaster("Not enough bag space"); + return false; + } + + ObjectGuid mailbox = FindMailbox(ai); + if (mail->money) + { + ostringstream out; + out << mail->subject << ", |cffffff00" << ChatHelper::formatMoney(mail->money) << "|cff00ff00 processed"; + ai->TellMaster(out.str()); + + WorldPacket packet; + packet << mailbox; + packet << mail->messageID; + bot->GetSession()->HandleMailTakeMoney(packet); + RemoveMail(bot, mail->messageID, mailbox); + } + else if (!mail->items.empty()) + { + for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i) + { + ItemPrototype const *proto = sObjectMgr.GetItemPrototype(i->item_template); + if (proto) + { + ostringstream out; + out << mail->subject << ", " << ChatHelper::formatItem(proto) << "|cff00ff00 processed"; + ai->TellMaster(out.str()); + } + } + + WorldPacket packet; + packet << mailbox; + packet << mail->messageID; + bot->GetSession()->HandleMailTakeItem(packet); + RemoveMail(bot, mail->messageID, mailbox); + } + return true; + } + + static TakeMailProcessor instance; + +private: + bool CheckBagSpace(Player* bot) + { + uint32 totalused = 0; + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + totalused++; + } + } + uint32 totalfree = 16 - totalused; + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + if (const Bag* const pBag = (Bag*) bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) + { + ItemPrototype const* pBagProto = pBag->GetProto(); + if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER) + { + totalfree += pBag->GetFreeSlots(); + } + } + + } + + return totalfree >= 2; + } +}; + +class DeleteMailProcessor : public MailProcessor +{ +public: + virtual bool Process(int index, Mail* mail, PlayerbotAI* ai) + { + ostringstream out; + out << "|cffffffff" << mail->subject << "|cffff0000 deleted"; + RemoveMail(ai->GetBot(), mail->messageID, FindMailbox(ai)); + ai->TellMaster(out.str()); + return true; + } + + static DeleteMailProcessor instance; +}; + +class ReadMailProcessor : public MailProcessor +{ +public: + virtual bool Process(int index, Mail* mail, PlayerbotAI* ai) + { + ostringstream out, body; + out << "|cffffffff" << mail->subject; + ai->TellMaster(out.str()); + if (!mail->body.empty()) + { + body << "\n" << mail->body; + ai->TellMaster(body.str()); + } + return true; + } + static ReadMailProcessor instance; +}; + +TellMailProcessor TellMailProcessor::instance; +TakeMailProcessor TakeMailProcessor::instance; +DeleteMailProcessor DeleteMailProcessor::instance; +ReadMailProcessor ReadMailProcessor::instance; + +bool MailAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + if (!MailProcessor::FindMailbox(ai)) + { + ai->TellMaster("There is no mailbox nearby"); + return false; + } + + if (processors.empty()) + { + processors["?"] = &TellMailProcessor::instance; + processors["take"] = &TakeMailProcessor::instance; + processors["delete"] = &DeleteMailProcessor::instance; + processors["read"] = &ReadMailProcessor::instance; + } + + string text = event.getParam(); + if (text.empty()) + { + ai->TellMaster("whisper 'mail ?' to query mailbox, 'mail take/delete/read filter' to take/delete/read mails by filter"); + return false; + } + + vector ss = split(text, ' '); + string action = ss[0]; + string filter = ss.size() > 1 ? ss[1] : ""; + MailProcessor* processor = processors[action]; + if (!processor) + { + ostringstream out; out << action << ": I don't know how to do that"; + ai->TellMaster(out.str()); + return false; + } + + if (!processor->Before(ai)) + { + return false; + } + + vector mailList; + time_t cur_time = time(0); + for (PlayerMails::iterator itr = bot->GetMailBegin(); itr != bot->GetMailEnd(); ++itr) + { + if ((*itr)->state == MAIL_STATE_DELETED || cur_time < (*itr)->deliver_time) + { + continue; + } + + Mail *mail = *itr; + mailList.push_back(mail); + } + + map filtered = filterList(mailList, filter); + for (map::iterator i = filtered.begin(); i != filtered.end(); ++i) + { + if (!processor->Process(i->first, i->second, ai)) + { + break; + } + } + + return processor->After(ai); +} + +void MailProcessor::RemoveMail(Player* bot, uint32 id, ObjectGuid mailbox) +{ + WorldPacket packet; + packet << mailbox; + packet << id; + bot->GetSession()->HandleMailDelete(packet); +} + +ObjectGuid MailProcessor::FindMailbox(PlayerbotAI* ai) +{ + list gos = *ai->GetAiObjectContext()->GetValue >("nearest game objects"); + ObjectGuid mailbox; + for (list::iterator i = gos.begin(); i != gos.end(); ++i) + { + GameObject* go = ai->GetGameObject(*i); + if (go && go->GetGoType() == GAMEOBJECT_TYPE_MAILBOX) + { + mailbox = go->GetObjectGuid(); + break; + } + } + + return mailbox; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/MailAction.h b/src/modules/Bots/playerbot/strategy/actions/MailAction.h new file mode 100644 index 000000000..095518c88 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/MailAction.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + + class MailProcessor + { + public: + virtual bool Before(PlayerbotAI* ai) { return true; } + virtual bool Process(int index, Mail* mail, PlayerbotAI* ai) = 0; + virtual bool After(PlayerbotAI* ai) { return true; } + + public: + static ObjectGuid FindMailbox(PlayerbotAI* ai); + + protected: + void RemoveMail(Player* bot, uint32 id, ObjectGuid mailbox); + }; + + + class MailAction : public InventoryAction + { + public: + MailAction(PlayerbotAI* ai) : InventoryAction(ai, "mail") {} + + virtual bool Execute(Event event); + + private: + bool CheckMailbox(); + + private: + static map processors; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp b/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp new file mode 100644 index 000000000..8a5a46a2b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/MovementActions.cpp @@ -0,0 +1,460 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../values/LastMovementValue.h" +#include "MovementActions.h" +#include "MotionMaster.h" +#include "MovementGenerator.h" +#include "../../FleeManager.h" +#include "../../LootObjectStack.h" +#include "../../PlayerbotAIConfig.h" +#include "MotionGenerators/TargetedMovementGenerator.h" + +using namespace ai; + +bool MovementAction::MoveNear(uint32 mapId, float x, float y, float z, float distance) +{ + float angle = GetFollowAngle(); + return MoveTo(mapId, x + cos(angle) * distance, y + sin(angle) * distance, z); +} + +bool MovementAction::MoveNear(WorldObject* target, float distance) +{ + if (!target) + { + return false; + } + + distance += target->GetObjectBoundingRadius(); + + float followAngle = GetFollowAngle(); + for (float angle = followAngle; angle <= followAngle + 2 * M_PI; angle += M_PI / 4) + { + float x = target->GetPositionX() + cos(angle) * distance, + y = target->GetPositionY()+ sin(angle) * distance, + z = target->GetPositionZ(); + if (!bot->IsWithinLOS(x, y, z)) + { + continue; + } + bool moved = MoveTo(target->GetMapId(), x, y, z); + if (moved) + { + return true; + } + } + return false; +} + +bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z) +{ + if (!bot->IsUnderWater()) + { + bot->UpdateGroundPositionZ(x, y, z); + } + + if (!IsMovingAllowed(mapId, x, y, z)) + { + return false; + } + + float distance = bot->GetDistance2d(x, y); + if (distance > sPlayerbotAIConfig.contactDistance) + { + WaitForReach(distance); + + if (bot->IsSitState()) + { + bot->SetStandState(UNIT_STAND_STATE_STAND); + } + + if (bot->IsNonMeleeSpellCasted(true)) + { + bot->CastStop(); + ai->InterruptSpell(); + } + + bool generatePath = !bot->IsFlying() && !bot->IsUnderWater(); + MotionMaster &mm = *bot->GetMotionMaster(); + mm.MovePoint(mapId, x, y, z, generatePath); + + AI_VALUE(LastMovement&, "last movement").Set(x, y, z, bot->GetOrientation()); + return true; + } + + return false; +} + +bool MovementAction::MoveTo(Unit* target, float distance) +{ + if (!IsMovingAllowed(target)) + { + return false; + } + + float bx = bot->GetPositionX(); + float by = bot->GetPositionY(); + + float tz = target->GetPositionZ(); + + float distanceToTarget = bot->GetDistance2d(target); + float angle = bot->GetAngle(target); + float needToGo = distanceToTarget - distance; + + float maxDistance = sPlayerbotAIConfig.spellDistance; + if (needToGo > 0 && needToGo > maxDistance) + { + needToGo = maxDistance; + } + else if (needToGo < 0 && needToGo < -maxDistance) + { + needToGo = -maxDistance; + } + + float dx = cos(angle) * needToGo + bx; + float dy = sin(angle) * needToGo + by; + + return MoveTo(target->GetMapId(), dx, dy, tz); +} + +float MovementAction::GetFollowAngle() +{ + Player* master = GetMaster(); + Group* group = master ? master->GetGroup() : bot->GetGroup(); + if (!group) + { + return 0.0f; + } + + int index = 1; + for (GroupReference *ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if( ref->getSource() == master) + { + continue; + } + + if( ref->getSource() == bot) + { + return 2 * M_PI / (group->GetMembersCount() -1) * index; + } + + index++; + } + return 0; +} + +bool MovementAction::IsMovingAllowed(Unit* target) +{ + if (!target) + { + return false; + } + + if (bot->GetMapId() != target->GetMapId()) + { + return false; + } + + float distance = bot->GetDistance(target); + if (distance > sPlayerbotAIConfig.reactDistance) + { + return false; + } + + return IsMovingAllowed(); +} + +bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z) +{ + float distance = bot->GetDistance(x, y, z); + if (distance > sPlayerbotAIConfig.reactDistance) + { + return false; + } + + return IsMovingAllowed(); +} + +bool MovementAction::IsMovingAllowed() +{ + if (bot->IsFrozen() || bot->IsPolymorphed() || + (bot->IsDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || + bot->IsBeingTeleported() || + bot->IsInRoots() || + bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() || + bot->HasAuraType(SPELL_AURA_MOD_STUN) || bot->IsFlying()) + return false; + + MotionMaster &mm = *bot->GetMotionMaster(); + return mm.GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; +} + +bool MovementAction::Follow(Unit* target, float distance) +{ + return Follow(target, distance, GetFollowAngle()); +} + +bool MovementAction::Follow(Unit* target, float distance, float angle) +{ + MotionMaster &mm = *bot->GetMotionMaster(); + + if (!target) + { + return false; + } + + if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= sPlayerbotAIConfig.sightDistance && + abs(bot->GetPositionZ() - target->GetPositionZ()) >= sPlayerbotAIConfig.spellDistance) + { + mm.Clear(); + float x = bot->GetPositionX(), y = bot->GetPositionY(), z = target->GetPositionZ(); + if (target->GetMapId() && bot->GetMapId() != target->GetMapId()) + { + bot->TeleportTo(target->GetMapId(), x, y, z, bot->GetOrientation()); + } + else + { + bot->Relocate(x, y, z, bot->GetOrientation()); + } + AI_VALUE(LastMovement&, "last movement").Set(target); + return true; + } + + if (!IsMovingAllowed(target)) + { + return false; + } + + if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(list, "possible targets").empty()) + { + distance += angle; + } + + if (bot->GetDistance2d(target) <= sPlayerbotAIConfig.followDistance) + { + return false; + } + + if (bot->IsSitState()) + { + bot->SetStandState(UNIT_STAND_STATE_STAND); + } + + if (bot->IsNonMeleeSpellCasted(true)) + { + bot->CastStop(); + ai->InterruptSpell(); + } + + AI_VALUE(LastMovement&, "last movement").Set(target); + + if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) + { + Unit *currentTarget = static_cast const*>(bot->GetMotionMaster()->GetCurrent())->GetTarget(); + if (currentTarget && currentTarget->GetObjectGuid() == target->GetObjectGuid()) return false; + } + + mm.MoveFollow(target, distance, angle); + return true; +} + +void MovementAction::WaitForReach(float distance) +{ + float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay; + + if (delay > sPlayerbotAIConfig.maxWaitForMove) + { + delay = sPlayerbotAIConfig.maxWaitForMove; + } + + Unit* target = *ai->GetAiObjectContext()->GetValue("current target"); + Unit* player = *ai->GetAiObjectContext()->GetValue("enemy player target"); + if ((player || target) && delay > sPlayerbotAIConfig.globalCoolDown) + { + delay = sPlayerbotAIConfig.globalCoolDown; + } + + ai->SetNextCheckDelay((uint32)delay); +} + +bool MovementAction::Flee(Unit *target) +{ + Player* master = GetMaster(); + if (!target) + { + target = master; + } + + if (!target) + { + return false; + } + + if (!sPlayerbotAIConfig.fleeingEnabled) + { + return false; + } + + if (!IsMovingAllowed()) + { + return false; + } + + FleeManager manager(bot, sPlayerbotAIConfig.fleeDistance, bot->GetAngle(target) + M_PI); + + float rx, ry, rz; + if (!manager.CalculateDestination(&rx, &ry, &rz)) + { + return false; + } + + return MoveTo(target->GetMapId(), rx, ry, rz); +} + +bool FleeAction::Execute(Event event) +{ + return Flee(AI_VALUE(Unit*, "current target")); +} + +bool FleeAction::isUseful() +{ + return AI_VALUE(uint8, "attacker count") > 0 && + AI_VALUE2(float, "distance", "current target") <= sPlayerbotAIConfig.shootDistance; +} + +bool RunAwayAction::Execute(Event event) +{ + return Flee(AI_VALUE(Unit*, "master target")); +} + +bool MoveRandomAction::Execute(Event event) +{ + vector locs; + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + WorldObject* target = ai->GetUnit(*i); + if (target && bot->GetDistance(target) > sPlayerbotAIConfig.tooCloseDistance) + { + WorldLocation loc; + target->GetPosition(loc); + locs.push_back(loc); + } + } + + list players = AI_VALUE(list, "nearest friendly players"); + for (list::iterator i = players.begin(); i != players.end(); i++) + { + WorldObject* target = ai->GetUnit(*i); + if (target && bot->GetDistance(target) > sPlayerbotAIConfig.tooCloseDistance) + { + WorldLocation loc; + target->GetPosition(loc); + locs.push_back(loc); + } + } + + list gos = AI_VALUE(list, "nearest game objects"); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + WorldObject* target = ai->GetGameObject(*i); + + if (target && bot->GetDistance(target) > sPlayerbotAIConfig.tooCloseDistance) + { + WorldLocation loc; + target->GetPosition(loc); + locs.push_back(loc); + } + } + + float distance = sPlayerbotAIConfig.grindDistance; + Map* map = bot->GetMap(); + for (int i = 0; i < 10; ++i) + { + float x = bot->GetPositionX(); + float y = bot->GetPositionY(); + float z = bot->GetPositionZ(); + x += urand(0, distance) - distance / 2; + y += urand(0, distance) - distance / 2; + bot->UpdateGroundPositionZ(x, y, z); + + const TerrainInfo* terrain = map->GetTerrain(); + if (terrain->IsUnderWater(x, y, z) || + terrain->IsInWater(x, y, z)) + continue; + + float ground = map->GetHeight(bot->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + continue; + } + + z = 0.05f + ground; + if (abs(z - bot->GetPositionZ()) > sPlayerbotAIConfig.tooCloseDistance) + { + continue; + } + + WorldLocation loc(bot->GetMapId(), x, y, z); + locs.push_back(loc); + } + + if (locs.empty()) + { + return false; + } + + WorldLocation target = locs[urand(0, locs.size() - 1)]; + return MoveNear(target.mapid, target.coord_x, target.coord_y, target.coord_z); +} + +bool MoveToLootAction::Execute(Event event) +{ + LootObject loot = AI_VALUE(LootObject, "loot target"); + if (!loot.IsLootPossible(bot)) + { + return false; + } + + WorldObject *wo = loot.GetWorldObject(bot); + return MoveNear(wo); +} + +bool MoveOutOfEnemyContactAction::Execute(Event event) +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) + { + return false; + } + + return MoveNear(target, sPlayerbotAIConfig.meleeDistance); +} + +bool MoveOutOfEnemyContactAction::isUseful() +{ + return AI_VALUE2(float, "distance", "current target") < (sPlayerbotAIConfig.meleeDistance + sPlayerbotAIConfig.contactDistance); +} + +bool SetFacingTargetAction::Execute(Event event) +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) + { + return false; + } + + if (bot->IsTaxiFlying()) + { + return true; + } + + bot->SetFacingTo(bot->GetAngle(target)); + ai->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown); + return true; +} + +bool SetFacingTargetAction::isUseful() +{ + return !AI_VALUE2(bool, "facing", "current target"); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/MovementActions.h b/src/modules/Bots/playerbot/strategy/actions/MovementActions.h new file mode 100644 index 000000000..96d8d5182 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/MovementActions.h @@ -0,0 +1,92 @@ +#pragma once + +#include "../Action.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class MovementAction : public Action { + public: + MovementAction(PlayerbotAI* ai, string name) : Action(ai, name) + { + bot = ai->GetBot(); + } + + protected: + bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.followDistance); + bool MoveTo(uint32 mapId, float x, float y, float z); + bool MoveTo(Unit* target, float distance = 0.0f); + bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig.followDistance); + float GetFollowAngle(); + bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance); + bool Follow(Unit* target, float distance, float angle); + void WaitForReach(float distance); + bool IsMovingAllowed(Unit* target); + bool IsMovingAllowed(uint32 mapId, float x, float y, float z); + bool IsMovingAllowed(); + bool Flee(Unit *target); + + protected: + Player* bot; + }; + + class FleeAction : public MovementAction + { + public: + FleeAction(PlayerbotAI* ai, float distance = sPlayerbotAIConfig.spellDistance) : MovementAction(ai, "flee") + { + this->distance = distance; + } + + virtual bool Execute(Event event); + virtual bool isUseful(); + + private: + float distance; + }; + + + class RunAwayAction : public MovementAction + { + public: + RunAwayAction(PlayerbotAI* ai) : MovementAction(ai, "runaway") {} + virtual bool Execute(Event event); + }; + + class MoveRandomAction : public MovementAction + { + public: + MoveRandomAction(PlayerbotAI* ai) : MovementAction(ai, "move random") {} + virtual bool Execute(Event event); + virtual bool isPossible() + { + return MovementAction::isPossible() && + AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.mediumHealth && + (!AI_VALUE2(uint8, "mana", "self target") || AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumMana); + } + }; + + class MoveToLootAction : public MovementAction + { + public: + MoveToLootAction(PlayerbotAI* ai) : MovementAction(ai, "move to loot") {} + virtual bool Execute(Event event); + }; + + class MoveOutOfEnemyContactAction : public MovementAction + { + public: + MoveOutOfEnemyContactAction(PlayerbotAI* ai) : MovementAction(ai, "move out of enemy contact") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; + + class SetFacingTargetAction : public MovementAction + { + public: + SetFacingTargetAction(PlayerbotAI* ai) : MovementAction(ai, "set facing") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp new file mode 100644 index 000000000..48be5c3be --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "NonCombatActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h new file mode 100644 index 000000000..ee17deb2c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../Action.h" +#include "UseItemAction.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class DrinkAction : public UseItemAction + { + public: + DrinkAction(PlayerbotAI* ai) : UseItemAction(ai, "drink") {} + + virtual bool Execute(Event event) + { + if (bot->IsInCombat()) + { + return false; + } + + return UseItemAction::Execute(event); + } + + virtual bool isUseful() + { + return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana; + } + }; + + class EatAction : public UseItemAction + { + public: + EatAction(PlayerbotAI* ai) : UseItemAction(ai, "food") {} + + virtual bool Execute(Event event) + { + if (bot->IsInCombat()) + { + return false; + } + + return UseItemAction::Execute(event); + } + + virtual bool isUseful() + { + return UseItemAction::isUseful() && AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig.lowHealth; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/OutfitAction.cpp b/src/modules/Bots/playerbot/strategy/actions/OutfitAction.cpp new file mode 100644 index 000000000..bf21ef676 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/OutfitAction.cpp @@ -0,0 +1,212 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "OutfitAction.h" +#include "../values/OutfitListValue.h" + +using namespace ai; + + +bool OutfitAction::Execute(Event event) +{ + string param = event.getParam(); + + if (param == "?") + { + List(); + ai->TellMaster("outfit +[item] to add items"); + ai->TellMaster("outfit -[item] to remove items"); + ai->TellMaster("outfit equip to equip items"); + } + else + { + string name = parseName(param); + ItemIds items = parseItems(param); + if (!name.empty()) + { + Save(name, items); + ostringstream out; + out << "Setting outfit " << name << " as " << param; + ai->TellMaster(out); + return true; + } + + items = chat->parseItems(param); + + int space = param.find(" "); + if (space == -1) + { + return false; + } + + name = param.substr(0, space); + ItemIds outfit = Find(name); + string command = param.substr(space + 1); + if (command == "equip") + { + ostringstream out; + out << "Equipping outfit " << name; + ai->TellMaster(out); + EquipItems(outfit); + return true; + } + else if (command == "reset") + { + ostringstream out; + out << "Resetting outfit " << name; + ai->TellMaster(out); + Save(name, ItemIds()); + return true; + } + else if (command == "update") + { + ostringstream out; + out << "Updating to current items outfit " << name; + ai->TellMaster(out); + Update(name); + return true; + } + + bool remove = param.size() > 1 && param.substr(space + 1, 1) == "-"; + for (ItemIds::iterator i = items.begin(); i != items.end(); i++) + { + uint32 itemid = *i; + ItemPrototype const *proto = sItemStorage.LookupEntry(*i); + ostringstream out; + out << chat->formatItem(proto); + if (remove) + { + set::iterator j = outfit.find(itemid); + if (j != outfit.end()) + { + outfit.erase(j); + } + + out << " removed from "; + } + else + { + outfit.insert(itemid); + out << " added to "; + } + out << name; + ai->TellMaster(out.str()); + } + Save(name, outfit); + } + + return true; +} + +void OutfitAction::Save(string& name, ItemIds items) +{ + list& outfits = AI_VALUE(list&, "outfit list"); + for (list::iterator i = outfits.begin(); i != outfits.end(); ++i) + { + string outfit = *i; + if (name == parseName(outfit)) + { + outfits.erase(i); + break; + } + } + + if (items.empty()) return; + + ostringstream out; + out << name << "="; + bool first = true; + for (ItemIds::iterator i = items.begin(); i != items.end(); i++) + { + if (first) first = false; else out << ","; + { + out << *i; + } + } + outfits.push_back(out.str()); +} + +ItemIds OutfitAction::Find(string& name) +{ + list& outfits = AI_VALUE(list&, "outfit list"); + for (list::iterator i = outfits.begin(); i != outfits.end(); ++i) + { + string outfit = *i; + if (name == parseName(outfit)) + { + return parseItems(outfit); + } + } + return set(); +} + + +void OutfitAction::List() +{ + list& outfits = AI_VALUE(list&, "outfit list"); + for (list::iterator i = outfits.begin(); i != outfits.end(); ++i) + { + string outfit = *i; + string name = parseName(outfit); + ItemIds items = parseItems(outfit); + + ostringstream out; + out << name << ": "; + for (ItemIds::iterator j = items.begin(); j != items.end(); ++j) + { + ItemPrototype const *proto = sItemStorage.LookupEntry(*j); + if (proto) + { + out << chat->formatItem(proto) << " "; + } + } + ai->TellMaster(out); + } +} + +string OutfitAction::parseName(string outfit) +{ + int pos = outfit.find("="); + if (pos == -1) return ""; + { + return outfit.substr(0, pos); + } +} + +ItemIds OutfitAction::parseItems(string text) +{ + ItemIds itemIds; + + uint8 pos = text.find("=") + 1; + while (pos < text.size()) + { + int endPos = text.find(',', pos); + if (endPos == -1) + { + endPos = text.size(); + } + + string idC = text.substr(pos, endPos - pos); + uint32 id = atol(idC.c_str()); + pos = endPos + 1; + if (id) + { + itemIds.insert(id); + } + } + + return itemIds; +} + +void OutfitAction::Update(string& name) +{ + ListItemsVisitor visitor; + IterateItems(&visitor, ITERATE_ITEMS_IN_EQUIP); + + ItemIds items; + for (map::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i) + { + items.insert(i->first); + } + + Save(name, items); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/OutfitAction.h b/src/modules/Bots/playerbot/strategy/actions/OutfitAction.h new file mode 100644 index 000000000..1d74c4427 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/OutfitAction.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Action.h" +#include "../../LootObjectStack.h" +#include "EquipAction.h" +#include "InventoryAction.h" + +namespace ai +{ + class OutfitAction : public EquipAction { + public: + OutfitAction(PlayerbotAI* ai) : EquipAction(ai, "outfit") {} + virtual bool Execute(Event event); + + private: + string parseName(string outfit); + ItemIds parseItems(string outfit); + + void List(); + ItemIds Find(string& name); + void Save(string& name, ItemIds outfit); + void Update(string& name); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/PassLeadershipToMasterAction.h b/src/modules/Bots/playerbot/strategy/actions/PassLeadershipToMasterAction.h new file mode 100644 index 000000000..15e631c54 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/PassLeadershipToMasterAction.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class PassLeadershipToMasterAction : public Action { + public: + PassLeadershipToMasterAction(PlayerbotAI* ai) : Action(ai, "leader") {} + + virtual bool Execute(Event event) + { + Player* master = GetMaster(); + if (master && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetObjectGuid())) + { + WorldPacket p(SMSG_GROUP_SET_LEADER, 8); + p << master->GetObjectGuid(); + bot->GetSession()->HandleGroupSetLeaderOpcode(p); + return true; + } + + return false; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/PositionAction.cpp b/src/modules/Bots/playerbot/strategy/actions/PositionAction.cpp new file mode 100644 index 000000000..36b4dab7f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/PositionAction.cpp @@ -0,0 +1,113 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../values/PositionValue.h" +#include "PositionAction.h" + +using namespace ai; + +void TellPosition(PlayerbotAI* ai, const string& name, ai::Position pos) +{ + ostringstream out; out << "Position " << name; + if (pos.isSet()) + { + float x = pos.x, y = pos.y; + Map2ZoneCoordinates(x, y, ai->GetBot()->GetZoneId()); + out << " is set to " << x << "," << y; + } + else + { + out << " is not set"; + } + ai->TellMaster(out); +} + +bool PositionAction::Execute(Event event) +{ + string param = event.getParam(); + if (param.empty()) + { + return false; + } + + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ai::PositionMap& posMap = context->GetValue("position")->Get(); + if (param == "?") + { + for (ai::PositionMap::iterator i = posMap.begin(); i != posMap.end(); ++i) + { + if (i->second.isSet()) + { + TellPosition(ai, i->first, i->second); + } + } + return true; + } + + vector params = split(param, ' '); + if (params.size() != 2) + { + ai->TellMaster("Whisper position ?/set/reset"); + return false; + } + + string name = params[0]; + string action = params[1]; + ai::Position pos = posMap[name]; + if (action == "?") + { + TellPosition(ai, name, pos); + return true; + } + + vector coords = split(action, ','); + if (coords.size() == 3) + { + pos.Set(atoi(coords[0].c_str()), atoi(coords[1].c_str()), atoi(coords[2].c_str())); + posMap[name] = pos; + + ostringstream out; out << "Position " << name << " is set"; + ai->TellMaster(out); + return true; + } + + if (action == "set") + { + pos.Set( bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + posMap[name] = pos; + + ostringstream out; out << "Position " << name << " is set"; + ai->TellMaster(out); + return true; + } + + if (action == "reset") + { + pos.Reset(); + posMap[name] = pos; + + ostringstream out; out << "Position " << name << " is reset"; + ai->TellMaster(out); + return true; + } + + return false; +} + +bool MoveToPositionAction::Execute(Event event) +{ + ai::Position pos = context->GetValue("position")->Get()[qualifier]; + if (!pos.isSet()) + { + ostringstream out; out << "Position " << qualifier << " is not set"; + ai->TellMaster(out); + return false; + } + + return MoveTo(bot->GetMapId(), pos.x, pos.y, pos.z); +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/PositionAction.h b/src/modules/Bots/playerbot/strategy/actions/PositionAction.h new file mode 100644 index 000000000..2b954fc77 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/PositionAction.h @@ -0,0 +1,36 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class PositionAction : public Action + { + public: + PositionAction(PlayerbotAI* ai) : Action(ai, "position") + {} + + virtual bool Execute(Event event); + }; + + class MoveToPositionAction : public MovementAction + { + public: + MoveToPositionAction(PlayerbotAI* ai, string qualifier) : MovementAction(ai, "move to position"), qualifier(qualifier) + {} + + virtual bool Execute(Event event); + + protected: + string qualifier; + }; + + class GuardAction : public MoveToPositionAction + { + public: + GuardAction(PlayerbotAI* ai) : MoveToPositionAction(ai, "guard") + {} + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.cpp b/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.cpp new file mode 100644 index 000000000..7e38ea69c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.cpp @@ -0,0 +1,217 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "QueryItemUsageAction.h" +#include "../../../ahbot/AhBot.h" +#include "../values/ItemUsageValue.h" +#include "../../RandomPlayerbotMgr.h" + + +using namespace ai; + + +bool QueryItemUsageAction::Execute(Event event) +{ + WorldPacket& data = event.getPacket(); + if (!data.empty()) + { + data.rpos(0); + + ObjectGuid guid; + data >> guid; + if (guid != bot->GetObjectGuid()) + { + return false; + } + + uint32 received, created, isShowChatMessage, notUsed, itemId, + suffixFactor, itemRandomPropertyId, count, invCount; + uint8 bagSlot; + + data >> received; // 0=looted, 1=from npc + data >> created; // 0=received, 1=created + data >> isShowChatMessage; // IsShowChatMessage + data >> bagSlot; + // item slot, but when added to stack: 0xFFFFFFFF + data >> notUsed; + data >> itemId; + data >> suffixFactor; + data >> itemRandomPropertyId; + data >> count; + // data >> invCount; // [-ZERO] count of items in inventory + + ItemPrototype const *item = sItemStorage.LookupEntry(itemId); + if (!item) + { + return false; + } + + ai->TellMaster(QueryItem(item, count, GetCount(item))); + return true; + } + + string text = event.getParam(); + ItemIds items = chat->parseItems(text); + for (ItemIds::iterator i = items.begin(); i != items.end(); i++) + { + ItemPrototype const *item = sItemStorage.LookupEntry(*i); + if (!item) continue; + + ai->TellMaster(QueryItem(item, 0, GetCount(item))); + } + return true; +} + +uint32 QueryItemUsageAction::GetCount(ItemPrototype const *item) +{ + uint32 total = 0; + list items = InventoryAction::parseItems(item->Name1); + if (!items.empty()) + { + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + total += (*i)->GetCount(); + } + } + return total; +} + +string QueryItemUsageAction::QueryItem(ItemPrototype const *item, uint32 count, uint32 total) +{ + ostringstream out; + string usage = QueryItemUsage(item); + string quest = QueryQuestItem(item->ItemId); + string price = QueryItemPrice(item); + if (usage.empty()) + { + usage = (quest.empty() ? "Useless" : "Quest"); + } + + out << chat->formatItem(item, count, total) << ": " << usage; + if (!quest.empty()) + { + out << ", " << quest; + } + if (!price.empty()) + { + out << ", " << price; + } + return out.str(); +} + +string QueryItemUsageAction::QueryItemUsage(ItemPrototype const *item) +{ + ostringstream out; out << item->ItemId; + ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str()); + switch (usage) + { + case ITEM_USAGE_EQUIP: + return "Equip"; + case ITEM_USAGE_REPLACE: + return "Equip (replace)"; + case ITEM_USAGE_SKILL: + return "Tradeskill"; + case ITEM_USAGE_USE: + return "Use"; + case ITEM_USAGE_GUILD_TASK: + return "Guild task"; + case ITEM_USAGE_DISENCHANT: + return "Disenchant"; + } + + return ""; +} + +string QueryItemUsageAction::QueryItemPrice(ItemPrototype const *item) +{ + if (!sRandomPlayerbotMgr.IsRandomBot(bot)) + { + return ""; + } + + if (item->Bonding == BIND_WHEN_PICKED_UP) + { + return ""; + } + + ostringstream msg; + list items = InventoryAction::parseItems(item->Name1); + int32 sellPrice = 0; + if (!items.empty()) + { + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* sell = *i; + sellPrice = sell->GetCount() * auctionbot.GetSellPrice(sell->GetProto()) * sRandomPlayerbotMgr.GetSellMultiplier(bot); + msg << "Sell: " << chat->formatMoney(sellPrice); + break; + } + } + + ostringstream out; out << item->ItemId; + ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str()); + if (usage == ITEM_USAGE_NONE) + { + return msg.str(); + } + + int32 buyPrice = auctionbot.GetBuyPrice(item) * sRandomPlayerbotMgr.GetBuyMultiplier(bot); + if (buyPrice) + { + if (sellPrice) msg << " "; + { + msg << "Buy: " << chat->formatMoney(buyPrice); + } + } + + return msg.str(); +} + +string QueryItemUsageAction::QueryQuestItem(uint32 itemId) +{ + Player *bot = ai->GetBot(); + QuestStatusMap& questMap = bot->getQuestStatusMap(); + for (QuestStatusMap::const_iterator i = questMap.begin(); i != questMap.end(); i++) + { + const Quest *questTemplate = sObjectMgr.GetQuestTemplate( i->first ); + if( !questTemplate ) + { + continue; + } + + uint32 questId = questTemplate->GetQuestId(); + QuestStatus status = bot->GetQuestStatus(questId); + if (status == QUEST_STATUS_INCOMPLETE || (status == QUEST_STATE_COMPLETE && !bot->GetQuestRewardStatus(questId))) + { + QuestStatusData const& questStatus = i->second; + string usage = QueryQuestItem(itemId, questTemplate, &questStatus); + if (!usage.empty()) return usage; + } + } + + return ""; +} + + +string QueryItemUsageAction::QueryQuestItem(uint32 itemId, const Quest *questTemplate, const QuestStatusData *questStatus) +{ + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + if (questTemplate->ReqItemId[i] != itemId) + { + continue; + } + + int required = questTemplate->ReqItemCount[i]; + int available = questStatus->m_itemcount[i]; + + if (!required) + { + continue; + } + + return chat->formatQuestObjective(chat->formatQuest(questTemplate), available, required); + } + + return ""; +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.h b/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.h new file mode 100644 index 000000000..268f0814d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QueryItemUsageAction.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class QueryItemUsageAction : public InventoryAction { + public: + QueryItemUsageAction(PlayerbotAI* ai, string name = "query item usage") : InventoryAction(ai, name) {} + virtual bool Execute(Event event); + + protected: + uint32 GetCount(ItemPrototype const *item); + string QueryItem(ItemPrototype const *item, uint32 count, uint32 total); + string QueryItemUsage(ItemPrototype const *item); + string QueryItemPrice(ItemPrototype const *item); + string QueryQuestItem(uint32 itemId, const Quest *questTemplate, const QuestStatusData *questStatus); + string QueryQuestItem(uint32 itemId); + + private: + ostringstream out; + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.cpp b/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.cpp new file mode 100644 index 000000000..e952b65cf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.cpp @@ -0,0 +1,98 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "QueryQuestAction.h" + + +using namespace ai; + +void QueryQuestAction::TellObjective(string name, int available, int required) +{ + ai->TellMaster(chat->formatQuestObjective(name, available, required)); +} + + +bool QueryQuestAction::Execute(Event event) +{ + + Player *bot = ai->GetBot(); + string text = event.getParam(); + + PlayerbotChatHandler ch(bot); + uint32 questId = ch.extractQuestId(text); + if (!questId) + { + return false; + } + + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + if(questId != bot->GetQuestSlotQuestId(slot)) + { + continue; + } + + ostringstream out; + out << "--- " << chat->formatQuest(sObjectMgr.GetQuestTemplate(questId)) << " "; + if (bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE) + { + out << "|c0000FF00completed|r ---"; + ai->TellMaster(out); + } + else + { + out << "|c00FF0000not completed|r ---"; + ai->TellMaster(out); + TellObjectives(questId); + } + + return true; + } + + return false; +} + +void QueryQuestAction::TellObjectives(uint32 questId) +{ + Quest const* questTemplate = sObjectMgr.GetQuestTemplate(questId); + QuestStatusData questStatus = bot->getQuestStatusMap()[questId]; + + for (int i = 0; i < QUEST_OBJECTIVES_COUNT; i++) + { + if (!questTemplate->ObjectiveText[i].empty()) + { + ai->TellMaster(questTemplate->ObjectiveText[i]); + } + + if (questTemplate->ReqItemId[i]) + { + int required = questTemplate->ReqItemCount[i]; + int available = questStatus.m_itemcount[i]; + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(questTemplate->ReqItemId[i]); + TellObjective(chat->formatItem(proto), available, required); + } + + if (questTemplate->ReqCreatureOrGOId[i]) + { + int required = questTemplate->ReqCreatureOrGOCount[i]; + int available = questStatus.m_creatureOrGOcount[i]; + + if (questTemplate->ReqCreatureOrGOId[i] < 0) + { + GameObjectInfo const* info = sObjectMgr.GetGameObjectInfo(-questTemplate->ReqCreatureOrGOId[i]); + if (info) + { + TellObjective(info->name, available, required); + } + } + else + { + + CreatureInfo const* info = sObjectMgr.GetCreatureTemplate(questTemplate->ReqCreatureOrGOId[i]); + if (info) + { + TellObjective(info->Name, available, required); + } + } + } + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.h b/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.h new file mode 100644 index 000000000..182229355 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QueryQuestAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class QueryQuestAction : public Action { + public: + QueryQuestAction(PlayerbotAI* ai) : Action(ai, "query quest") {} + virtual bool Execute(Event event); + + private: + void TellObjectives(uint32 questId); + void TellObjective(string name, int available, int required); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/QuestAction.cpp b/src/modules/Bots/playerbot/strategy/actions/QuestAction.cpp new file mode 100644 index 000000000..b456a2739 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QuestAction.cpp @@ -0,0 +1,161 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "QuestAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool QuestAction::Execute(Event event) +{ + ObjectGuid guid = event.getObject(); + + Player* master = GetMaster(); + if (!master) + { + return false; + } + + if (!guid) + { + guid = master->GetSelectionGuid(); + } + + if (!guid) + { + return false; + } + + return ProcessQuests(guid); +} + +bool QuestAction::ProcessQuests(ObjectGuid questGiver) +{ + GameObject *gameObject = ai->GetGameObject(questGiver); + if (gameObject && gameObject->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER) + { + return ProcessQuests(gameObject); + } + + Creature* creature = ai->GetCreature(questGiver); + if (creature) + { + return ProcessQuests(creature); + } + + return false; +} + +bool QuestAction::ProcessQuests(WorldObject* questGiver) +{ + ObjectGuid guid = questGiver->GetObjectGuid(); + + if (bot->GetDistance(questGiver) > INTERACTION_DISTANCE) + { + ai->TellMaster("Cannot talk to quest giver"); + return false; + } + + if (!bot->IsInFront(questGiver, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT)) + { + bot->SetFacingTo(bot->GetAngle(questGiver)); + } + + bot->SetSelectionGuid(guid); + bot->PrepareQuestMenu(guid); + QuestMenu& questMenu = bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 i = 0; i < questMenu.MenuItemCount(); ++i) + { + QuestMenuItem const& menuItem = questMenu.GetItem(i); + uint32 questID = menuItem.m_qId; + Quest const* quest = sObjectMgr.GetQuestTemplate(questID); + if (!quest) + { + continue; + } + + ProcessQuest(quest, questGiver); + } + + return true; +} + +bool QuestAction::AcceptQuest(Quest const* quest, uint64 questGiver) +{ + std::ostringstream out; + + uint32 questId = quest->GetQuestId(); + + if (bot->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE) + { + out << "Already completed"; + } + else if (! bot->CanTakeQuest(quest, false)) + { + if (! bot->SatisfyQuestStatus(quest, false)) + { + out << "Already on"; + } + else + { + out << "Can't take"; + } + } + else if (! bot->SatisfyQuestLog(false)) + { + out << "Quest log is full"; + } + else if (! bot->CanAddQuest(quest, false)) + { + out << "Bags are full"; + } + + else + { + WorldPacket p(CMSG_QUESTGIVER_ACCEPT_QUEST); + uint32 unk1 = 0; + p << questGiver << questId << unk1; + p.rpos(0); + bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(p); + + if (bot->GetQuestStatus(questId) != QUEST_STATUS_NONE && bot->GetQuestStatus(questId) != QUEST_STATUS_AVAILABLE) + { + out << "Accepted " << chat->formatQuest(quest); + ai->TellMaster(out); + return true; + } + } + + out << " " << chat->formatQuest(quest); + ai->TellMaster(out); + return false; +} + +bool QuestObjectiveCompletedAction::Execute(Event event) +{ + WorldPacket p(event.getPacket()); + p.rpos(0); + + uint32 entry, questId, available, required; + ObjectGuid guid; + p >> questId >> entry >> available >> required >> guid; + + if (entry & 0x80000000) + { + entry &= 0x7FFFFFFF; + GameObjectInfo const* info = sObjectMgr.GetGameObjectInfo(entry); + if (info) + { + ai->TellMaster(chat->formatQuestObjective(info->name, available, required)); + } + } + else + { + CreatureInfo const* info = sObjectMgr.GetCreatureTemplate(entry); + if (info) + { + ai->TellMaster(chat->formatQuestObjective(info->Name, available, required)); + } + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/QuestAction.h b/src/modules/Bots/playerbot/strategy/actions/QuestAction.h new file mode 100644 index 000000000..7708fc6d6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/QuestAction.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../Action.h" +#include "QuestDef.h" + +namespace ai +{ + class QuestAction : public Action + { + public: + QuestAction(PlayerbotAI* ai, string name) : Action(ai, name) {} + + public: + virtual bool Execute(Event event); + + protected: + virtual void ProcessQuest(Quest const* quest, WorldObject* questGiver) = 0; + + protected: + bool AcceptQuest(Quest const* quest, uint64 questGiver); + bool ProcessQuests(ObjectGuid questGiver); + bool ProcessQuests(WorldObject* questGiver); + }; + + class QuestObjectiveCompletedAction : public Action + { + public: + QuestObjectiveCompletedAction(PlayerbotAI* ai) : Action(ai, "quest objective completed") {} + + public: + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RandomBotUpdateAction.h b/src/modules/Bots/playerbot/strategy/actions/RandomBotUpdateAction.h new file mode 100644 index 000000000..d8a91b4ce --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RandomBotUpdateAction.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../RandomPlayerbotMgr.h" +#include "../Action.h" + +namespace ai +{ + class RandomBotUpdateAction : public Action + { + public: + RandomBotUpdateAction(PlayerbotAI* ai) : Action(ai, "random bot update") + {} + + virtual bool Execute(Event event) + { + if (!sRandomPlayerbotMgr.IsRandomBot(bot)) + { + return false; + } + + if (bot->GetGroup()) + { + return true; + } + + return sRandomPlayerbotMgr.ProcessBot(bot); + } + + virtual bool isUseful() + { + return AI_VALUE(bool, "random bot update"); + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReachTargetActions.h b/src/modules/Bots/playerbot/strategy/actions/ReachTargetActions.h new file mode 100644 index 000000000..ac8cb7bdd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReachTargetActions.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class ReachTargetAction : public MovementAction + { + public: + ReachTargetAction(PlayerbotAI* ai, string name, float distance) : MovementAction(ai, name) + { + this->distance = distance; + } + virtual bool Execute(Event event) + { + return MoveTo(AI_VALUE(Unit*, "current target"), distance); + } + virtual bool isUseful() + { + return AI_VALUE2(float, "distance", "current target") > (distance + sPlayerbotAIConfig.contactDistance); + } + virtual string GetTargetName() { return "current target"; } + + protected: + float distance; + }; + + class CastReachTargetSpellAction : public CastSpellAction + { + public: + CastReachTargetSpellAction(PlayerbotAI* ai, string spell, float distance) : CastSpellAction(ai, spell) + { + this->distance = distance; + } + virtual bool isUseful() + { + return AI_VALUE2(float, "distance", "current target") > (distance + sPlayerbotAIConfig.contactDistance); + } + + protected: + float distance; + }; + + class ReachMeleeAction : public ReachTargetAction + { + public: + ReachMeleeAction(PlayerbotAI* ai) : ReachTargetAction(ai, "reach melee", sPlayerbotAIConfig.meleeDistance) {} + }; + + class ReachSpellAction : public ReachTargetAction + { + public: + ReachSpellAction(PlayerbotAI* ai, float distance = sPlayerbotAIConfig.spellDistance) : ReachTargetAction(ai, "reach spell", distance) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.cpp new file mode 100644 index 000000000..bb88d63df --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.cpp @@ -0,0 +1,223 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ReadyCheckAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; +#include "botpch.h" +#include "../../playerbot.h" +#include "ReadyCheckAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +string formatPercent(const string& name, uint8 value, float percent) +{ + ostringstream out; + + string color; + if (percent > 75) + { + color = "|cff00ff00"; + } + else if (percent > 50) + { + color = "|cffffff00"; + } + else + { + color = "|cffff0000"; + } + + out << "|cffffffff[" << name << "]" << color << "x" << (int)value; + return out.str(); +} + +class ReadyChecker +{ +public: + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) = 0; + virtual string GetName() = 0; + virtual bool PrintAlways() { return true; } + + static list checkers; +}; + +list ReadyChecker::checkers; + +class HealthChecker : public ReadyChecker +{ +public: + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.almostFullHealth; + } + virtual string GetName() { return "HP"; } +}; + +class ManaChecker : public ReadyChecker +{ +public: + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + return !AI_VALUE2(bool, "has mana", "self target") || AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth; + } + virtual string GetName() { return "MP"; } +}; + +class DistanceChecker : public ReadyChecker +{ +public: + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + Player* bot = ai->GetBot(); + Player* master = ai->GetMaster(); + if (master) + { + bool distance = bot->GetDistance(master) <= sPlayerbotAIConfig.sightDistance; + if (!distance) + { + return false; + } + } + return true; + } + virtual bool PrintAlways() { return false; } + virtual string GetName() { return "Far away"; } +}; + +class HunterChecker : public ReadyChecker +{ +public: + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + Player* bot = ai->GetBot(); + if (bot->getClass() == CLASS_HUNTER) + { + if (!bot->GetUInt32Value(PLAYER_AMMO_ID)) + { + ai->TellMaster("Out of ammo!"); + return false; + } + + if (!bot->GetPet()) + { + ai->TellMaster("No pet!"); + return false; + } + + if (bot->GetPet()->GetHappinessState() == UNHAPPY) + { + ai->TellMaster("Pet is unhappy!"); + return false; + } + } + return true; + } + virtual bool PrintAlways() { return false; } + virtual string GetName() { return "Far away"; } +}; + + +class ItemCountChecker : public ReadyChecker +{ +public: + ItemCountChecker(const string& item, const string& name) { this->item = item; this->name = name; } + + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + return AI_VALUE2(uint8, "item count", item) > 0; + } + virtual string GetName() { return name; } + +private: + string item, name; +}; + +class ManaPotionChecker : public ItemCountChecker +{ +public: + ManaPotionChecker(const string& item, const string& name) : ItemCountChecker(item, name) {} + + virtual bool Check(PlayerbotAI *ai, AiObjectContext* context) + { + return !AI_VALUE2(bool, "has mana", "self target") || ItemCountChecker::Check(ai, context); + } +}; + +bool ReadyCheckAction::Execute(Event event) +{ + WorldPacket &p = event.getPacket(); + ObjectGuid player; + p.rpos(0); + if (!p.empty()) + { + p >> player; + if (player == bot->GetObjectGuid()) + { + return false; + } + } + + return ReadyCheck(); +} + +bool ReadyCheckAction::ReadyCheck() +{ + if (ReadyChecker::checkers.empty()) + { + ReadyChecker::checkers.push_back(new HealthChecker()); + ReadyChecker::checkers.push_back(new ManaChecker()); + ReadyChecker::checkers.push_back(new DistanceChecker()); + ReadyChecker::checkers.push_back(new HunterChecker()); + + ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food")); + ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water")); + ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot")); + ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot")); + } + + bool result = true; + for (list::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end(); ++i) + { + ReadyChecker* checker = *i; + bool ok = checker->Check(ai, context); + result = result && ok; + } + + ostringstream out; + + uint8 hp = AI_VALUE2(uint8, "item count", "healing potion"); + out << formatPercent("Hp", hp, 100.0 * hp / 5); + + out << ", "; + uint8 food = AI_VALUE2(uint8, "item count", "food"); + out << formatPercent("Food", food, 100.0 * food / 20); + + if (AI_VALUE2(bool, "has mana", "self target")) + { + out << ", "; + uint8 mp = AI_VALUE2(uint8, "item count", "mana potion"); + out << formatPercent("Mp", mp, 100.0 * mp / 5); + + out << ", "; + uint8 water = AI_VALUE2(uint8, "item count", "water"); + out << formatPercent("Water", water, 100.0 * water / 20); + } + + ai->TellMaster(out); + + WorldPacket* const packet = new WorldPacket(MSG_RAID_READY_CHECK); + *packet << bot->GetObjectGuid(); + *packet << uint8(1); + bot->GetSession()->QueuePacket(packet); + + ai->ChangeStrategy("-ready check", BOT_STATE_NON_COMBAT); + + return true; +} + +bool FinishReadyCheckAction::Execute(Event event) +{ + return ReadyCheck(); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.h b/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.h new file mode 100644 index 000000000..979c32513 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReadyCheckAction.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class ReadyCheckAction : public InventoryAction + { + public: + ReadyCheckAction(PlayerbotAI* ai, string name = "ready check") : InventoryAction(ai, name) {} + + virtual bool Execute(Event event); + + protected: + bool ReadyCheck(); + }; + + class FinishReadyCheckAction : public ReadyCheckAction + { + public: + FinishReadyCheckAction(PlayerbotAI* ai) : ReadyCheckAction(ai, "finish ready check") {} + + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReleaseSpiritAction.h b/src/modules/Bots/playerbot/strategy/actions/ReleaseSpiritAction.h new file mode 100644 index 000000000..820dddc87 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReleaseSpiritAction.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class ReleaseSpiritAction : public Action { + public: + ReleaseSpiritAction(PlayerbotAI* ai) : Action(ai, "release") {} + + public: + virtual bool Execute(Event event) + { + if (bot->IsAlive() || bot->GetCorpse()) + { + return false; + } + + ai->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT); + + bot->SetBotDeathTimer(); + bot->BuildPlayerRepop(); + + bot->RepopAtGraveyard(); + return true; + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.cpp b/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.cpp new file mode 100644 index 000000000..190880aef --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.cpp @@ -0,0 +1,46 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RememberTaxiAction.h" +#include "../values/LastMovementValue.h" + +using namespace ai; + +bool RememberTaxiAction::Execute(Event event) +{ + + + WorldPacket p(event.getPacket()); + p.rpos(0); + + switch (p.GetOpcode()) + { + case CMSG_ACTIVATETAXI: + { + LastMovement& movement = context->GetValue("last taxi")->Get(); + movement.taxiNodes.clear(); + movement.taxiNodes.resize(2); + + p >> movement.taxiMaster >> movement.taxiNodes[0] >> movement.taxiNodes[1]; + return true; + } + case CMSG_ACTIVATETAXIEXPRESS: + { + ObjectGuid guid; + uint32 node_count, totalcost; + p >> guid >> totalcost >> node_count; + + LastMovement& movement = context->GetValue("last taxi")->Get(); + movement.taxiNodes.clear(); + for (uint32 i = 0; i < node_count; ++i) + { + uint32 node; + p >> node; + movement.taxiNodes.push_back(node); + } + + return true; + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.h b/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.h new file mode 100644 index 000000000..03b674b14 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RememberTaxiAction.h @@ -0,0 +1,13 @@ +#pragma once + +namespace ai +{ + class RememberTaxiAction : public Action { + public: + RememberTaxiAction(PlayerbotAI* ai) : Action(ai, "remember taxi") {} + + public: + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.cpp b/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.cpp new file mode 100644 index 000000000..ef3edebab --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.cpp @@ -0,0 +1,36 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RepairAllAction.h" + + +using namespace ai; + +bool RepairAllAction::Execute(Event event) +{ + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Creature *unit = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_REPAIR); + if (!unit) + { + continue; + } + + if(bot->hasUnitState(UNIT_STAT_DIED)) + { + bot->RemoveSpellsCausingAura(SPELL_AURA_FEIGN_DEATH); + } + + bot->SetFacingToObject(unit); + uint32 totalCost = bot->DurabilityRepairAll(true, 1.0f, false); + + ostringstream out; + out << "Repair: " << chat->formatMoney(totalCost) << " (" << unit->GetName() << ")"; + ai->TellMasterNoFacing(out.str()); + + return true; + } + + ai->TellMaster("Cannot find any npc to repair at"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.h b/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.h new file mode 100644 index 000000000..2b4d96ef2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RepairAllAction.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class RepairAllAction : public Action + { + public: + RepairAllAction(PlayerbotAI* ai) : Action(ai, "repair") {} + virtual bool Execute(Event event); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.cpp new file mode 100644 index 000000000..3871b539e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.cpp @@ -0,0 +1,14 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ResetAiAction.h" +#include "../../PlayerbotDbStore.h" + +using namespace ai; + +bool ResetAiAction::Execute(Event event) +{ + sPlayerbotDbStore.Reset(ai); + ai->ResetStrategies(); + ai->TellMaster("AI was reset to defaults"); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.h b/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.h new file mode 100644 index 000000000..929abcb54 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ResetAiAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class ResetAiAction : public Action { + public: + ResetAiAction(PlayerbotAI* ai) : Action(ai, "reset ai") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.cpp b/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.cpp new file mode 100644 index 000000000..70d8c4372 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.cpp @@ -0,0 +1,108 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RevealGatheringItemAction.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +uint64 extractGuid(WorldPacket& packet); + +class AnyGameObjectInObjectRangeCheck +{ +public: + AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(GameObject* u) + { + if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo()) + { + return true; + } + + return false; + } + +private: + WorldObject const* i_obj; + float i_range; +}; + +bool RevealGatheringItemAction::Execute(Event event) +{ + if (!bot->GetGroup()) + { + return false; + } + + list targets; + AnyGameObjectInObjectRangeCheck u_check(bot, sPlayerbotAIConfig.grindDistance); + GameObjectListSearcher searcher(targets, u_check); + Cell::VisitAllObjects((const WorldObject*)bot, searcher, sPlayerbotAIConfig.reactDistance); + + vector result; + for(list::iterator tIter = targets.begin(); tIter != targets.end(); ++tIter) + { + GameObject* go = *tIter; + if (!go || !go->isSpawned() || bot->GetDistance2d(go) <= sPlayerbotAIConfig.lootDistance) + { + continue; + } + + if (LockEntry const *lockInfo = sLockStore.LookupEntry(go->GetGOInfo()->GetLockId())) + { + for (int i = 0; i < 8; ++i) + { + if (lockInfo->Type[i] == LOCK_KEY_SKILL) + { + uint32 skillId = SkillByLockType(LockType(lockInfo->Index[i])); + uint32 reqSkillValue = max((uint32)2, lockInfo->Skill[i]); + if ((skillId == SKILL_MINING || skillId == SKILL_HERBALISM) && + ai->HasSkill((SkillType)skillId) && uint32(bot->GetPureSkillValue(skillId)) >= reqSkillValue) + { + result.push_back(go); + break; + } + } + } + } + + if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGNODE && ai->HasSkill(SKILL_FISHING)) + { + result.push_back(go); + } + } + + if (result.empty()) + { + return false; + } + + GameObject *go = result[urand(0, result.size() - 1)]; + if (!go) return false; + + ostringstream msg; + msg << "I see a " << ChatHelper::formatGameobject(go) << ". "; + switch (go->GetGoType()) + { + case GAMEOBJECT_TYPE_CHEST: + msg << "Let's look at it."; + break; + case GAMEOBJECT_TYPE_FISHINGNODE: + msg << "Let's fish a bit."; + break; + default: + msg << "Should we go nearer?"; + } + + // everything is fine, do it + WorldPacket data(MSG_MINIMAP_PING, (8 + 4 + 4)); + data << bot->GetObjectGuid(); + data << go->GetPositionX(); + data << go->GetPositionY(); + bot->GetGroup()->BroadcastPacket(&data, true, -1, bot->GetObjectGuid()); + bot->Say(msg.str(), LANG_UNIVERSAL); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.h b/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.h new file mode 100644 index 000000000..dea0bc111 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RevealGatheringItemAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class RevealGatheringItemAction : public Action { + public: + RevealGatheringItemAction(PlayerbotAI* ai) : Action(ai, "reveal gathering item") {} + + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.cpp new file mode 100644 index 000000000..b576aea66 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.cpp @@ -0,0 +1,57 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ReviveFromCorpseAction.h" +#include "../../PlayerbotFactory.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool ReviveFromCorpseAction::Execute(Event event) +{ + Corpse* corpse = bot->GetCorpse(); + if (!corpse) + { + return false; + } + + time_t reclaimTime = corpse->GetGhostTime() + bot->GetCorpseReclaimDelay( corpse->GetType()==CORPSE_RESURRECTABLE_PVP ); + if (reclaimTime > time(0) || corpse->GetDistance(bot) > sPlayerbotAIConfig.spellDistance) + { + return false; + } + + bot->ResurrectPlayer(0.5f); + bot->SpawnCorpseBones(); + bot->SaveToDB(); + context->GetValue("current target")->Set(NULL); + bot->SetSelectionGuid(ObjectGuid()); + return true; +} + +bool SpiritHealerAction::Execute(Event event) +{ + Corpse* corpse = bot->GetCorpse(); + if (!corpse) + { + return false; + } + + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (unit && unit->IsSpiritHealer()) + { + PlayerbotChatHandler ch(bot); + bot->ResurrectPlayer(0.5f); + bot->SpawnCorpseBones(); + bot->SaveToDB(); + context->GetValue("current target")->Set(NULL); + bot->SetSelectionGuid(ObjectGuid()); + return true; + } + } + + ai->TellMaster("Cannot find any spirit healer nearby"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.h b/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.h new file mode 100644 index 000000000..d5e3cf2d8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ReviveFromCorpseAction.h @@ -0,0 +1,21 @@ +#pragma once + +namespace ai +{ + class ReviveFromCorpseAction : public Action { + public: + ReviveFromCorpseAction(PlayerbotAI* ai) : Action(ai, "revive") {} + + public: + virtual bool Execute(Event event); + }; + + class SpiritHealerAction : public Action { + public: + SpiritHealerAction(PlayerbotAI* ai) : Action(ai, "spirit healer") {} + + public: + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RewardAction.cpp b/src/modules/Bots/playerbot/strategy/actions/RewardAction.cpp new file mode 100644 index 000000000..cb40dbcfe --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RewardAction.cpp @@ -0,0 +1,78 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RewardAction.h" +#include "../ItemVisitors.h" +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool RewardAction::Execute(Event event) +{ + string link = event.getParam(); + + ItemIds itemIds = chat->parseItems(link); + if (itemIds.empty()) + { + return false; + } + + uint32 itemId = *itemIds.begin(); + + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Unit* npc = ai->GetUnit(*i); + if (npc && Reward(itemId, npc)) + { + return true; + } + } + + list gos = AI_VALUE(list, "nearest game objects"); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + GameObject* go = ai->GetGameObject(*i); + if (go && Reward(itemId, go)) + { + return true; + } + } + + ai->TellMaster("Cannot talk to quest giver"); + return false; +} + +bool RewardAction::Reward(uint32 itemId, Object* questGiver) +{ + QuestMenu& questMenu = bot->PlayerTalkClass->GetQuestMenu(); + for (uint32 iI = 0; iI < questMenu.MenuItemCount(); ++iI) + { + QuestMenuItem const& qItem = questMenu.GetItem(iI); + + uint32 questID = qItem.m_qId; + Quest const* pQuest = sObjectMgr.GetQuestTemplate(questID); + QuestStatus status = bot->GetQuestStatus(questID); + + // if quest is complete, turn it in + if (status == QUEST_STATUS_COMPLETE && + ! bot->GetQuestRewardStatus(questID) && + pQuest->GetRewChoiceItemsCount() > 1 && + bot->CanRewardQuest(pQuest, false)) + { + for (uint8 rewardIdx=0; rewardIdx < pQuest->GetRewChoiceItemsCount(); ++rewardIdx) + { + ItemPrototype const * const pRewardItem = sObjectMgr.GetItemPrototype(pQuest->RewChoiceItemId[rewardIdx]); + if (itemId == pRewardItem->ItemId) + { + bot->RewardQuest(pQuest, rewardIdx, questGiver, false); + + ostringstream out; out << chat->formatItem(pRewardItem) << " rewarded"; + ai->TellMaster(out); + return true; + } + } + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/RewardAction.h b/src/modules/Bots/playerbot/strategy/actions/RewardAction.h new file mode 100644 index 000000000..f4eda1335 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RewardAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class RewardAction : public InventoryAction { + public: + RewardAction(PlayerbotAI* ai) : InventoryAction(ai, "reward") {} + virtual bool Execute(Event event); + + private: + bool Reward(uint32 itemId, Object* pNpc); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/RtiAction.h b/src/modules/Bots/playerbot/strategy/actions/RtiAction.h new file mode 100644 index 000000000..6c2ef7b15 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/RtiAction.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class RtiAction : public Action + { + public: + RtiAction(PlayerbotAI* ai) : Action(ai, "rti") + {} + + virtual bool Execute(Event event) + { + string text = event.getParam(); + if (text.empty() || text == "?") + { + ostringstream out; out << "RTI: "; + AppendRti(out); + ai->TellMaster(out); + return true; + } + + context->GetValue("rti")->Set(text); + ostringstream out; out << "RTI set to: "; + AppendRti(out); + ai->TellMaster(out); + return true; + } + + private: + void AppendRti(ostringstream & out) + { + out << AI_VALUE(string, "rti"); + + Unit* target = AI_VALUE(Unit*, "rti target"); + if(target) + { + out << " (" << target->GetName() << ")"; + } + + } + + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.cpp new file mode 100644 index 000000000..1f45357cd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.cpp @@ -0,0 +1,81 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SaveManaAction.h" +#include "../../AiFactory.h" +#include "../ItemVisitors.h" + +using namespace ai; + +bool SaveManaAction::Execute(Event event) +{ + string text = event.getParam(); + double value = AI_VALUE(double, "mana save level"); + + if (text == "?") + { + ostringstream out; out << "Mana save level: " << format(value); + ai->TellMaster(out); + return true; + } + + if (text == "*") + { + switch (bot->getClass()) + { + case CLASS_HUNTER: + case CLASS_SHAMAN: + case CLASS_DRUID: + value = 5.0; + break; + case CLASS_MAGE: + case CLASS_PRIEST: + case CLASS_WARLOCK: + value = 2.0; + break; + default: + value = 3.0; + } + } + else if (text.empty()) + { + value = 1.0; + } + else + { + value = atof(text.c_str()); + } + + value = min(10.0, value); + value = max(1.0, value); + value = floor(value * 100 + 0.5) / 100.0; + + ai->GetAiObjectContext()->GetValue("mana save level")->Set(value); + + ostringstream out; out << "Mana save level set: " << format(value); + ai->TellMaster(out); + + return true; +} + +string SaveManaAction::format(double value) +{ + ostringstream out; + if (value <= 1.0) + { + out << "|cFF808080"; + } + else if (value <= 5.0) + { + out << "|cFF00FF00"; + } + else if (value <= 7.0) + { + out << "|cFFFFFF00"; + } + else + { + out << "|cFFFF0000"; + } + out << value << "|cffffffff"; + return out.str(); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.h b/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.h new file mode 100644 index 000000000..3d9b3e0f8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SaveManaAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class SaveManaAction : public Action + { + public: + SaveManaAction(PlayerbotAI* ai) : Action(ai, "save mana") {} + + public: + virtual bool Execute(Event event); + + private: + string format(double value); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SayAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SayAction.cpp new file mode 100644 index 000000000..63d6f6512 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SayAction.cpp @@ -0,0 +1,119 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SayAction.h" + +using namespace ai; + +map > SayAction::stringTable; +map SayAction::probabilityTable; + +SayAction::SayAction(PlayerbotAI* ai) : Action(ai, "say"), Qualified() +{ +} + +void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) + { + return; + } + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + } +} + +bool SayAction::Execute(Event event) +{ + if (stringTable.empty()) + { + QueryResult* results = CharacterDatabase.PQuery("SELECT name, text, type FROM ai_playerbot_speech"); + if (results) + { + do + { + Field* fields = results->Fetch(); + string name = fields[0].GetString(); + string text = fields[1].GetString(); + string type = fields[2].GetString(); + + if (type == "yell") text = "/y " + text; + { + stringTable[name].push_back(text); + } + } while (results->NextRow()); + delete results; + } + } + if (probabilityTable.empty()) + { + QueryResult* results = CharacterDatabase.PQuery("SELECT name, probability FROM ai_playerbot_speech_probability"); + if (results) + { + do + { + Field* fields = results->Fetch(); + string name = fields[0].GetString(); + uint32 probability = fields[1].GetUInt32(); + + probabilityTable[name] = probability; + } while (results->NextRow()); + delete results; + } + } + + vector &strings = stringTable[qualifier]; + if (strings.empty()) return false; + + ai->GetAiObjectContext()->GetValue("last said", qualifier)->Set(time(0) + urand(1, 60)); + + uint32 probability = probabilityTable[qualifier]; + if (!probability) probability = 100; + { + if (urand(0, 100) >= probability) return false; + } + + uint32 idx = urand(0, strings.size() - 1); + string text = strings[idx]; + + Unit* target = AI_VALUE(Unit*, "tank target"); + if (!target) target = AI_VALUE(Unit*, "current target"); + { + if (target) replaceAll(text, "", target->GetName()); + } + + replaceAll(text, "", IsAlliance(bot->getRace()) ? "Alliance" : "Horde"); + + if (bot->GetMap()) + { + const TerrainInfo * terrain = bot->GetMap()->GetTerrain(); + if (terrain) + { + uint32 areaId = terrain->GetAreaId(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + if (areaId) + { + AreaTableEntry const* area = sAreaStore.LookupEntry(areaId); + if (area) + { + replaceAll(text, "", area->area_name[0]); + } + } + } + } + + if (strncmp(text.c_str(), "/y ", 3)) + { + bot->Yell(text.substr(3), LANG_UNIVERSAL); + } + else + { + bot->Say(text, LANG_UNIVERSAL); + } +} + + +bool SayAction::isUseful() +{ + time_t lastSaid = AI_VALUE2(time_t, "last said", qualifier); + return (time(0) - lastSaid) > 30; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SayAction.h b/src/modules/Bots/playerbot/strategy/actions/SayAction.h new file mode 100644 index 000000000..7caad21aa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SayAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class SayAction : public Action, public Qualified + { + public: + SayAction(PlayerbotAI* ai); + virtual bool Execute(Event event); + virtual bool isUseful(); + virtual string getName() { return "say::" + qualifier; } + + private: + static map > stringTable; + static map probabilityTable; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.cpp new file mode 100644 index 000000000..91e5b8fb9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.cpp @@ -0,0 +1,30 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../../RandomPlayerbotMgr.h" +#include "SecurityCheckAction.h" + +using namespace ai; + + +bool SecurityCheckAction::isUseful() +{ + return sRandomPlayerbotMgr.IsRandomBot(bot) && ai->GetMaster() && ai->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER; +} + +bool SecurityCheckAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (group) + { + LootMethod method = group->GetLootMethod(); + ItemQualities threshold = group->GetLootThreshold(); + if (method == MASTER_LOOT || method == FREE_FOR_ALL || threshold > ITEM_QUALITY_UNCOMMON) + { + ai->TellMaster("I won't do anything until you change loot type to group loot with green threshold"); + ai->ChangeStrategy("+passive,+stay", BOT_STATE_NON_COMBAT); + ai->ChangeStrategy("+passive,+stay", BOT_STATE_COMBAT); + return true; + } + } + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.h b/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.h new file mode 100644 index 000000000..4ae38f05d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SecurityCheckAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class SecurityCheckAction : public Action + { + public: + SecurityCheckAction(PlayerbotAI* ai) : Action(ai, "security check") {} + virtual bool isUseful(); + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SellAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SellAction.cpp new file mode 100644 index 000000000..6a1d11ce0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SellAction.cpp @@ -0,0 +1,106 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SellAction.h" +#include "../ItemVisitors.h" + +using namespace ai; + +class SellItemsVisitor : public IterateItemsVisitor +{ +public: + explicit SellItemsVisitor(SellAction* action) : IterateItemsVisitor() + { + this->action = action; + } + + virtual bool Visit(Item* item) + { + action->Sell(item); + return true; + } + +private: + SellAction* action; +}; + +class SellGrayItemsVisitor : public SellItemsVisitor +{ +public: + explicit SellGrayItemsVisitor(SellAction* action) : SellItemsVisitor(action) {} + + virtual bool Visit(Item* item) + { + if (item->GetProto()->Quality != ITEM_QUALITY_POOR) + { + return true; + } + + return SellItemsVisitor::Visit(item); + } +}; + + +bool SellAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + string text = event.getParam(); + + if (text == "gray" || text == "*") + { + SellGrayItemsVisitor visitor(this); + IterateItems(&visitor); + return true; + } + + ItemIds ids = chat->parseItems(text); + + for (ItemIds::iterator i =ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + Sell(&visitor); + } + + return true; +} + + +void SellAction::Sell(FindItemVisitor* visitor) +{ + IterateItems(visitor); + list items = visitor->GetResult(); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Sell(*i); + } +} + +void SellAction::Sell(Item* item) +{ + Player* master = GetMaster(); + list vendors = ai->GetAiObjectContext()->GetValue >("nearest npcs")->Get(); + bool bought = false; + for (list::iterator i = vendors.begin(); i != vendors.end(); ++i) + { + ObjectGuid vendorguid = *i; + Creature *pCreature = bot->GetNPCIfCanInteractWith(vendorguid,UNIT_NPC_FLAG_VENDOR); + if (!pCreature) + { + continue; + } + + ObjectGuid itemguid = item->GetObjectGuid(); + uint32 count = item->GetCount(); + + WorldPacket p; + p << vendorguid << itemguid << count; + bot->GetSession()->HandleSellItemOpcode(p); + + ostringstream out; out << "Selling " << chat->formatItem(item->GetProto()); + ai->TellMaster(out); + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SellAction.h b/src/modules/Bots/playerbot/strategy/actions/SellAction.h new file mode 100644 index 000000000..c1f091548 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SellAction.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class SellAction : public InventoryAction { + public: + SellAction(PlayerbotAI* ai) : InventoryAction(ai, "sell") {} + virtual bool Execute(Event event); + + void Sell(FindItemVisitor* visitor); + void Sell(Item* item); + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/SendMailAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SendMailAction.cpp new file mode 100644 index 000000000..a227eadde --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SendMailAction.cpp @@ -0,0 +1,130 @@ +#include "botpch.h" +#include "Mail.h" +#include "../../playerbot.h" +#include "SendMailAction.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool SendMailAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + uint32 account = sObjectMgr.GetPlayerAccountIdByGUID(bot->GetObjectGuid()); + if (sPlayerbotAIConfig.IsInRandomAccountList(account)) + { + ai->TellMaster("I can't do that"); + return false; + } + + list gos = *context->GetValue >("nearest game objects"); + bool mailboxFound = false; + for (list::iterator i = gos.begin(); i != gos.end(); ++i) + { + GameObject* go = ai->GetGameObject(*i); + if (go && go->GetGoType() == GAMEOBJECT_TYPE_MAILBOX) + { + mailboxFound = true; + break; + } + } + + if (!mailboxFound) + { + ai->TellMaster("There is no mailbox nearby"); + return false; + } + + string text = event.getParam(); + Player* receiver = master; + vector ss = split(text, ' '); + if (ss.size() > 1) + { + Player* p = sObjectMgr.GetPlayer(ss[ss.size() - 1].c_str()); + if (p) receiver = p; + } + + ItemIds ids = chat->parseItems(text); + if (ids.size() > 1) + { + ai->TellMaster("You can not request more than one item"); + return false; + } + + if (ids.empty()) + { + uint32 money = chat->parseMoney(text); + if (!money) + { + return false; + } + + if (bot->GetMoney() < money) + { + ai->TellMaster("I don't have enough money"); + return false; + } + + ostringstream body; + body << "Hello, " << receiver->GetName() << ",\n"; + body << "\n"; + body << "Here is the money you asked for"; + body << "\n"; + body << "Thanks,\n"; + body << bot->GetName() << "\n"; + + + MailDraft draft("Money you asked for", body.str()); + draft.SetMoney(money); + bot->SetMoney(bot->GetMoney() - money); + draft.SendMailTo(MailReceiver(receiver), MailSender(bot)); + + ostringstream out; out << "Sending mail to " << receiver->GetName(); + ai->TellMaster(out.str()); + return true; + } + + ostringstream body; + body << "Hello, " << receiver->GetName() << ",\n"; + body << "\n"; + body << "Here are the item(s) you asked for"; + body << "\n"; + body << "Thanks,\n"; + body << bot->GetName() << "\n"; + + MailDraft draft("Item(s) you asked for", body.str()); + for (ItemIds::iterator i =ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + list items = visitor.GetResult(); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* item = *i; + if (item->IsSoulBound() || item->IsConjuredConsumable()) + { + ostringstream out; + out << "Cannot send " << ChatHelper::formatItem(item->GetProto()); + ai->TellMaster(out); + continue; + } + + bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); + item->DeleteFromInventoryDB(); + item->SetOwnerGuid(master->GetObjectGuid()); + item->SaveToDB(); + draft.AddItem(item); + draft.SendMailTo(MailReceiver(receiver), MailSender(bot)); + + ostringstream out; out << "Sending mail to " << receiver->GetName(); + ai->TellMaster(out.str()); + return true; + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SendMailAction.h b/src/modules/Bots/playerbot/strategy/actions/SendMailAction.h new file mode 100644 index 000000000..d65a26238 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SendMailAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class SendMailAction : public InventoryAction + { + public: + SendMailAction(PlayerbotAI* ai) : InventoryAction(ai, "sendmail") {} + + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.cpp new file mode 100644 index 000000000..f1f486324 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.cpp @@ -0,0 +1,50 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SetHomeAction.h" +#include "../../PlayerbotAIConfig.h" + + +using namespace ai; + +bool SetHomeAction::Execute(Event event) +{ + Player* master = ai->GetMaster(); + if (!master) + { + return false; + } + + ObjectGuid selection = master->GetSelectionGuid(); + if (selection) + { + Unit* unit = master->GetMap()->GetUnit(selection); + if (unit && unit->IsInnkeeper()) + { + float angle = GetFollowAngle(); + float x = unit->GetPositionX() + sPlayerbotAIConfig.followDistance * cos(angle); + float y = unit->GetPositionY() + sPlayerbotAIConfig.followDistance * sin(angle); + float z = unit->GetPositionZ(); + WorldLocation loc(unit->GetMapId(), x, y, z); + bot->SetHomebindToLocation(loc, unit->GetAreaId()); + ai->TellMaster("This inn is my new home"); + return true; + } + } + + list npcs = AI_VALUE(list, "nearest npcs"); + for (list::iterator i = npcs.begin(); i != npcs.end(); i++) + { + Creature *unit = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_INNKEEPER); + if (!unit) + { + continue; + } + + bot->GetSession()->SendBindPoint(unit); + ai->TellMaster("This inn is my new home"); + return true; + } + + ai->TellMaster("Can't find any innkeeper around"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.h b/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.h new file mode 100644 index 000000000..b98a5b55e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SetHomeAction.h @@ -0,0 +1,12 @@ +#pragma once + +#include "MovementActions.h" + +namespace ai +{ + class SetHomeAction : public MovementAction { + public: + SetHomeAction(PlayerbotAI* ai) : MovementAction(ai, "home") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.cpp b/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.cpp new file mode 100644 index 000000000..5554b104e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.cpp @@ -0,0 +1,44 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShareQuestAction.h" + + +using namespace ai; + +bool ShareQuestAction::Execute(Event event) +{ + string link = event.getParam(); + if (!GetMaster()) + { + return false; + } + + PlayerbotChatHandler handler(GetMaster()); + uint32 entry = handler.extractQuestId(link); + if (!entry) + { + return false; + } + + Quest const* quest = sObjectMgr.GetQuestTemplate(entry); + if (!quest) + { + return false; + } + + // remove all quest entries for 'entry' from quest log + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 logQuest = bot->GetQuestSlotQuestId(slot); + if (logQuest == entry) + { + WorldPacket p; + p << entry; + bot->GetSession()->HandlePushQuestToParty(p); + ai->TellMaster("Quest shared"); + return true; + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.h b/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.h new file mode 100644 index 000000000..932d67421 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/ShareQuestAction.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class ShareQuestAction : public Action { + public: + ShareQuestAction(PlayerbotAI* ai) : Action(ai, "share quest") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.cpp new file mode 100644 index 000000000..485a82530 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.cpp @@ -0,0 +1,136 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SkipSpellsListAction.h" +#include "../values/SkipSpellsListValue.h" +#include "LootAction.h" + +using namespace ai; + + +bool SkipSpellsListAction::Execute(Event event) +{ + string cmd = event.getParam(); + + set& skipSpells = AI_VALUE(set&, "skip spells list"); + + SpellIds spellIds = parseIds(cmd); + if (!spellIds.empty()) { + { + skipSpells.clear(); + } + for (SpellIds::iterator i = spellIds.begin(); i != spellIds.end(); ++i) + { + skipSpells.insert(*i); + } + cmd = "?"; + } + + if (cmd == "reset") + { + skipSpells.clear(); + ai->TellMaster("Ignored spell list is empty"); + return true; + } + + if (cmd.empty() || cmd == "?") + { + ostringstream out; + if (skipSpells.empty()) + { + ai->TellMaster("Ignored spell list is empty"); + return true; + } + + out << "Ignored spell list: "; + + bool first = true; + for (set::iterator i = skipSpells.begin(); i != skipSpells.end(); i++) + { + SpellEntry const* spell = sSpellStore.LookupEntry(*i); + if (!spell) + { + continue; + } + + if (first) first = false; else out << ", "; + { + out << chat->formatSpell(spell); + } + } + ai->TellMaster(out); + } + else + { + bool remove = cmd.size() > 1 && cmd.substr(0, 1) == "-"; + if (remove) + { + cmd = cmd.substr(1); + } + + uint32 spellId = chat->parseSpell(cmd); + if (!spellId) + { + ai->TellMaster("Unknown spell"); + return false; + } + + SpellEntry const* spell = sSpellStore.LookupEntry(spellId); + if (!spell) + { + return false; + } + + if (remove) + { + set::iterator j = skipSpells.find(spellId); + if (j != skipSpells.end()) + { + skipSpells.erase(j); + ostringstream out; + out << chat->formatSpell(spell) << " removed from ignored spells"; + ai->TellMaster(out); + return true; + } + } + else + { + set::iterator j = skipSpells.find(spellId); + if (j == skipSpells.end()) + { + skipSpells.insert(spellId); + ostringstream out; + out << chat->formatSpell(spell) << " added to ignored spells"; + ai->TellMaster(out); + return true; + } + } + } + + return false; +} + + +SpellIds SkipSpellsListAction::parseIds(string text) +{ + SpellIds spellIds; + + uint8 pos = 0; + while (pos < text.size()) + { + int endPos = text.find(',', pos); + if (endPos == -1) + { + endPos = text.size(); + } + + string idC = text.substr(pos, endPos - pos); + uint32 id = atol(idC.c_str()); + pos = endPos + 1; + if (id) + { + spellIds.insert(id); + } + } + + return spellIds; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.h b/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.h new file mode 100644 index 000000000..143ce6758 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SkipSpellsListAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" +#include "../../LootObjectStack.h" + +namespace ai +{ + class SkipSpellsListAction : public Action { + public: + SkipSpellsListAction(PlayerbotAI* ai) : Action(ai, "ss") {} + virtual bool Execute(Event event); + + private: + SpellIds parseIds(string text); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/StatsAction.cpp b/src/modules/Bots/playerbot/strategy/actions/StatsAction.cpp new file mode 100644 index 000000000..b0201a4e8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/StatsAction.cpp @@ -0,0 +1,198 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "StatsAction.h" + + +using namespace ai; + +bool StatsAction::Execute(Event event) +{ + ostringstream out; + + ListGold(out); + + out << ", "; + ListBagSlots(out); + + out << ", "; + ListRepairCost(out); + + if (bot->GetUInt32Value(PLAYER_NEXT_LEVEL_XP)) + { + out << ", "; + ListXP(out); + } + + ai->TellMaster(out); + return true; +} + +void StatsAction::ListGold(ostringstream &out) +{ + out << chat->formatMoney(bot->GetMoney()); +} + +void StatsAction::ListBagSlots(ostringstream &out) +{ + uint32 totalused = 0, total = 16; + // list out items in main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + const Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) + { + totalused++; + } + } + uint32 totalfree = 16 - totalused; + // list out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + ItemPrototype const* pBagProto = pBag->GetProto(); + if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER) + { + total += pBag->GetBagSize(); + totalfree += pBag->GetFreeSlots(); + } + } + + } + + string color = "ff00ff00"; + if (totalfree < total / 2) + { + color = "ffffff00"; + } + if (totalfree < total / 4) + { + color = "ffff0000"; + } + out << "|h|c" << color << totalfree << "/" << total << "|h|cffffffff Bag"; +} + +void StatsAction::ListXP( ostringstream &out ) +{ + uint32 curXP = bot->GetUInt32Value(PLAYER_XP); + uint32 nextLevelXP = bot->GetUInt32Value(PLAYER_NEXT_LEVEL_XP); + uint32 restXP = bot->GetUInt32Value(PLAYER_REST_STATE_EXPERIENCE); + uint32 xpPercent = 0; + + if (nextLevelXP) + { + xpPercent = 100 * curXP / nextLevelXP; + } + uint32 restPercent = 0; + if (restXP) + { + restPercent = 2 * (100 * restXP / nextLevelXP); + } + + out << "|cff00ff00" << xpPercent << "|cffffd333/|cff00ff00" << restPercent << "%|cffffffff XP"; +} + +void StatsAction::ListRepairCost(ostringstream &out) +{ + uint32 totalCost = 0; + double repairPercent = 0; + double repairCount = 0; + for(int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + uint16 pos = ( (INVENTORY_SLOT_BAG_0 << 8) | i ); + totalCost += EstRepair(pos); + double repair = RepairPercent(pos); + if (repair < 100) + { + repairPercent += repair; + repairCount++; + } + } + repairPercent /= repairCount; + + string color = "ff00ff00"; + if (repairPercent < 50) + { + color = "ffffff00"; + } + if (repairPercent < 25) + { + color = "ffff0000"; + } + out << "|c" << color << (uint32)ceil(repairPercent) << "% (" << chat->formatMoney(totalCost) << ")|cffffffff Dur"; +} + +uint32 StatsAction::EstRepair(uint16 pos) +{ + Item* item = bot->GetItemByPos(pos); + + uint32 TotalCost = 0; + if(!item) + { + return TotalCost; + } + + uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + if(!maxDurability) + { + return TotalCost; + } + + uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY); + + uint32 LostDurability = maxDurability - curDurability; + if(LostDurability>0) + { + ItemPrototype const *ditemProto = item->GetProto(); + + DurabilityCostsEntry const *dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel); + if(!dcost) + { + sLog.outError("RepairDurability: Wrong item lvl %u", ditemProto->ItemLevel); + return TotalCost; + } + + uint32 dQualitymodEntryId = (ditemProto->Quality+1)*2; + DurabilityQualityEntry const *dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId); + if(!dQualitymodEntry) + { + sLog.outError("RepairDurability: Wrong dQualityModEntry %u", dQualitymodEntryId); + return TotalCost; + } + + uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class,ditemProto->SubClass)]; + uint32 costs = uint32(LostDurability*dmultiplier*double(dQualitymodEntry->quality_mod)); + + if (costs==0) //fix for ITEM_QUALITY_ARTIFACT + { + costs = 1; + } + + TotalCost = costs; + } + return TotalCost; +} + +double StatsAction::RepairPercent(uint16 pos) +{ + Item* item = bot->GetItemByPos(pos); + if (!item) + { + return 100; + } + + uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY); + if(!maxDurability) + { + return 100; + } + + uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY); + if (!curDurability) + { + return 0; + } + + return curDurability * 100.0 / maxDurability; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/StatsAction.h b/src/modules/Bots/playerbot/strategy/actions/StatsAction.h new file mode 100644 index 000000000..f3f0c4fd9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/StatsAction.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class StatsAction : public Action { + public: + StatsAction(PlayerbotAI* ai) : Action(ai, "stats") {} + virtual bool Execute(Event event); + + private: + void ListBagSlots(ostringstream &out); + void ListXP(ostringstream &out); + void ListRepairCost(ostringstream &out); + void ListGold(ostringstream &out); + uint32 EstRepair(uint16 pos); + double RepairPercent(uint16 pos); + + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/StayActions.cpp b/src/modules/Bots/playerbot/strategy/actions/StayActions.cpp new file mode 100644 index 000000000..1d6ef85ad --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/StayActions.cpp @@ -0,0 +1,39 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "StayActions.h" +#include "../values/LastMovementValue.h" + +using namespace ai; + +void StayActionBase::Stay() +{ + AI_VALUE(LastMovement&, "last movement").Set(NULL); + + MotionMaster &mm = *bot->GetMotionMaster(); + if (mm.GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE || bot->IsFlying()) + { + return; + } + + mm.Clear(); + bot->InterruptMoving(); + bot->clearUnitState(UNIT_STAT_CHASE); + bot->clearUnitState(UNIT_STAT_FOLLOW); + + if (!bot->IsStandState()) + { + bot->SetStandState(UNIT_STAND_STATE_STAND); + } +} + +bool StayAction::Execute(Event event) +{ + Stay(); + + return true; +} + +bool StayAction::isUseful() +{ + return !AI_VALUE2(bool, "moving", "self target"); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/StayActions.h b/src/modules/Bots/playerbot/strategy/actions/StayActions.h new file mode 100644 index 000000000..ccbe2e3b2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/StayActions.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class StayActionBase : public MovementAction { + public: + StayActionBase(PlayerbotAI* ai, string name) : MovementAction(ai, name) {} + + protected: + void Stay(); + }; + + class StayAction : public StayActionBase { + public: + StayAction(PlayerbotAI* ai) : StayActionBase(ai, "stay") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.cpp b/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.cpp new file mode 100644 index 000000000..eb96ed59f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.cpp @@ -0,0 +1,319 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SuggestWhatToDoAction.h" +#include "../../../ahbot/AhBot.h" +#include "ChannelMgr.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +SuggestWhatToDoAction::SuggestWhatToDoAction(PlayerbotAI* ai, const string name) : InventoryAction(ai, name) +{ + suggestions.push_back(&SuggestWhatToDoAction::instance); + suggestions.push_back(&SuggestWhatToDoAction::specificQuest); + suggestions.push_back(&SuggestWhatToDoAction::newQuest); + suggestions.push_back(&SuggestWhatToDoAction::grindMaterials); + suggestions.push_back(&SuggestWhatToDoAction::grindReputation); + suggestions.push_back(&SuggestWhatToDoAction::nothing); + suggestions.push_back(&SuggestWhatToDoAction::relax); +} + +bool SuggestWhatToDoAction::Execute(Event event) +{ + if (!sRandomPlayerbotMgr.IsRandomBot(bot) || bot->GetGroup() || bot->GetInstanceId()) + { + return false; + } + + int index = rand() % suggestions.size(); + (this->*suggestions[index])(); + + return true; +} + +void SuggestWhatToDoAction::instance() +{ + uint32 level = bot->getLevel(); + if (level > 15) + { + switch (urand(0, 5)) + { + case 0: + spam("Need a tank for an instance run"); + break; + case 1: + spam("Need a healer for an instance run"); + break; + case 2: + spam("I would like to do an instance run. Would you like to join me?"); + break; + case 3: + spam("Need better equipment. Why not do an instance run?"); + break; + case 4: + spam("Have dungeon quests? Can join your group!"); + break; + case 5: + spam("Have group quests? Invite me!"); + break; + } + } +} + +vector SuggestWhatToDoAction::GetIncompletedQuests() +{ + vector result; + + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (!questId) + { + continue; + } + + QuestStatus status = bot->GetQuestStatus(questId); + if (status == QUEST_STATUS_INCOMPLETE || status == QUEST_STATUS_NONE) + { + result.push_back(questId); + } + } + + return result; +} + +void SuggestWhatToDoAction::specificQuest() +{ + vector quests = GetIncompletedQuests(); + if (quests.empty()) + { + return; + } + + int index = rand() % quests.size(); + + Quest const* quest = sObjectMgr.GetQuestTemplate(quests[index]); + ostringstream out; out << "We could do some quest, for instance " << chat->formatQuest(quest); + spam(out.str()); +} + +void SuggestWhatToDoAction::newQuest() +{ + vector quests = GetIncompletedQuests(); + if (quests.size() < MAX_QUEST_LOG_SIZE - 5) + { + spam("I would like to pick up and do a new quest. Just invite me!"); + } +} + +void SuggestWhatToDoAction::grindMaterials() +{ + if (bot->getLevel() <= 5) + { + return; + } + + QueryResult *result = CharacterDatabase.PQuery("SELECT distinct category, multiplier FROM ahbot_category where category not in ('other', 'quest', 'trade', 'reagent') and multiplier > 3 order by multiplier desc limit 10"); + if (!result) + { + return; + } + + map categories; + do + { + Field* fields = result->Fetch(); + categories[fields[0].GetCppString()] = fields[1].GetDouble(); + } while (result->NextRow()); + + for (map::iterator i = categories.begin(); i != categories.end(); ++i) + { + if (urand(0, 10) < 3) { + { + string name = i->first; + } + double multiplier = i->second; + + for (int j = 0; j < ahbot::CategoryList::instance.size(); j++) + { + ahbot::Category* category = ahbot::CategoryList::instance[j]; + if (name == category->GetName()) + { + string item = category->GetLabel(); + transform(item.begin(), item.end(), item.begin(), ::tolower); + ostringstream itemout, msg; + itemout << "|c0000FF00" << item << "|cffffffff"; + item = itemout.str(); + msg << "|cffffffff"; + switch (urand(0, 4)) + { + case 0: + msg << "Need help for tradeskill? I've heard that " << item << " are a good sell on the AH at the moment."; + break; + case 1: + msg << "Can we have some trade material grinding? Especially for " << item << "?"; + break; + case 2: + msg << "I have some trade materials for sell. You know, " << item << " are very expensive now, I'd sell it cheaper"; + break; + default: + msg << "I am going to grind some trade materials, " << item << " to be exact. Would you like to join me?"; + } + spam(msg.str()); + break; + } + } + } + } + + delete result; +} + +void SuggestWhatToDoAction::grindReputation() +{ + if (bot->getLevel() > 15) + { + spam("I think we should do something to improve our reputation"); + } +} + +void SuggestWhatToDoAction::nothing() +{ + spam("I don't want to do anything. Help me find something interesting"); +} + +void SuggestWhatToDoAction::relax() +{ + spam("It is so boring... We could relax a bit"); +} + +void SuggestWhatToDoAction::spam(string msg) +{ + Player* player = sRandomPlayerbotMgr.GetRandomPlayer(); + if (!player || !player->IsInWorld()) + { + return; + } + + if (!ai->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_TALK, true, player)) + { + return; + } + + if (sPlayerbotAIConfig.whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr.IsRandomBot(bot) && + player->GetSession()->GetSecurity() < SEC_GAMEMASTER && + (bot->GetMapId() != player->GetMapId() || bot->GetDistance(player) > sPlayerbotAIConfig.whisperDistance)) + return; + + bot->Whisper(msg, LANG_UNIVERSAL, player->GetObjectGuid()); +} + + +class FindTradeItemsVisitor : public IterateItemsVisitor +{ +public: + explicit FindTradeItemsVisitor(uint32 quality) : quality(quality), IterateItemsVisitor() {} + + virtual bool Visit(Item* item) + { + ItemPrototype const* proto = item->GetProto(); + if (proto->Quality != quality) + { + return true; + } + + if (proto->Class == ITEM_CLASS_TRADE_GOODS && proto->Bonding == NO_BIND) + { + if(proto->Quality == ITEM_QUALITY_NORMAL && item->GetCount() > 1 && item->GetCount() == item->GetMaxStackCount()) + { + stacks.push_back(proto->ItemId); + } + + items.push_back(proto->ItemId); + count[proto->ItemId] += item->GetCount(); + } + + return true; + } + + map count; + vector stacks; + vector items; + +private: + uint32 quality; +}; + + +SuggestTradeAction::SuggestTradeAction(PlayerbotAI* ai) : SuggestWhatToDoAction(ai, "suggest trade") +{ +} + +bool SuggestTradeAction::Execute(Event event) +{ + uint32 quality = urand(0, 100); + if (quality > 90) + { + quality = ITEM_QUALITY_EPIC; + } + else if (quality >75) + { + quality = ITEM_QUALITY_RARE; + } + else if (quality > 50) + { + quality = ITEM_QUALITY_UNCOMMON; + } + else + { + quality = ITEM_QUALITY_NORMAL; + } + + uint32 item = 0, count = 0; + while (quality-- > ITEM_QUALITY_POOR) + { + FindTradeItemsVisitor visitor(quality); + IterateItems(&visitor); + if (!visitor.stacks.empty()) + { + int index = urand(0, visitor.stacks.size() - 1); + item = visitor.stacks[index]; + } + + if (!item) + { + if (!visitor.items.empty()) + { + int index = urand(0, visitor.items.size() - 1); + item = visitor.items[index]; + } + } + + if (item) + { + count = visitor.count[item]; + break; + } + } + + if (!item || !count) + { + return false; + } + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(item); + if (!proto) + { + return false; + } + + uint32 price = auctionbot.GetSellPrice(proto) * sRandomPlayerbotMgr.GetSellMultiplier(bot) * count; + if (!price) + { + return false; + } + + ostringstream out; out << "Selling " << chat->formatItem(proto, count) << " for " << chat->formatMoney(price); + spam(out.str()); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.h b/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.h new file mode 100644 index 000000000..f561c3d8e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/SuggestWhatToDoAction.h @@ -0,0 +1,36 @@ +#pragma once + +#include "InventoryAction.h" + +namespace ai +{ + class SuggestWhatToDoAction : public InventoryAction + { + public: + SuggestWhatToDoAction(PlayerbotAI* ai, string name = "suggest what to do"); + virtual bool Execute(Event event); + + protected: + typedef void (SuggestWhatToDoAction::*Suggestion) (); + vector suggestions; + void instance(); + void specificQuest(); + void newQuest(); + void grindMaterials(); + void grindReputation(); + void nothing(); + void relax(); + void trade(); + void spam(string msg); + + vector GetIncompletedQuests(); + }; + + class SuggestTradeAction : public SuggestWhatToDoAction + { + public: + SuggestTradeAction(PlayerbotAI* ai); + virtual bool Execute(Event event); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.cpp new file mode 100644 index 000000000..a997d99c5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.cpp @@ -0,0 +1,99 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TalkToQuestGiverAction.h" + + +using namespace ai; + +void TalkToQuestGiverAction::ProcessQuest(Quest const* quest, WorldObject* questGiver) +{ + std::ostringstream out; out << "Quest "; + + QuestStatus status = bot->GetQuestStatus(quest->GetQuestId()); + switch (status) + { + case QUEST_STATUS_COMPLETE: + TurnInQuest(quest, questGiver, out); + break; + case QUEST_STATUS_INCOMPLETE: + out << "|cffff0000Incompleted|r"; + break; + case QUEST_STATUS_AVAILABLE: + case QUEST_STATUS_NONE: + out << "|cff00ff00Available|r"; + break; + case QUEST_STATUS_FAILED: + out << "|cffff0000Failed|r"; + break; + } + + out << ": " << chat->formatQuest(quest); + ai->TellMaster(out); +} + +void TalkToQuestGiverAction::TurnInQuest(Quest const* quest, WorldObject* questGiver, ostringstream& out) +{ + uint32 questID = quest->GetQuestId(); + + if (bot->GetQuestRewardStatus(questID)) + { + return; + } + + if (quest->GetRewChoiceItemsCount() == 0) + { + RewardNoItem(quest, questGiver, out); + } + else if (quest->GetRewChoiceItemsCount() == 1) + { + RewardSingleItem(quest, questGiver, out); + } + else { + { + AskToSelectReward(quest, out); + } + } +} + +void TalkToQuestGiverAction::RewardNoItem(Quest const* quest, WorldObject* questGiver, ostringstream& out) +{ + if (bot->CanRewardQuest(quest, false)) + { + bot->RewardQuest(quest, 0, questGiver, false); + out << "Completed"; + } + else + { + out << "|cffff0000Unable to turn in|r"; + } +} + +void TalkToQuestGiverAction::RewardSingleItem(Quest const* quest, WorldObject* questGiver, ostringstream& out) +{ + int index = 0; + ItemPrototype const *item = sObjectMgr.GetItemPrototype(quest->RewChoiceItemId[index]); + if (bot->CanRewardQuest(quest, index, false)) + { + bot->RewardQuest(quest, index, questGiver, true); + + out << "Rewarded " << chat->formatItem(item); + } + else + { + out << "|cffff0000Unable to turn in:|r, reward: " << chat->formatItem(item); + } +} + +void TalkToQuestGiverAction::AskToSelectReward(Quest const* quest, ostringstream& out) +{ + ostringstream msg; + msg << "Choose reward: "; + for (uint8 i=0; i < quest->GetRewChoiceItemsCount(); ++i) + { + ItemPrototype const* item = sObjectMgr.GetItemPrototype(quest->RewChoiceItemId[i]); + msg << chat->formatItem(item); + } + ai->TellMaster(msg); + + out << "Reward pending"; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.h b/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.h new file mode 100644 index 000000000..374dc69ca --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TalkToQuestGiverAction.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Action.h" +#include "QuestAction.h" + +namespace ai +{ + class TalkToQuestGiverAction : public QuestAction { + public: + TalkToQuestGiverAction(PlayerbotAI* ai) : QuestAction(ai, "talk to quest giver") {} + + protected: + virtual void ProcessQuest(Quest const* quest, WorldObject* questGiver); + + private: + void TurnInQuest(Quest const* quest, WorldObject* questGiver, ostringstream& out); + void RewardNoItem(Quest const* quest, WorldObject* questGiver, ostringstream& out); + void RewardSingleItem(Quest const* quest, WorldObject* questGiver, ostringstream& out); + void AskToSelectReward(Quest const* quest, ostringstream& out); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/TaxiAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TaxiAction.cpp new file mode 100644 index 000000000..829124204 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TaxiAction.cpp @@ -0,0 +1,53 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TaxiAction.h" +#include "../values/LastMovementValue.h" + +using namespace ai; + +bool TaxiAction::Execute(Event event) +{ + ai->RemoveShapeshift(); + + LastMovement& movement = context->GetValue("last taxi")->Get(); + + WorldPacket& p = event.getPacket(); + if (!p.empty() && p.GetOpcode() == CMSG_MOVE_SPLINE_DONE) + { + WorldPacket p1(p); + p1.rpos(0); + bot->GetSession()->HandleMoveSplineDoneOpcode(p1); + movement.taxiNodes.clear(); + movement.Set(NULL); + return true; + } + + list units = *context->GetValue >("nearest npcs"); + for (list::iterator i = units.begin(); i != units.end(); i++) + { + Creature *npc = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_FLIGHTMASTER); + if (!npc) + { + continue; + } + + if (movement.taxiNodes.empty()) + { + ostringstream out; + out << "I will order the taxi from " << npc->GetName() << ". Please start flying, then instruct me again"; + ai->TellMaster(out); + return true; + } + + if (!bot->ActivateTaxiPathTo(movement.taxiNodes, npc)) + { + ai->TellMaster("I can't fly with you"); + return false; + } + + return true; + } + + ai->TellMaster("Cannot find any flightmaster to talk"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TaxiAction.h b/src/modules/Bots/playerbot/strategy/actions/TaxiAction.h new file mode 100644 index 000000000..7e5a6d664 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TaxiAction.h @@ -0,0 +1,13 @@ +#pragma once + +namespace ai +{ + class TaxiAction : public Action { + public: + TaxiAction(PlayerbotAI* ai) : Action(ai, "taxi") {} + + public: + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/TeleportAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TeleportAction.cpp new file mode 100644 index 000000000..9a94a5e21 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TeleportAction.cpp @@ -0,0 +1,60 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TeleportAction.h" +#include "../values/LastMovementValue.h" + +using namespace ai; + +bool TeleportAction::Execute(Event event) +{ + list gos = *context->GetValue >("nearest game objects"); + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + GameObject* go = ai->GetGameObject(*i); + if (!go) + { + continue; + } + + GameObjectInfo const *goInfo = go->GetGOInfo(); + if (goInfo->type != GAMEOBJECT_TYPE_SPELLCASTER) + { + continue; + } + + uint32 spellId = goInfo->spellcaster.spellId; + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (pSpellInfo->Effect[0] != SPELL_EFFECT_TELEPORT_UNITS && pSpellInfo->Effect[1] != SPELL_EFFECT_TELEPORT_UNITS && pSpellInfo->Effect[2] != SPELL_EFFECT_TELEPORT_UNITS) + { + continue; + } + + ostringstream out; out << "Teleporting using " << goInfo->name; + ai->TellMasterNoFacing(out.str()); + + ai->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT); + + Spell *spell = new Spell(bot, pSpellInfo, false); + SpellCastTargets targets; + targets.setUnitTarget(bot); + spell->prepare(&targets, NULL); + spell->cast(true); + return true; + } + + + LastMovement& movement = context->GetValue("last area trigger")->Get(); + if (movement.lastAreaTrigger) + { + WorldPacket p(CMSG_AREATRIGGER); + p << movement.lastAreaTrigger; + p.rpos(0); + + bot->GetSession()->HandleAreaTriggerOpcode(p); + movement.lastAreaTrigger = 0; + return true; + } + + ai->TellMaster("Cannot find any portal to teleport"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TeleportAction.h b/src/modules/Bots/playerbot/strategy/actions/TeleportAction.h new file mode 100644 index 000000000..6d4a02028 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TeleportAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class TeleportAction : public Action { + public: + TeleportAction(PlayerbotAI* ai) : Action(ai, "teleport") {} + + public: + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.cpp new file mode 100644 index 000000000..08d8d331c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.cpp @@ -0,0 +1,75 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellCastFailedAction.h" + + +using namespace ai; + +bool TellCastFailedAction::Execute(Event event) +{ + WorldPacket p(event.getPacket()); + p.rpos(0); + uint8 castCount, result; + uint32 spellId; + p >> castCount >> spellId >> result; + ai->SpellInterrupted(spellId); + + if (result == SPELL_CAST_OK) + { + return false; + } + + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + ostringstream out; out << chat->formatSpell(pSpellInfo) << ": "; + switch (result) + { + case SPELL_FAILED_NOT_READY: + out << "not ready"; + break; + case SPELL_FAILED_REQUIRES_SPELL_FOCUS: + out << "requires spell focus"; + break; + case SPELL_FAILED_REQUIRES_AREA: + out << "cannot cast here"; + break; + case SPELL_FAILED_EQUIPPED_ITEM_CLASS: + out << "requires item"; + break; + case SPELL_FAILED_EQUIPPED_ITEM_CLASS_MAINHAND: + case SPELL_FAILED_EQUIPPED_ITEM_CLASS_OFFHAND: + out << "requires weapon"; + break; + case SPELL_FAILED_PREVENTED_BY_MECHANIC: + out << "interrupted"; + break; + default: + out << "cannot cast"; + } + int32 castTime = GetSpellCastTime(pSpellInfo); + if (castTime >= 2000) + { + ai->TellMasterNoFacing(out.str()); + } + return true; +} + + +bool TellSpellAction::Execute(Event event) +{ + string spell = event.getParam(); + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + if (!spellId) + { + return false; + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId ); + if (!spellInfo) + { + return false; + } + + ostringstream out; out << chat->formatSpell(spellInfo); + ai->TellMaster(out); + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.h b/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.h new file mode 100644 index 000000000..1de906a83 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellCastFailedAction.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TellSpellAction : public Action + { + public: + TellSpellAction(PlayerbotAI* ai) : Action(ai, "spell") {} + + virtual bool Execute(Event event); + }; + + class TellCastFailedAction : public Action + { + public: + TellCastFailedAction(PlayerbotAI* ai) : Action(ai, "tell cast failed") {} + + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.cpp new file mode 100644 index 000000000..786b537c4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.cpp @@ -0,0 +1,29 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellItemCountAction.h" +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool TellItemCountAction::Execute(Event event) +{ + string text = event.getParam(); + list found = parseItems(text); + map itemMap; + map soulbound; + for (list::iterator i = found.begin(); i != found.end(); i++) + { + ItemPrototype const* proto = (*i)->GetProto(); + itemMap[proto->ItemId] += (*i)->GetCount(); + soulbound[proto->ItemId] = (*i)->IsSoulBound(); + } + + ai->TellMaster("=== Inventory ==="); + for (map::iterator i = itemMap.begin(); i != itemMap.end(); ++i) + { + ItemPrototype const* proto = sItemStorage.LookupEntry(i->first); + TellItem(proto, i->second, soulbound[i->first]); + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.h b/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.h new file mode 100644 index 000000000..093b0e046 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellItemCountAction.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class TellItemCountAction : public InventoryAction { + public: + TellItemCountAction(PlayerbotAI* ai) : InventoryAction(ai, "c") {} + virtual bool Execute(Event event); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/TellLosAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TellLosAction.cpp new file mode 100644 index 000000000..da5c32560 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellLosAction.cpp @@ -0,0 +1,65 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellLosAction.h" + + +using namespace ai; + +bool TellLosAction::Execute(Event event) +{ + string param = event.getParam(); + + if (param.empty() || param == "targets") + { + list targets = *context->GetValue >("possible targets"); + ListUnits("--- Targets ---", targets); + } + + if (param.empty() || param == "npcs") + { + list npcs = *context->GetValue >("nearest npcs"); + ListUnits("--- NPCs ---", npcs); + } + + if (param.empty() || param == "corpses") + { + list corpses = *context->GetValue >("nearest corpses"); + ListUnits("--- Corpses ---", corpses); + } + + if (param.empty() || param == "gos" || param == "game objects") + { + list gos = *context->GetValue >("nearest game objects"); + ListGameObjects("--- Game objects ---", gos); + } + + return true; +} + +void TellLosAction::ListUnits(string title, list units) +{ + ai->TellMaster(title); + + for (list::iterator i = units.begin(); i != units.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (unit) + { + ai->TellMaster(unit->GetName()); + } + } + +} +void TellLosAction::ListGameObjects(string title, list gos) +{ + ai->TellMaster(title); + + for (list::iterator i = gos.begin(); i != gos.end(); i++) + { + GameObject* go = ai->GetGameObject(*i); + if (go) + { + ai->TellMaster(chat->formatGameobject(go)); + } + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellLosAction.h b/src/modules/Bots/playerbot/strategy/actions/TellLosAction.h new file mode 100644 index 000000000..15d83fbc0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellLosAction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TellLosAction : public Action { + public: + TellLosAction(PlayerbotAI* ai) : Action(ai, "los") {} + virtual bool Execute(Event event); + + private: + void ListUnits(string title, list units); + void ListGameObjects(string title, list gos); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellMasterAction.h b/src/modules/Bots/playerbot/strategy/actions/TellMasterAction.h new file mode 100644 index 000000000..4174708d5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellMasterAction.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TellMasterAction : public Action { + public: + TellMasterAction(PlayerbotAI* ai, string text) : Action(ai, "tell master"), text(text) {} + + virtual bool Execute(Event event) + { + ai->TellMaster(text); + return true; + } + + private: + string text; + }; + + class OutOfReactRangeAction : public MovementAction { + public: + OutOfReactRangeAction(PlayerbotAI* ai) : MovementAction(ai, "tell out of react range") {} + + virtual bool Execute(Event event) + { + bool canFollow = Follow(AI_VALUE(Unit*, "master target")); + if (!canFollow) + { + ai->SetNextCheckDelay(5000); + return false; + } + + ai->TellMaster("Wait for me!"); + return true; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.cpp new file mode 100644 index 000000000..56cacbb9f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellReputationAction.h" + + +using namespace ai; + +bool TellReputationAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + ObjectGuid selection = master->GetSelectionGuid(); + if (selection.IsEmpty()) + { + return false; + } + + Unit* unit = master->GetMap()->GetUnit(selection); + if (!unit) + { + return false; + } + + const FactionTemplateEntry *factionTemplate = unit->getFactionTemplateEntry(); + uint32 faction = factionTemplate->faction; + const FactionEntry* entry = sFactionStore.LookupEntry(faction); + int32 reputation = bot->GetReputationMgr().GetReputation(faction); + + ostringstream out; + out << entry->name[0] << ": "; + out << "|cff"; + ReputationRank rank = bot->GetReputationMgr().GetRank(entry); + switch (rank) { + case REP_HATED: + out << "cc2222hated"; + break; + case REP_HOSTILE: + out << "ff0000hostile"; + break; + case REP_UNFRIENDLY: + out << "ee6622unfriendly"; + break; + case REP_NEUTRAL: + out << "ffff00neutral"; + break; + case REP_FRIENDLY: + out << "00ff00friendly"; + break; + case REP_HONORED: + out << "00ff88honored"; + break; + case REP_REVERED: + out << "00ffccrevered"; + break; + case REP_EXALTED: + out << "00ffffexalted"; + break; + default: + out << "808080unknown"; + break; + } + + out << "|cffffffff"; + + int32 base = ReputationMgr::Reputation_Cap + 1; + for (int i = MAX_REPUTATION_RANK - 1; i >= rank; --i) + { + base -= ReputationMgr::PointsInRank[i]; + } + + out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")"; + ai->TellMaster(out); + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.h b/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.h new file mode 100644 index 000000000..449e67f2d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellReputationAction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TellReputationAction : public Action { + public: + TellReputationAction(PlayerbotAI* ai) : Action(ai, "reputation") {} + virtual bool Execute(Event event); + + private: + + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.cpp new file mode 100644 index 000000000..33daaf3da --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.cpp @@ -0,0 +1,58 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellTargetAction.h" +#include "ThreatManager.h" + + +using namespace ai; + +bool TellTargetAction::Execute(Event event) +{ + Unit* target = context->GetValue("current target")->Get(); + if (target) + { + ostringstream out; + out << "Attacking " << target->GetName(); + ai->TellMaster(out); + + context->GetValue("old target")->Set(target); + } + return true; +} + +bool TellAttackersAction::Execute(Event event) +{ + ai->TellMaster("--- Attackers ---"); + + list attackers = context->GetValue >("attackers")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || !unit->IsAlive()) + { + continue; + } + + ai->TellMaster(unit->GetName()); + } + + ai->TellMaster("--- Threat ---"); + HostileReference *ref = bot->GetHostileRefManager().getFirst(); + if (!ref) + { + return true; + } + + while( ref ) + { + ThreatManager *threatManager = ref->getSource(); + Unit *unit = threatManager->getOwner(); + float threat = ref->getThreat(); + + ostringstream out; out << unit->GetName() << " (" << threat << ")"; + ai->TellMaster(out); + + ref = ref->next(); + } + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.h b/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.h new file mode 100644 index 000000000..799510096 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TellTargetAction.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TellTargetAction : public Action + { + public: + TellTargetAction(PlayerbotAI* ai) : Action(ai, "tell target") {} + virtual bool Execute(Event event); + }; + + class TellAttackersAction : public Action + { + public: + TellAttackersAction(PlayerbotAI* ai) : Action(ai, "tell attackers") {} + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TradeAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TradeAction.cpp new file mode 100644 index 000000000..fd077a8b8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TradeAction.cpp @@ -0,0 +1,97 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TradeAction.h" +#include "../ItemVisitors.h" +#include "../values/ItemCountValue.h" + +using namespace ai; + +bool TradeAction::Execute(Event event) +{ + string text = event.getParam(); + uint32 copper = chat->parseMoney(text); + if (copper > 0) + { + WorldPacket* const packet = new WorldPacket(CMSG_SET_TRADE_GOLD, 4); + *packet << copper; + bot->GetSession()->QueuePacket(packet); + } + + size_t pos = text.rfind(" "); + int count = pos!=string::npos ? atoi(text.substr(pos + 1).c_str()) : 1; + list found = parseItems(text); + if (found.empty()) + { + return false; + } + + int traded = 0; + for (list::iterator i = found.begin(); i != found.end(); i++) + { + Item* item = *i; + + if (!bot->GetTrader() || item->IsInTrade()) + { + continue; + } + + int8 slot = item->CanBeTraded() ? -1 : TRADE_SLOT_NONTRADED; + if (TradeItem(*item, slot) && slot != TRADE_SLOT_NONTRADED && ++traded >= count) + { + break; + } + } + + return true; +} + +bool TradeAction::TradeItem(const Item& item, int8 slot) +{ + int8 tradeSlot = -1; + Item* itemPtr = const_cast(&item); + + TradeData* pTrade = bot->GetTradeData(); + if ((slot >= 0 && slot < TRADE_SLOT_COUNT) && pTrade->GetItem(TradeSlots(slot)) == NULL) + { + tradeSlot = slot; + } + + if (slot == TRADE_SLOT_NONTRADED) + { + pTrade->SetItem(TRADE_SLOT_NONTRADED, itemPtr); + } + else + { + for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT && tradeSlot == -1; i++) + { + if (pTrade->GetItem(TradeSlots(i)) == itemPtr) + { + tradeSlot = i; + + WorldPacket* const packet = new WorldPacket(CMSG_CLEAR_TRADE_ITEM, 1); + *packet << (uint8) tradeSlot; + bot->GetSession()->QueuePacket(packet); + pTrade->SetItem(TradeSlots(i), NULL); + return true; + } + } + + for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT && tradeSlot == -1; i++) + { + if (pTrade->GetItem(TradeSlots(i)) == NULL) + { + pTrade->SetItem(TradeSlots(i), itemPtr); + tradeSlot = i; + } + } + } + + if (tradeSlot == -1) return false; + + WorldPacket* const packet = new WorldPacket(CMSG_SET_TRADE_ITEM, 3); + *packet << (uint8) tradeSlot << (uint8) item.GetBagSlot() + << (uint8) item.GetSlot(); + bot->GetSession()->QueuePacket(packet); + return true; +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/TradeAction.h b/src/modules/Bots/playerbot/strategy/actions/TradeAction.h new file mode 100644 index 000000000..050a583de --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TradeAction.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class TradeAction : public InventoryAction { + public: + TradeAction(PlayerbotAI* ai) : InventoryAction(ai, "trade") {} + virtual bool Execute(Event event); + + private: + bool TradeItem(const Item& item, int8 slot); + + static map slots; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp new file mode 100644 index 000000000..4457511f8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp @@ -0,0 +1,240 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TradeStatusAction.h" + +#include "../ItemVisitors.h" +#include "../../PlayerbotAIConfig.h" +#include "../../../ahbot/AhBot.h" +#include "../../RandomPlayerbotMgr.h" +#include "../../GuildTaskMgr.h" +#include "../values/ItemUsageValue.h" + +using namespace ai; + + + +bool TradeStatusAction::Execute(Event event) +{ + Player* trader = bot->GetTrader(); + Player* master = GetMaster(); + if (!trader) + { + return false; + } + + if (trader != master) + { + bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader->GetObjectGuid()); + } + + if (trader != master || !ai->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, true, master)) + { + WorldPacket p; + uint32 status = 0; + p << status; + bot->GetSession()->HandleCancelTradeOpcode(p); + return false; + } + + WorldPacket p(event.getPacket()); + p.rpos(0); + uint32 status; + p >> status; + + if (status == TRADE_STATUS_TRADE_ACCEPT) + { + WorldPacket p; + uint32 status = 0; + p << status; + + if (CheckTrade()) + { + int32 botMoney = CalculateCost(bot->GetTradeData(), true); + + map itemIds; + for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) + { + Item* item = master->GetTradeData()->GetItem((TradeSlots)slot); + if (item) + { + itemIds[item->GetProto()->ItemId] += item->GetCount(); + } + } + + bot->GetSession()->HandleAcceptTradeOpcode(p); + if (bot->GetTradeData()) + { + return false; + } + + for (map::iterator i = itemIds.begin(); i != itemIds.end(); ++i) + { + sGuildTaskMgr.CheckItemTask(i->first, i->second, master, bot); + } + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + int32 lootAmount = sRandomPlayerbotMgr.GetLootAmount(bot); + sRandomPlayerbotMgr.SetLootAmount(bot, max(0, lootAmount - botMoney * 10)); + } + return true; + } + } + else if (status == TRADE_STATUS_BEGIN_TRADE) + { + if (!bot->IsInFront(trader, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT)) + { + bot->SetFacingToObject(trader); + } + BeginTrade(); + return true; + } + + return false; +} + + +void TradeStatusAction::BeginTrade() +{ + WorldPacket p; + bot->GetSession()->HandleBeginTradeOpcode(p); + + ListItemsVisitor visitor; + IterateItems(&visitor); + + ai->TellMaster("=== Inventory ==="); + TellItems(visitor.items, visitor.soulbound); + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + uint32 discount = sRandomPlayerbotMgr.GetTradeDiscount(bot); + if (discount) + { + ostringstream out; out << "Discount up to: " << chat->formatMoney(discount); + ai->TellMaster(out); + } + } +} + +bool TradeStatusAction::CheckTrade() +{ + if (!sRandomPlayerbotMgr.IsRandomBot(bot)) + { + return true; + } + + Player* master = GetMaster(); + if (!bot->GetTradeData() || !master->GetTradeData()) + { + return false; + } + + for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) + { + Item* item = bot->GetTradeData()->GetItem((TradeSlots)slot); + if (item && !auctionbot.GetSellPrice(item->GetProto())) + { + ostringstream out; + out << chat->formatItem(item->GetProto()) << " - This is not for sale"; + ai->TellMaster(out); + return false; + } + + item = master->GetTradeData()->GetItem((TradeSlots)slot); + if (item) + { + ostringstream out; out << item->GetProto()->ItemId; + ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str()); + if (!auctionbot.GetBuyPrice(item->GetProto()) || usage == ITEM_USAGE_NONE) + { + ostringstream out; + out << chat->formatItem(item->GetProto()) << " - I don't need this"; + ai->TellMaster(out); + return false; + } + } + } + + int32 botItemsMoney = CalculateCost(bot->GetTradeData(), true); + int32 botMoney = bot->GetTradeData()->GetMoney() + botItemsMoney; + int32 playerItemsMoney = CalculateCost(master->GetTradeData(), false); + int32 playerMoney = master->GetTradeData()->GetMoney() + playerItemsMoney; + + if (!botMoney && !playerMoney) + { + return true; + } + + if (!botItemsMoney && !playerItemsMoney) + { + ai->TellMaster("There are no items to trade"); + return false; + } + + int32 discount = min(botItemsMoney, (int32)sRandomPlayerbotMgr.GetTradeDiscount(bot)); + botMoney = max(0, botMoney - discount); + + if (playerMoney >= botMoney) + { + switch (urand(0, 4)) { + case 0: + ai->TellMaster("A pleasure doing business with you"); + break; + case 1: + ai->TellMaster("Fair trade"); + break; + case 2: + ai->TellMaster("Thanks"); + break; + case 3: + ai->TellMaster("Off with you"); + break; + } + return true; + } + + ostringstream out; + out << "I want " << chat->formatMoney(botMoney - playerMoney) << " for this"; + ai->TellMaster(out); + return false; +} + +int32 TradeStatusAction::CalculateCost(TradeData* data, bool sell) +{ + if (!data) + { + return 0; + } + + uint32 sum = 0; + for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) + { + Item* item = data->GetItem((TradeSlots)slot); + if (!item) + { + continue; + } + + ItemPrototype const* proto = item->GetProto(); + if (!proto) + { + continue; + } + + if (proto->Quality < ITEM_QUALITY_NORMAL) + { + return 0; + } + + if (sell) + { + sum += item->GetCount() * auctionbot.GetSellPrice(proto) * sRandomPlayerbotMgr.GetSellMultiplier(bot); + } + else + { + sum += item->GetCount() * auctionbot.GetBuyPrice(proto) * sRandomPlayerbotMgr.GetBuyMultiplier(bot); + } + } + + return sum; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.h b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.h new file mode 100644 index 000000000..7500a64b8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" +#include "QueryItemUsageAction.h" + +namespace ai +{ + class TradeStatusAction : public QueryItemUsageAction + { + public: + TradeStatusAction(PlayerbotAI* ai) : QueryItemUsageAction(ai, "accept trade") {} + virtual bool Execute(Event event); + + private: + void BeginTrade(); + bool CheckTrade(); + int32 CalculateCost(TradeData* data, bool sell); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TrainerAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TrainerAction.cpp new file mode 100644 index 000000000..cbfd9806e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TrainerAction.cpp @@ -0,0 +1,140 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TrainerAction.h" + +using namespace ai; + +void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, ostringstream& msg) +{ + if (bot->GetMoney() < cost) + { + return; + } + + bot->ModifyMoney(-int32(cost)); + bot->CastSpell(bot, tSpell->spell, true); + + msg << " - learned"; +} + +void TrainerAction::List(Creature* creature, TrainerSpellAction action, SpellIds& spells) +{ + TellHeader(creature); + + TrainerSpellData const* cSpells = creature->GetTrainerSpells(); + TrainerSpellData const* tSpells = creature->GetTrainerTemplateSpells(); + float fDiscountMod = bot->GetReputationPriceDiscount(creature); + uint32 totalCost = 0; + + TrainerSpellData const* trainer_spells = cSpells; + if (!trainer_spells) + { + trainer_spells = tSpells; + } + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell) + { + continue; + } + + uint32 reqLevel = 0; + + reqLevel = tSpell->isProvidedReqLevel ? tSpell->reqLevel : std::max(reqLevel, tSpell->reqLevel); + TrainerSpellState state = bot->GetTrainerSpellState(tSpell, reqLevel); + if (state != TRAINER_SPELL_GREEN) + { + continue; + } + + uint32 spellId = tSpell->spell; + const SpellEntry *const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod)); + totalCost += cost; + + ostringstream out; + out << chat->formatSpell(pSpellInfo) << chat->formatMoney(cost); + + if (action && (spells.empty() || spells.find(tSpell->spell) != spells.end())) + { + (this->*action)(cost, tSpell, out); + } + + ai->TellMaster(out); + } + + TellFooter(totalCost); +} + + +bool TrainerAction::Execute(Event event) +{ + string text = event.getParam(); + + Player* master = GetMaster(); + if (!master) + { + return false; + } + + Creature *creature = ai->GetCreature(master->GetSelectionGuid()); + if (!creature) + { + return false; + } + + if (!creature->IsTrainerOf(bot, false)) + { + return false; + } + + // check present spell in trainer spell list + TrainerSpellData const* cSpells = creature->GetTrainerSpells(); + TrainerSpellData const* tSpells = creature->GetTrainerTemplateSpells(); + if (!cSpells && !tSpells) + { + ai->TellMaster("No spells can be learned from this trainer"); + return false; + } + + uint32 spell = chat->parseSpell(text); + SpellIds spells; + if (spell) + { + spells.insert(spell); + } + + if (text == "learn") + { + List(creature, &TrainerAction::Learn, spells); + } + else + { + List(creature, NULL, spells); + } + + return true; +} + +void TrainerAction::TellHeader(Creature* creature) +{ + ostringstream out; out << "--- can learn from " << creature->GetName() << " ---"; + ai->TellMaster(out); +} + +void TrainerAction::TellFooter(uint32 totalCost) +{ + if (totalCost) + { + ostringstream out; out << "Total cost: " << chat->formatMoney(totalCost); + ai->TellMaster(out); + } +} diff --git a/src/modules/Bots/playerbot/strategy/actions/TrainerAction.h b/src/modules/Bots/playerbot/strategy/actions/TrainerAction.h new file mode 100644 index 000000000..7522cce69 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/TrainerAction.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class TrainerAction : public Action { + public: + TrainerAction(PlayerbotAI* ai) : Action(ai, "trainer") {} + + public: + virtual bool Execute(Event event); + + private: + typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, ostringstream& msg); + void List(Creature* creature, TrainerSpellAction action, SpellIds& spells); + void Learn(uint32 cost, TrainerSpell const* tSpell, ostringstream& msg); + void TellHeader(Creature* creature); + void TellFooter(uint32 totalCost); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/UnequipAction.cpp b/src/modules/Bots/playerbot/strategy/actions/UnequipAction.cpp new file mode 100644 index 000000000..fef53fd5f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UnequipAction.cpp @@ -0,0 +1,63 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "UnequipAction.h" + +#include "../values/ItemCountValue.h" + +using namespace ai; + +vector split(const string &s, char delim); + +bool UnequipAction::Execute(Event event) +{ + string text = event.getParam(); + + ItemIds ids = chat->parseItems(text); + if (ids.empty()) + { + vector names = split(text, ','); + for (vector::iterator i = names.begin(); i != names.end(); ++i) + { + uint32 slot = chat->parseSlot(*i); + if (slot != EQUIPMENT_SLOT_END) + { + Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (pItem) UnequipItem(*pItem); + } + } + } + else + { + for (ItemIds::iterator i =ids.begin(); i != ids.end(); i++) + { + FindItemByIdVisitor visitor(*i); + UnequipItem(&visitor); + } + } + + return true; +} + + +void UnequipAction::UnequipItem(FindItemVisitor* visitor) +{ + IterateItems(visitor, ITERATE_ALL_ITEMS); + list items = visitor->GetResult(); + if (!items.empty()) UnequipItem(**items.begin()); +} + +void UnequipAction::UnequipItem(Item& item) +{ + uint8 bagIndex = item.GetBagSlot(); + uint8 slot = item.GetSlot(); + uint8 dstBag = NULL_BAG; + + + WorldPacket* const packet = new WorldPacket(CMSG_AUTOSTORE_BAG_ITEM, 3); + *packet << bagIndex << slot << dstBag; + bot->GetSession()->QueuePacket(packet); + + ostringstream out; out << chat->formatItem(item.GetProto()) << " unequipped"; + ai->TellMaster(out); +} + diff --git a/src/modules/Bots/playerbot/strategy/actions/UnequipAction.h b/src/modules/Bots/playerbot/strategy/actions/UnequipAction.h new file mode 100644 index 000000000..fdf7ebb1c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UnequipAction.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class UnequipAction : public InventoryAction { + public: + UnequipAction(PlayerbotAI* ai) : InventoryAction(ai, "unequip") {} + virtual bool Execute(Event event); + + private: + void UnequipItem(Item& item); + void UnequipItem(FindItemVisitor* visitor); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp new file mode 100644 index 000000000..163eb6c4a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp @@ -0,0 +1,349 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "UseItemAction.h" + +#include "../../PlayerbotAIConfig.h" +#include "DBCStore.h" +using namespace ai; + +bool UseItemAction::Execute(Event event) +{ + string name = event.getParam(); + if (name.empty()) + { + name = getName(); + } + + list items = AI_VALUE2(list, "inventory items", name); + list gos = chat->parseGameobjects(name); + + if (gos.empty()) + { + if (items.size() > 1) + { + list::iterator i = items.begin(); + Item* itemTarget = *i++; + Item* item = *i; + return UseItemOnItem(item, itemTarget); + } + else if (!items.empty()) + { + return UseItemAuto(*items.begin()); + } + } + else + { + if (items.empty()) + { + return UseGameObject(*gos.begin()); + } + else + { + return UseItemOnGameObject(*items.begin(), *gos.begin()); + } + } + + ai->TellMaster("No items (or game objects) available"); + return false; +} + +bool UseItemAction::UseGameObject(ObjectGuid guid) +{ + GameObject* go = ai->GetGameObject(guid); + if (!go || !go->isSpawned()) + { + return false; + } + + go->Use(bot); + ostringstream out; out << "Using " << chat->formatGameobject(go); + ai->TellMaster(out); + return true; +} + +bool UseItemAction::UseItemAuto(Item* item) +{ + return UseItem(item, ObjectGuid(), NULL); +} + +bool UseItemAction::UseItemOnGameObject(Item* item, ObjectGuid go) +{ + return UseItem(item, go, NULL); +} + +bool UseItemAction::UseItemOnItem(Item* item, Item* itemTarget) +{ + return UseItem(item, ObjectGuid(), itemTarget); +} + +bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget) +{ + if (bot->CanUseItem(item) != EQUIP_ERR_OK) + { + return false; + } + + if (bot->IsNonMeleeSpellCasted(true)) + { + return false; + } + if (bot->IsInCombat()) + { + for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + SpellEntry const *spellInfo = sSpellStore.LookupEntry(item->GetProto()->Spells[i].SpellId); + if (spellInfo && IsNonCombatSpell(spellInfo)) + { + return false; + } + } + } + uint8 bagIndex = item->GetBagSlot(); + uint8 slot = item->GetSlot(); + uint8 cast_count = 1; + uint32 spellid = item->GetSpell(); + ObjectGuid item_guid = item->GetObjectGuid(); + uint32 glyphIndex = 0; + uint8 unk_flags = 0; + + WorldPacket* const packet = new WorldPacket(CMSG_USE_ITEM, 64); + *packet << bagIndex << slot << cast_count << spellid << item_guid << glyphIndex << unk_flags; + + bool targetSelected = false; + ostringstream out; out << "Using " << chat->formatItem(item->GetProto()); + if (item->GetProto()->Stackable) + { + uint32 count = item->GetCount(); + if (count > 1) + { + out << " (" << count << " available) "; + } + else + { + out << " (the last one!)"; + } + } + + if (goGuid) + { + GameObject* go = ai->GetGameObject(goGuid); + if (go && go->isSpawned()) + { + uint32 targetFlag = TARGET_FLAG_OBJECT; + *packet << targetFlag << goGuid.WriteAsPacked(); + out << " on " << chat->formatGameobject(go); + targetSelected = true; + } + } + + if (itemTarget) + { + if (item->GetProto()->Class == ITEM_CLASS_GEM) + { + bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true); + if (!fit) + { + ai->TellMaster("Socket does not fit"); + } + return fit; + } + else + { + uint32 targetFlag = TARGET_FLAG_ITEM; + *packet << targetFlag << itemTarget->GetPackGUID(); + out << " on " << chat->formatItem(itemTarget->GetProto()); + targetSelected = true; + } + } + + Player* master = GetMaster(); + if (!targetSelected && item->GetProto()->Class != ITEM_CLASS_CONSUMABLE && master) + { + ObjectGuid masterSelection = master->GetSelectionGuid(); + if (masterSelection) + { + Unit* unit = ai->GetUnit(masterSelection); + if (unit) + { + uint32 targetFlag = TARGET_FLAG_UNIT; + *packet << targetFlag << masterSelection.WriteAsPacked(); + out << " on " << unit->GetName(); + targetSelected = true; + } + } + } + + + if(uint32 questid = item->GetProto()->StartQuest) + { + Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid); + if (qInfo) + { + WorldPacket* const qpacket = new WorldPacket(CMSG_QUESTGIVER_ACCEPT_QUEST, 8 + 4 + 4); + *qpacket << item_guid; + *qpacket << questid; + *qpacket << uint32(0); + bot->GetSession()->QueuePacket(qpacket); // queue the qpacket to get around race condition + ostringstream out; out << "Got quest " << chat->formatQuest(qInfo); + ai->TellMaster(out); + return true; + } + } + + MotionMaster &mm = *bot->GetMotionMaster(); + mm.Clear(); + bot->clearUnitState(UNIT_STAT_CHASE); + bot->clearUnitState(UNIT_STAT_FOLLOW); + + if (bot->isMoving()) + { + return false; + } + + for (int i = 0; iGetProto()->Spells[i].SpellId; + if (!spellId) + { + continue; + } + + if (!ai->CanCastSpell(spellId, bot, false)) + { + continue; + } + + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (pSpellInfo->Targets & TARGET_FLAG_ITEM) + { + Item* itemForSpell = AI_VALUE2(Item*, "item for spell", spellId); + if (!itemForSpell) + { + continue; + } + + if (itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + continue; + } + + if (bot->GetTrader()) + { + if (selfOnly) + { + return false; + } + + *packet << (uint32)TARGET_FLAG_TRADE_ITEM << (uint8)1 << (uint64)TRADE_SLOT_NONTRADED; + targetSelected = true; + out << " on traded item"; + } + else + { + *packet << (uint32)TARGET_FLAG_ITEM; + packet->appendPackGUID(itemForSpell->GetObjectGuid()); + targetSelected = true; + out << " on " << chat->formatItem(itemForSpell->GetProto()); + } + + Spell *spell = new Spell(bot, pSpellInfo, false); + ai->WaitForSpellCast(spell); + delete spell; + } + else if (!goGuid && !itemTarget) + { + *packet << (uint32)TARGET_FLAG_SELF; + targetSelected = true; + out << " on self"; + } + break; + } + + if (!targetSelected) + { + return false; + } + + ItemPrototype const* proto = item->GetProto(); + if (proto->Class == ITEM_CLASS_CONSUMABLE && (proto->SubClass == ITEM_SUBCLASS_FOOD || proto->SubClass == ITEM_SUBCLASS_CONSUMABLE) && + (proto->Spells[0].SpellCategory == 11 || proto->Spells[0].SpellCategory == 59)) + { + if (bot->IsInCombat()) + { + return false; + } + + ai->SetNextCheckDelay(30000); + } + ai->TellMaster(out); + bot->GetSession()->QueuePacket(packet); + return true; +} +bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace) +{ + WorldPacket* const packet = new WorldPacket(CMSG_SOCKET_GEMS); + *packet << item->GetObjectGuid(); + + bool fits = false; + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot) + { + uint8 SocketColor = item->GetProto()->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color; + GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gem->GetProto()->GemProperties); + if (gemProperty && (gemProperty->color & SocketColor)) + { + if (fits) + { + *packet << ObjectGuid(); + continue; + } + + uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) + { + *packet << gem->GetObjectGuid(); + fits = true; + continue; + } + + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry || !enchantEntry->GemID) + { + *packet << gem->GetObjectGuid(); + fits = true; + continue; + } + + if (replace && enchantEntry->GemID != gem->GetProto()->ItemId) + { + *packet << gem->GetObjectGuid(); + fits = true; + continue; + } + + } + + *packet << ObjectGuid(); + } + + if (fits) + { + ostringstream out; out << "Socketing " << chat->formatItem(item->GetProto()); + out << " with " << chat->formatItem(gem->GetProto()); + ai->TellMaster(out); + + bot->GetSession()->QueuePacket(packet); + } + return fits; +} + + + +bool UseItemAction::isPossible() +{ + return getName() == "use" || AI_VALUE2(uint8, "item count", getName()) > 0; +} + +bool UseSpellItemAction::isUseful() +{ + return AI_VALUE2(bool, "spell cast useful", getName()); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h new file mode 100644 index 000000000..085c6a9f4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h @@ -0,0 +1,47 @@ +#pragma once + +#include "../Action.h" + +namespace ai +{ + class UseItemAction : public Action { + public: + UseItemAction(PlayerbotAI* ai, string name = "use", bool selfOnly = false) : Action(ai, name), selfOnly(selfOnly) {} + + public: + virtual bool Execute(Event event); + virtual bool isPossible(); + + private: + bool UseItemAuto(Item* item); + bool UseItemOnGameObject(Item* item, ObjectGuid go); + bool UseItemOnItem(Item* item, Item* itemTarget); + bool UseItem(Item* item, ObjectGuid go, Item* itemTarget); + bool UseGameObject(ObjectGuid guid); + bool SocketItem(Item * item, Item * gem, bool replace = false); + + private: + bool selfOnly; + }; + + class UseSpellItemAction : public UseItemAction { + public: + UseSpellItemAction(PlayerbotAI* ai, string name, bool selfOnly = false) : UseItemAction(ai, name, selfOnly) {} + + public: + virtual bool isUseful(); + }; + + class UseHealingPotion : public UseItemAction { + public: + UseHealingPotion(PlayerbotAI* ai) : UseItemAction(ai, "healing potion") {} + virtual bool isUseful() { return AI_VALUE2(bool, "combat", "self target"); } + }; + + class UseManaPotion : public UseItemAction + { + public: + UseManaPotion(PlayerbotAI* ai) : UseItemAction(ai, "mana potion") {} + virtual bool isUseful() { return AI_VALUE2(bool, "combat", "self target"); } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.cpp b/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.cpp new file mode 100644 index 000000000..bd9e49e49 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.cpp @@ -0,0 +1,160 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "UseMeetingStoneAction.h" +#include "../../PlayerbotAIConfig.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace MaNGOS; + +bool UseMeetingStoneAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + WorldPacket p(event.getPacket()); + p.rpos(0); + ObjectGuid guid; + p >> guid; + + if (master->GetSelectionGuid() && master->GetSelectionGuid() != bot->GetObjectGuid()) + { + return false; + } + + if (!master->GetSelectionGuid() && master->GetGroup() != bot->GetGroup()) + { + return false; + } + + if (master->IsBeingTeleported()) + { + return false; + } + + if (bot->IsInCombat()) + { + ai->TellMasterNoFacing("I am in combat"); + return false; + } + + Map* map = master->GetMap(); + if (!map) + { + return NULL; + } + + GameObject *gameObject = map->GetGameObject(guid); + if (!gameObject) + { + return false; + } + + const GameObjectInfo* goInfo = gameObject->GetGOInfo(); + if (!goInfo || goInfo->type != GAMEOBJECT_TYPE_SUMMONING_RITUAL) + { + return false; + } + + return Teleport(master, bot); +} + +class AnyGameObjectInObjectRangeCheck +{ +public: + AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(GameObject* u) + { + if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo()) + { + return true; + } + + return false; + } + +private: + WorldObject const* i_obj; + float i_range; +}; + + +bool SummonAction::Execute(Event event) +{ + Player* master = GetMaster(); + if (!master) + { + return false; + } + + if (master->GetSession()->GetSecurity() >= SEC_PLAYER) + { + return Teleport(master, bot); + } + + if (Summon(master, bot)) + { + ai->TellMasterNoFacing("Hello!"); + return true; + } + + if (Summon(bot, master)) + { + ai->TellMasterNoFacing("Welcome!"); + return true; + } + + ai->TellMasterNoFacing("There are no meeting stone nearby"); + return false; +} + + +bool SummonAction::Summon(Player *summoner, Player *player) +{ + list targets; + AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig.sightDistance); + GameObjectListSearcher searcher(targets, u_check); + Cell::VisitAllObjects((const WorldObject*)summoner, searcher, sPlayerbotAIConfig.sightDistance); + + list result; + for(list::iterator tIter = targets.begin(); tIter != targets.end(); ++tIter) + { + GameObject* go = *tIter; + if (go && go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE) + { + return Teleport(summoner, player); + } + } + + return false; +} + +bool SummonAction::Teleport(Player *summoner, Player *player) +{ + if (!summoner->IsBeingTeleported() && !player->IsBeingTeleported()) + { + float followAngle = GetFollowAngle(); + for (float angle = followAngle - M_PI; angle <= followAngle + M_PI; angle += M_PI / 4) + { + uint32 mapId = summoner->GetMapId(); + float x = summoner->GetPositionX() + cos(angle) * sPlayerbotAIConfig.followDistance; + float y = summoner->GetPositionY()+ sin(angle) * sPlayerbotAIConfig.followDistance; + float z = summoner->GetPositionZ(); + if (summoner->IsWithinLOS(x, y, z)) + { + player->GetMotionMaster()->Clear(); + player->TeleportTo(mapId, x, y, z, 0); + return true; + } + } + } + + ai->TellMasterNoFacing("Not enough place to summon"); + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.h b/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.h new file mode 100644 index 000000000..5afa72c62 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/UseMeetingStoneAction.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Action.h" +#include "MovementActions.h" + +namespace ai +{ + class SummonAction : public MovementAction + { + public: + SummonAction(PlayerbotAI* ai, string name = "summon") : MovementAction(ai, name) {} + + virtual bool Execute(Event event); + + protected: + bool Teleport(Player *summoner, Player *player); + bool Summon(Player *summoner, Player *player); + }; + + class UseMeetingStoneAction : public SummonAction + { + public: + UseMeetingStoneAction(PlayerbotAI* ai) : SummonAction(ai, "use meeting stone") {} + + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/actions/WhoAction.cpp b/src/modules/Bots/playerbot/strategy/actions/WhoAction.cpp new file mode 100644 index 000000000..426cacf19 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/WhoAction.cpp @@ -0,0 +1,150 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WhoAction.h" +#include "../../AiFactory.h" +#include "../ItemVisitors.h" +#include "../../../ahbot/AhBot.h" +#include "../../RandomPlayerbotMgr.h" + +using namespace ai; + +#ifndef WIN32 +inline int strcmpi(const char* s1, const char* s2) +{ + for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2); + { + return *s1 - *s2; + } +} +#endif + +bool WhoAction::Execute(Event event) +{ + Player* owner = event.getOwner(); + if (!owner) + { + return false; + } + + ostringstream out; + string text = event.getParam(); + if (!text.empty()) + { + out << QuerySkill(text); + + if (sRandomPlayerbotMgr.IsRandomBot(bot)) + { + out << QueryTrade(text); + } + } + else + { + out << QuerySpec(text); + } + + if (ai->GetMaster()) + { + if (!out.str().empty()) out << ", "; + { + out << "playing with " << ai->GetMaster()->GetName(); + } + } + + string tell = out.str(); + if (tell.empty()) + { + return false; + } + + // ignore random bot chat filter + bot->Whisper(tell, LANG_UNIVERSAL, owner->GetObjectGuid()); + return true; +} + + +string WhoAction::QueryTrade(string text) +{ + ostringstream out; + + list items = InventoryAction::parseItems(text); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* sell = *i; + int32 sellPrice = auctionbot.GetSellPrice(sell->GetProto()) * sRandomPlayerbotMgr.GetSellMultiplier(bot) * sell->GetCount(); + if (!sellPrice) + { + continue; + } + + out << "Selling " << chat->formatItem(sell->GetProto(), sell->GetCount()) << " for " << chat->formatMoney(sellPrice); + return out.str(); + } + + return ""; +} + +string WhoAction::QuerySkill(string text) +{ + ostringstream out; + uint32 skill = chat->parseSkill(text); + if (!skill || !ai->HasSkill((SkillType)skill)) + { + return ""; + } + + string skillName = chat->formatSkill(skill); + uint32 spellId = AI_VALUE2(uint32, "spell id", skillName); + uint16 value = bot->GetSkillValue(skill); + uint16 maxSkill = bot->GetMaxSkillValue(skill); + ObjectGuid guid = bot->GetObjectGuid(); + string data = "0"; + out << "|cFFFFFF00|Htrade:" << spellId << ":" << value << ":" << maxSkill << ":" + << std::hex << std::uppercase << guid.GetRawValue() + << std::nouppercase << std::dec << ":" << data + << "|h[" << skillName << "]|h|r" + << " |h|cff00ff00" << value << "|h|cffffffff/" + << "|h|cff00ff00" << maxSkill << "|h|cffffffff "; + + return out.str(); +} + +string WhoAction::QuerySpec(string& text) +{ + ostringstream out; + + int spec = AiFactory::GetPlayerSpecTab(bot); + out << "|h|cffffffff" << chat->formatClass(bot, spec); + out << " (|h|cff00ff00" << (uint32)bot->getLevel() << "|h|cffffffff lvl), "; + out << "|h|cff00ff00" << ai->GetEquipGearScore(bot, false, false) << "|h|cffffffff GS ("; + + ItemCountByQuality visitor; + IterateItems(&visitor, ITERATE_ITEMS_IN_EQUIP); + + bool needSlash = false; + if (visitor.count[ITEM_QUALITY_EPIC]) + { + out << "|h|cffff00ff" << visitor.count[ITEM_QUALITY_EPIC] << "|h|cffffffff"; + needSlash = true; + } + + if (visitor.count[ITEM_QUALITY_RARE]) + { + if (needSlash) out << "/"; + { + out << "|h|cff8080ff" << visitor.count[ITEM_QUALITY_RARE] << "|h|cffffffff"; + } + needSlash = true; + } + + if (visitor.count[ITEM_QUALITY_UNCOMMON]) + { + if (needSlash) out << "/"; + { + out << "|h|cff00ff00" << visitor.count[ITEM_QUALITY_UNCOMMON] << "|h|cffffffff"; + } + } + + out << ")"; + + return out.str(); +} diff --git a/src/modules/Bots/playerbot/strategy/actions/WhoAction.h b/src/modules/Bots/playerbot/strategy/actions/WhoAction.h new file mode 100644 index 000000000..a04fb4502 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/WhoAction.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../Action.h" +#include "InventoryAction.h" + +namespace ai +{ + class WhoAction : public InventoryAction { + public: + WhoAction(PlayerbotAI* ai) : InventoryAction(ai, "who") {} + + public: + virtual bool Execute(Event event); + + private: + string QueryTrade(string text); + string QuerySkill(string text); + string QuerySpec(string& text); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/actions/WorldPacketActionContext.h b/src/modules/Bots/playerbot/strategy/actions/WorldPacketActionContext.h new file mode 100644 index 000000000..e6a643bc9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/actions/WorldPacketActionContext.h @@ -0,0 +1,100 @@ +#pragma once + +#include "AcceptInvitationAction.h" +#include "PassLeadershipToMasterAction.h" +#include "TellMasterAction.h" +#include "TalkToQuestGiverAction.h" +#include "AcceptQuestAction.h" +#include "LootRollAction.h" +#include "ReviveFromCorpseAction.h" +#include "AcceptResurrectAction.h" +#include "UseMeetingStoneAction.h" +#include "AreaTriggerAction.h" +#include "CheckMountStateAction.h" +#include "RememberTaxiAction.h" +#include "TradeStatusAction.h" +#include "InventoryChangeFailureAction.h" +#include "LootAction.h" +#include "QuestAction.h" +#include "LeaveGroupAction.h" +#include "TellCastFailedAction.h" +#include "AcceptDuelAction.h" +#include "ReadyCheckAction.h" +#include "LfgActions.h" +#include "SecurityCheckAction.h" +#include "GuildAcceptAction.h" + +namespace ai +{ + class WorldPacketActionContext : public NamedObjectContext + { + public: + WorldPacketActionContext() + { + creators["accept invitation"] = &WorldPacketActionContext::accept_invitation; + creators["leader"] = &WorldPacketActionContext::pass_leadership_to_master; + creators["tell not enough money"] = &WorldPacketActionContext::tell_not_enough_money; + creators["tell not enough reputation"] = &WorldPacketActionContext::tell_not_enough_reputation; + creators["tell cannot equip"] = &WorldPacketActionContext::tell_cannot_equip; + creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest; + creators["accept quest"] = &WorldPacketActionContext::accept_quest; + creators["accept all quests"] = &WorldPacketActionContext::accept_all_quests; + creators["accept quest share"] = &WorldPacketActionContext::accept_quest_share; + creators["loot roll"] = &WorldPacketActionContext::loot_roll; + creators["revive from corpse"] = &WorldPacketActionContext::revive_from_corpse; + creators["accept resurrect"] = &WorldPacketActionContext::accept_resurrect; + creators["use meeting stone"] = &WorldPacketActionContext::use_meeting_stone; + creators["area trigger"] = &WorldPacketActionContext::area_trigger; + creators["reach area trigger"] = &WorldPacketActionContext::reach_area_trigger; + creators["check mount state"] = &WorldPacketActionContext::check_mount_state; + creators["remember taxi"] = &WorldPacketActionContext::remember_taxi; + creators["accept trade"] = &WorldPacketActionContext::accept_trade; + creators["store loot"] = &WorldPacketActionContext::store_loot; + creators["tell out of react range"] = &WorldPacketActionContext::tell_out_of_react_range; + creators["quest objective completed"] = &WorldPacketActionContext::quest_objective_completed; + creators["party command"] = &WorldPacketActionContext::party_command; + creators["tell cast failed"] = &WorldPacketActionContext::tell_cast_failed; + creators["accept duel"] = &WorldPacketActionContext::accept_duel; + creators["ready check"] = &WorldPacketActionContext::ready_check; + creators["ready check finished"] = &WorldPacketActionContext::ready_check_finished; + creators["uninvite"] = &WorldPacketActionContext::uninvite; + creators["security check"] = &WorldPacketActionContext::security_check; + creators["guild accept"] = &WorldPacketActionContext::guild_accept; + creators["inventory change failure"] = &WorldPacketActionContext::inventory_change_failure; + } + + private: + static Action* inventory_change_failure(PlayerbotAI* ai) { return new InventoryChangeFailureAction(ai); } + static Action* guild_accept(PlayerbotAI* ai) { return new GuildAcceptAction(ai); } + static Action* security_check(PlayerbotAI* ai) { return new SecurityCheckAction(ai); } + static Action* uninvite(PlayerbotAI* ai) { return new UninviteAction(ai); } + static Action* ready_check_finished(PlayerbotAI* ai) { return new FinishReadyCheckAction(ai); } + static Action* ready_check(PlayerbotAI* ai) { return new ReadyCheckAction(ai); } + static Action* accept_duel(PlayerbotAI* ai) { return new AcceptDuelAction(ai); } + static Action* tell_cast_failed(PlayerbotAI* ai) { return new TellCastFailedAction(ai); } + static Action* party_command(PlayerbotAI* ai) { return new PartyCommandAction(ai); } + static Action* quest_objective_completed(PlayerbotAI* ai) { return new QuestObjectiveCompletedAction(ai); } + static Action* store_loot(PlayerbotAI* ai) { return new StoreLootAction(ai); } + static Action* tell_out_of_react_range(PlayerbotAI* ai) { return new OutOfReactRangeAction(ai); } + static Action* accept_trade(PlayerbotAI* ai) { return new TradeStatusAction(ai); } + static Action* remember_taxi(PlayerbotAI* ai) { return new RememberTaxiAction(ai); } + static Action* check_mount_state(PlayerbotAI* ai) { return new CheckMountStateAction(ai); } + static Action* area_trigger(PlayerbotAI* ai) { return new AreaTriggerAction(ai); } + static Action* reach_area_trigger(PlayerbotAI* ai) { return new ReachAreaTriggerAction(ai); } + static Action* use_meeting_stone(PlayerbotAI* ai) { return new UseMeetingStoneAction(ai); } + static Action* accept_resurrect(PlayerbotAI* ai) { return new AcceptResurrectAction(ai); } + static Action* revive_from_corpse(PlayerbotAI* ai) { return new ReviveFromCorpseAction(ai); } + static Action* accept_invitation(PlayerbotAI* ai) { return new AcceptInvitationAction(ai); } + static Action* pass_leadership_to_master(PlayerbotAI* ai) { return new PassLeadershipToMasterAction(ai); } + static Action* tell_not_enough_money(PlayerbotAI* ai) { return new TellMasterAction(ai, "Not enough money"); } + static Action* tell_not_enough_reputation(PlayerbotAI* ai) { return new TellMasterAction(ai, "Not enough reputation"); } + static Action* tell_cannot_equip(PlayerbotAI* ai) { return new InventoryChangeFailureAction(ai); } + static Action* turn_in_quest(PlayerbotAI* ai) { return new TalkToQuestGiverAction(ai); } + static Action* accept_quest(PlayerbotAI* ai) { return new AcceptQuestAction(ai); } + static Action* accept_all_quests(PlayerbotAI* ai) { return new AcceptAllQuestsAction(ai); } + static Action* accept_quest_share(PlayerbotAI* ai) { return new AcceptQuestShareAction(ai); } + static Action* loot_roll(PlayerbotAI* ai) { return (QueryItemUsageAction*)new LootRollAction(ai); } + }; + + +}; diff --git a/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.cpp new file mode 100644 index 000000000..bf8d54429 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.cpp @@ -0,0 +1,172 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "BearTankDruidStrategy.h" + +using namespace ai; + +class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + BearTankDruidStrategyActionNodeFactory() + { + creators["melee"] = &melee; + creators["feral charge - bear"] = &feral_charge_bear; + creators["swipe (bear)"] = &swipe_bear; + creators["faerie fire (feral)"] = &faerie_fire_feral; + creators["bear form"] = &bear_form; + creators["dire bear form"] = &dire_bear_form; + creators["mangle (bear)"] = &mangle_bear; + creators["maul"] = &maul; + creators["bash"] = &bash; + creators["swipe"] = &swipe; + creators["lacerate"] = &lacerate; + creators["demoralizing roar"] = &demoralizing_roar; + } +private: + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NextAction::array(0, new NextAction("feral charge - bear"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* feral_charge_bear(PlayerbotAI* ai) + { + return new ActionNode ("feral charge - bear", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("reach melee"), NULL), + /*C*/ NULL); + } + static ActionNode* swipe_bear(PlayerbotAI* ai) + { + return new ActionNode ("swipe (bear)", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* faerie_fire_feral(PlayerbotAI* ai) + { + return new ActionNode ("faerie fire (feral)", + /*P*/ NextAction::array(0, new NextAction("feral charge - bear"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* bear_form(PlayerbotAI* ai) + { + return new ActionNode ("bear form", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* dire_bear_form(PlayerbotAI* ai) + { + return new ActionNode ("dire bear form", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("bear form"), NULL), + /*C*/ NULL); + } + static ActionNode* mangle_bear(PlayerbotAI* ai) + { + return new ActionNode ("mangle (bear)", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("lacerate"), NULL), + /*C*/ NULL); + } + static ActionNode* maul(PlayerbotAI* ai) + { + return new ActionNode ("maul", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* bash(PlayerbotAI* ai) + { + return new ActionNode ("bash", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* swipe(PlayerbotAI* ai) + { + return new ActionNode ("swipe", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* lacerate(PlayerbotAI* ai) + { + return new ActionNode ("lacerate", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("maul"), NULL), + /*C*/ NULL); + } + static ActionNode* growl(PlayerbotAI* ai) + { + return new ActionNode ("growl", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* demoralizing_roar(PlayerbotAI* ai) + { + return new ActionNode ("demoralizing roar", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } +}; + +BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* ai) : FeralDruidStrategy(ai) +{ + actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory()); +} + +NextAction** BearTankDruidStrategy::getDefaultActions() +{ + return NextAction::array(0, + new NextAction("lacerate", ACTION_NORMAL + 4), + new NextAction("mangle (bear)", ACTION_NORMAL + 3), + new NextAction("maul", ACTION_NORMAL + 2), + new NextAction("faerie fire (feral)", ACTION_NORMAL + 1), + new NextAction("melee", ACTION_NORMAL), + NULL); +} + +void BearTankDruidStrategy::InitTriggers(std::list &triggers) +{ + FeralDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "thorns", + NextAction::array(0, new NextAction("thorns", ACTION_HIGH + 9), NULL))); + + triggers.push_back(new TriggerNode( + "bear form", + NextAction::array(0, new NextAction("dire bear form", ACTION_HIGH + 8), NULL))); + + triggers.push_back(new TriggerNode( + "faerie fire (feral)", + NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH + 7), NULL))); + + triggers.push_back(new TriggerNode( + "lose aggro", + NextAction::array(0, new NextAction("growl", ACTION_HIGH + 8), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("demoralizing roar", ACTION_HIGH + 6), new NextAction("swipe (bear)", ACTION_HIGH + 6), NULL))); + + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("swipe (bear)", ACTION_HIGH + 5), NULL))); + + triggers.push_back(new TriggerNode( + "bash", + NextAction::array(0, new NextAction("bash", ACTION_INTERRUPT + 2), NULL))); + + triggers.push_back(new TriggerNode( + "bash on enemy healer", + NextAction::array(0, new NextAction("bash on enemy healer", ACTION_INTERRUPT + 1), NULL))); + +} diff --git a/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.h new file mode 100644 index 000000000..f5b200a35 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/BearTankDruidStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "FeralDruidStrategy.h" + +namespace ai +{ + class BearTankDruidStrategy : public FeralDruidStrategy + { + public: + BearTankDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bear"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.cpp new file mode 100644 index 000000000..b23b9b5a3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.cpp @@ -0,0 +1,177 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "CasterDruidStrategy.h" +#include "FeralDruidStrategy.h" + +using namespace ai; + +class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CasterDruidStrategyActionNodeFactory() + { + creators["faerie fire"] = &faerie_fire; + creators["hibernate"] = &hibernate; + creators["entangling roots"] = &entangling_roots; + creators["entangling roots on cc"] = &entangling_roots_on_cc; + creators["wrath"] = &wrath; + creators["starfall"] = &starfall; + creators["insect swarm"] = &insect_swarm; + creators["moonfire"] = &moonfire; + creators["starfire"] = &starfire; + creators["nature's grasp"] = &natures_grasp; + } +private: + static ActionNode* faerie_fire(PlayerbotAI* ai) + { + return new ActionNode ("faerie fire", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* hibernate(PlayerbotAI* ai) + { + return new ActionNode ("hibernate", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NextAction::array(0, new NextAction("entangling roots"), NULL), + /*C*/ NextAction::array(0, new NextAction("flee", 49.0f), NULL)); + } + static ActionNode* entangling_roots(PlayerbotAI* ai) + { + return new ActionNode ("entangling roots", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NextAction::array(0, new NextAction("flee", 49.0f), NULL)); + } + static ActionNode* entangling_roots_on_cc(PlayerbotAI* ai) + { + return new ActionNode ("entangling roots on cc", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* wrath(PlayerbotAI* ai) + { + return new ActionNode ("wrath", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* starfall(PlayerbotAI* ai) + { + return new ActionNode ("starfall", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NextAction::array(0, new NextAction("hurricane"), NULL), + /*C*/ NULL); + } + static ActionNode* insect_swarm(PlayerbotAI* ai) + { + return new ActionNode ("insect swarm", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* moonfire(PlayerbotAI* ai) + { + return new ActionNode ("moonfire", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* starfire(PlayerbotAI* ai) + { + return new ActionNode ("starfire", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* natures_grasp(PlayerbotAI* ai) + { + return new ActionNode ("nature's grasp", + /*P*/ NextAction::array(0, new NextAction("moonkin form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } +}; + +CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* ai) : GenericDruidStrategy(ai) +{ + actionNodeFactories.Add(new CasterDruidStrategyActionNodeFactory()); + actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory()); +} + +NextAction** CasterDruidStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 2), new NextAction("wrath", ACTION_NORMAL + 1), NULL); +} + +void CasterDruidStrategy::InitTriggers(std::list &triggers) +{ + GenericDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_MOVE), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("regrowth", ACTION_MEDIUM_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "almost full health", + NextAction::array(0, new NextAction("rejuvenation", ACTION_LIGHT_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member almost full health", + NextAction::array(0, new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 1), NULL))); + + + triggers.push_back(new TriggerNode( + "insect swarm", + NextAction::array(0, new NextAction("insect swarm", ACTION_NORMAL + 5), NULL))); + + triggers.push_back(new TriggerNode( + "moonfire", + NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), NULL))); + + triggers.push_back(new TriggerNode( + "eclipse (solar)", + NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 6), NULL))); + + triggers.push_back(new TriggerNode( + "eclipse (lunar)", + NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 6), NULL))); + + triggers.push_back(new TriggerNode( + "moonfire", + NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), NULL))); + + + + triggers.push_back(new TriggerNode( + "nature's grasp", + NextAction::array(0, new NextAction("nature's grasp", ACTION_EMERGENCY), NULL))); + + triggers.push_back(new TriggerNode( + "entangling roots", + NextAction::array(0, new NextAction("entangling roots on cc", ACTION_HIGH + 2), NULL))); +} + +void CasterDruidAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "high aoe", + NextAction::array(0, new NextAction("starfall", ACTION_HIGH + 1), NULL))); +} + +void CasterDruidDebuffStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "faerie fire", + NextAction::array(0, new NextAction("faerie fire", ACTION_HIGH), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.h new file mode 100644 index 000000000..66ac8386b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/CasterDruidStrategy.h @@ -0,0 +1,39 @@ +#pragma once + +#include "GenericDruidStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class CasterDruidStrategy : public GenericDruidStrategy + { + public: + CasterDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "caster"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } + }; + + class CasterDruidAoeStrategy : public CombatStrategy + { + public: + CasterDruidAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "caster aoe"; } + }; + + class CasterDruidDebuffStrategy : public CombatStrategy + { + public: + CasterDruidDebuffStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "caster debuff"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.cpp new file mode 100644 index 000000000..80d9779d1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.cpp @@ -0,0 +1,139 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "CatDpsDruidStrategy.h" + +using namespace ai; + +class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CatDpsDruidStrategyActionNodeFactory() + { + creators["faerie fire (feral)"] = &faerie_fire_feral; + creators["melee"] = &melee; + creators["feral charge - cat"] = &feral_charge_cat; + creators["cat form"] = &cat_form; + creators["claw"] = &claw; + creators["mangle (cat)"] = &mangle_cat; + creators["rake"] = &rake; + creators["ferocious bite"] = &ferocious_bite; + creators["rip"] = &rip; + } +private: + static ActionNode* faerie_fire_feral(PlayerbotAI* ai) + { + return new ActionNode ("faerie fire (feral)", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NextAction::array(0, new NextAction("feral charge - cat"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* feral_charge_cat(PlayerbotAI* ai) + { + return new ActionNode ("feral charge - cat", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("reach melee"), NULL), + /*C*/ NULL); + } + static ActionNode* cat_form(PlayerbotAI* ai) + { + return new ActionNode ("cat form", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* claw(PlayerbotAI* ai) + { + return new ActionNode ("claw", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* mangle_cat(PlayerbotAI* ai) + { + return new ActionNode ("mangle (cat)", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("claw"), NULL), + /*C*/ NULL); + } + static ActionNode* rake(PlayerbotAI* ai) + { + return new ActionNode ("rake", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* ferocious_bite(PlayerbotAI* ai) + { + return new ActionNode ("ferocious bite", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("rip"), NULL), + /*C*/ NULL); + } + static ActionNode* rip(PlayerbotAI* ai) + { + return new ActionNode ("rip", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } +}; + +CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* ai) : FeralDruidStrategy(ai) +{ + actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory()); +} + +NextAction** CatDpsDruidStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("mangle (cat)", ACTION_NORMAL + 1), NULL); +} + +void CatDpsDruidStrategy::InitTriggers(std::list &triggers) +{ + FeralDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "cat form", + NextAction::array(0, new NextAction("cat form", ACTION_MOVE + 2), NULL))); + + triggers.push_back(new TriggerNode( + "rake", + NextAction::array(0, new NextAction("rake", ACTION_NORMAL + 5), NULL))); + + triggers.push_back(new TriggerNode( + "combo points available", + NextAction::array(0, new NextAction("ferocious bite", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "medium threat", + NextAction::array(0, new NextAction("cower", ACTION_EMERGENCY + 1), NULL))); + + triggers.push_back(new TriggerNode( + "faerie fire (feral)", + NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "tiger's fury", + NextAction::array(0, new NextAction("tiger's fury", ACTION_EMERGENCY + 1), NULL))); + + triggers.push_back(new TriggerNode( + "entangling roots", + NextAction::array(0, new NextAction("entangling roots on cc", ACTION_HIGH + 1), NULL))); + +} + +void CatAoeDruidStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 2), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.h new file mode 100644 index 000000000..397f0b36e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/CatDpsDruidStrategy.h @@ -0,0 +1,29 @@ +#pragma once + +#include "FeralDruidStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class CatDpsDruidStrategy : public FeralDruidStrategy + { + public: + CatDpsDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cat"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } + }; + + class CatAoeDruidStrategy : public CombatStrategy + { + public: + CatAoeDruidStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cat aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidActions.cpp b/src/modules/Bots/playerbot/strategy/druid/DruidActions.cpp new file mode 100644 index 000000000..4fca07eca --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidActions.cpp @@ -0,0 +1,31 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidActions.h" + +using namespace ai; + +bool CastCasterFormAction::Execute(Event event) +{ + ai->RemoveShapeshift(); + return true; +} + +NextAction** CastAbolishPoisonAction::getAlternatives() +{ + return NextAction::merge( NextAction::array(0, new NextAction("cure poison"), NULL), CastSpellAction::getPrerequisites()); +} + +NextAction** CastAbolishPoisonOnPartyAction::getAlternatives() +{ + return NextAction::merge( NextAction::array(0, new NextAction("cure poison on party"), NULL), CastSpellAction::getPrerequisites()); +} + +Value* CastEntanglingRootsCcAction::GetTargetValue() +{ + return context->GetValue("cc target", "entangling roots"); +} + +bool CastEntanglingRootsCcAction::Execute(Event event) +{ + return ai->CastSpell("entangling roots", GetTarget()); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidActions.h b/src/modules/Bots/playerbot/strategy/druid/DruidActions.h new file mode 100644 index 000000000..898bcc997 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidActions.h @@ -0,0 +1,205 @@ +#pragma once + +#include "../actions/GenericActions.h" +#include "DruidShapeshiftActions.h" +#include "DruidBearActions.h" +#include "DruidCatActions.h" + +namespace ai +{ + class CastFaerieFireAction : public CastSpellAction + { + public: + CastFaerieFireAction(PlayerbotAI* ai) : CastSpellAction(ai, "faerie fire") {} + }; + + class CastFaerieFireFeralAction : public CastSpellAction + { + public: + CastFaerieFireFeralAction(PlayerbotAI* ai) : CastSpellAction(ai, "faerie fire (feral)") {} + }; + + class CastRejuvenationAction : public CastHealingSpellAction { + public: + CastRejuvenationAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "rejuvenation") {} + }; + + class CastRegrowthAction : public CastHealingSpellAction { + public: + CastRegrowthAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "regrowth") {} + + }; + + class CastHealingTouchAction : public CastHealingSpellAction { + public: + CastHealingTouchAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "healing touch") {} + + }; + + class CastRejuvenationOnPartyAction : public HealPartyMemberAction + { + public: + CastRejuvenationOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "rejuvenation") {} + }; + + class CastRegrowthOnPartyAction : public HealPartyMemberAction + { + public: + CastRegrowthOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "regrowth") {} + }; + + class CastHealingTouchOnPartyAction : public HealPartyMemberAction + { + public: + CastHealingTouchOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "healing touch") {} + }; + + class CastReviveAction : public ResurrectPartyMemberAction + { + public: + CastReviveAction(PlayerbotAI* ai) : ResurrectPartyMemberAction(ai, "revive") {} + + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("caster form"), NULL), ResurrectPartyMemberAction::getPrerequisites()); + } + }; + + class CastRebirthAction : public ResurrectPartyMemberAction + { + public: + CastRebirthAction(PlayerbotAI* ai) : ResurrectPartyMemberAction(ai, "rebirth") {} + + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("caster form"), NULL), ResurrectPartyMemberAction::getPrerequisites()); + } + }; + + class CastMarkOfTheWildAction : public CastBuffSpellAction { + public: + CastMarkOfTheWildAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "mark of the wild") {} + }; + + class CastMarkOfTheWildOnPartyAction : public BuffOnPartyAction { + public: + CastMarkOfTheWildOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "mark of the wild") {} + }; + + class CastSurvivalInstinctsAction : public CastBuffSpellAction { + public: + CastSurvivalInstinctsAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "survival instincts") {} + }; + + class CastThornsAction : public CastBuffSpellAction { + public: + CastThornsAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "thorns") {} + }; + + class CastWrathAction : public CastSpellAction + { + public: + CastWrathAction(PlayerbotAI* ai) : CastSpellAction(ai, "wrath") {} + }; + + class CastStarfallAction : public CastSpellAction + { + public: + CastStarfallAction(PlayerbotAI* ai) : CastSpellAction(ai, "starfall") {} + }; + + class CastHurricaneAction : public CastSpellAction + { + public: + CastHurricaneAction(PlayerbotAI* ai) : CastSpellAction(ai, "hurricane") {} + }; + + class CastMoonfireAction : public CastDebuffSpellAction + { + public: + CastMoonfireAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "moonfire") {} + }; + + class CastInsectSwarmAction : public CastDebuffSpellAction + { + public: + CastInsectSwarmAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "insect swarm") {} + }; + + class CastStarfireAction : public CastSpellAction + { + public: + CastStarfireAction(PlayerbotAI* ai) : CastSpellAction(ai, "starfire") {} + }; + + class CastEntanglingRootsAction : public CastSpellAction + { + public: + CastEntanglingRootsAction(PlayerbotAI* ai) : CastSpellAction(ai, "entangling roots") {} + }; + + class CastEntanglingRootsCcAction : public CastSpellAction + { + public: + CastEntanglingRootsCcAction(PlayerbotAI* ai) : CastSpellAction(ai, "entangling roots on cc") {} + virtual Value* GetTargetValue(); + virtual bool Execute(Event event); + }; + + class CastNaturesGraspAction : public CastBuffSpellAction + { + public: + CastNaturesGraspAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "nature's grasp") {} + }; + + class CastHibernateAction : public CastSpellAction + { + public: + CastHibernateAction(PlayerbotAI* ai) : CastSpellAction(ai, "hibernate") {} + }; + + class CastCurePoisonAction : public CastCureSpellAction + { + public: + CastCurePoisonAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cure poison") {} + }; + + class CastCurePoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastCurePoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cure poison", DISPEL_POISON) {} + }; + + class CastAbolishPoisonAction : public CastCureSpellAction + { + public: + CastAbolishPoisonAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "abolish poison") {} + virtual NextAction** getAlternatives(); + }; + + class CastAbolishPoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastAbolishPoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "abolish poison", DISPEL_POISON) {} + + virtual NextAction** getAlternatives(); + }; + + class CastBarskinAction : public CastBuffSpellAction + { + public: + CastBarskinAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "barskin") {} + }; + + class CastInnervateAction : public CastSpellAction + { + public: + CastInnervateAction(PlayerbotAI* ai) : CastSpellAction(ai, "innervate") {} + + virtual string GetTargetName() { return "self target"; } + }; + + class CastTranquilityAction : public CastAoeHealSpellAction + { + public: + CastTranquilityAction(PlayerbotAI* ai) : CastAoeHealSpellAction(ai, "tranquility") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.cpp new file mode 100644 index 000000000..0db983666 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.cpp @@ -0,0 +1,260 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidActions.h" +#include "DruidAiObjectContext.h" +#include "BearTankDruidStrategy.h" +#include "CatDpsDruidStrategy.h" +#include "CasterDruidStrategy.h" +#include "GenericDruidNonCombatStrategy.h" +#include "../NamedObjectContext.h" +#include "DruidTriggers.h" +#include "HealDruidStrategy.h" + +using namespace ai; + +namespace ai +{ + namespace druid + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &druid::StrategyFactoryInternal::nc; + creators["cat aoe"] = &druid::StrategyFactoryInternal::cat_aoe; + creators["caster aoe"] = &druid::StrategyFactoryInternal::caster_aoe; + creators["caster debuff"] = &druid::StrategyFactoryInternal::caster_debuff; + creators["dps debuff"] = &druid::StrategyFactoryInternal::caster_debuff; + creators["cure"] = &druid::StrategyFactoryInternal::cure; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new GenericDruidNonCombatStrategy(ai); } + static Strategy* cat_aoe(PlayerbotAI* ai) { return new CatAoeDruidStrategy(ai); } + static Strategy* caster_aoe(PlayerbotAI* ai) { return new CasterDruidAoeStrategy(ai); } + static Strategy* caster_debuff(PlayerbotAI* ai) { return new CasterDruidDebuffStrategy(ai); } + static Strategy* cure(PlayerbotAI* ai) { return new DruidCureStrategy(ai); } + }; + + class DruidStrategyFactoryInternal : public NamedObjectContext + { + public: + DruidStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["bear"] = &druid::DruidStrategyFactoryInternal::bear; + creators["tank"] = &druid::DruidStrategyFactoryInternal::bear; + creators["cat"] = &druid::DruidStrategyFactoryInternal::cat; + creators["caster"] = &druid::DruidStrategyFactoryInternal::caster; + creators["dps"] = &druid::DruidStrategyFactoryInternal::cat; + creators["heal"] = &druid::DruidStrategyFactoryInternal::heal; + } + + private: + static Strategy* bear(PlayerbotAI* ai) { return new BearTankDruidStrategy(ai); } + static Strategy* cat(PlayerbotAI* ai) { return new CatDpsDruidStrategy(ai); } + static Strategy* caster(PlayerbotAI* ai) { return new CasterDruidStrategy(ai); } + static Strategy* heal(PlayerbotAI* ai) { return new HealDruidStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace druid + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["thorns"] = &TriggerFactoryInternal::Thorns; + creators["bash"] = &TriggerFactoryInternal::bash; + creators["faerie fire (feral)"] = &TriggerFactoryInternal::faerie_fire_feral; + creators["faerie fire"] = &TriggerFactoryInternal::faerie_fire; + creators["insect swarm"] = &TriggerFactoryInternal::insect_swarm; + creators["moonfire"] = &TriggerFactoryInternal::moonfire; + creators["nature's grasp"] = &TriggerFactoryInternal::natures_grasp; + creators["tiger's fury"] = &TriggerFactoryInternal::tigers_fury; + creators["rake"] = &TriggerFactoryInternal::rake; + creators["mark of the wild"] = &TriggerFactoryInternal::mark_of_the_wild; + creators["mark of the wild on party"] = &TriggerFactoryInternal::mark_of_the_wild_on_party; + creators["cure poison"] = &TriggerFactoryInternal::cure_poison; + creators["party member cure poison"] = &TriggerFactoryInternal::party_member_cure_poison; + creators["entangling roots"] = &TriggerFactoryInternal::entangling_roots; + creators["bear form"] = &TriggerFactoryInternal::bear_form; + creators["cat form"] = &TriggerFactoryInternal::cat_form; + creators["tree form"] = &TriggerFactoryInternal::tree_form; + creators["eclipse (solar)"] = &TriggerFactoryInternal::eclipse_solar; + creators["eclipse (lunar)"] = &TriggerFactoryInternal::eclipse_lunar; + creators["bash on enemy healer"] = &TriggerFactoryInternal::bash_on_enemy_healer; + } + + private: + static Trigger* eclipse_solar(PlayerbotAI* ai) { return new EclipseSolarTrigger(ai); } + static Trigger* eclipse_lunar(PlayerbotAI* ai) { return new EclipseLunarTrigger(ai); } + static Trigger* Thorns(PlayerbotAI* ai) { return new ThornsTrigger(ai); } + static Trigger* bash(PlayerbotAI* ai) { return new BashInterruptSpellTrigger(ai); } + static Trigger* faerie_fire_feral(PlayerbotAI* ai) { return new FaerieFireFeralTrigger(ai); } + static Trigger* insect_swarm(PlayerbotAI* ai) { return new InsectSwarmTrigger(ai); } + static Trigger* moonfire(PlayerbotAI* ai) { return new MoonfireTrigger(ai); } + static Trigger* faerie_fire(PlayerbotAI* ai) { return new FaerieFireTrigger(ai); } + static Trigger* natures_grasp(PlayerbotAI* ai) { return new NaturesGraspTrigger(ai); } + static Trigger* tigers_fury(PlayerbotAI* ai) { return new TigersFuryTrigger(ai); } + static Trigger* rake(PlayerbotAI* ai) { return new RakeTrigger(ai); } + static Trigger* mark_of_the_wild(PlayerbotAI* ai) { return new MarkOfTheWildTrigger(ai); } + static Trigger* mark_of_the_wild_on_party(PlayerbotAI* ai) { return new MarkOfTheWildOnPartyTrigger(ai); } + static Trigger* cure_poison(PlayerbotAI* ai) { return new CurePoisonTrigger(ai); } + static Trigger* party_member_cure_poison(PlayerbotAI* ai) { return new PartyMemberCurePoisonTrigger(ai); } + static Trigger* entangling_roots(PlayerbotAI* ai) { return new EntanglingRootsTrigger(ai); } + static Trigger* bear_form(PlayerbotAI* ai) { return new BearFormTrigger(ai); } + static Trigger* cat_form(PlayerbotAI* ai) { return new CatFormTrigger(ai); } + static Trigger* tree_form(PlayerbotAI* ai) { return new TreeFormTrigger(ai); } + static Trigger* bash_on_enemy_healer(PlayerbotAI* ai) { return new BashInterruptEnemyHealerSpellTrigger(ai); } + }; + }; +}; + +namespace ai +{ + namespace druid + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["feral charge - bear"] = &AiObjectContextInternal::feral_charge_bear; + creators["feral charge - cat"] = &AiObjectContextInternal::feral_charge_cat; + creators["swipe (bear)"] = &AiObjectContextInternal::swipe_bear; + creators["faerie fire (feral)"] = &AiObjectContextInternal::faerie_fire_feral; + creators["faerie fire"] = &AiObjectContextInternal::faerie_fire; + creators["bear form"] = &AiObjectContextInternal::bear_form; + creators["dire bear form"] = &AiObjectContextInternal::dire_bear_form; + creators["moonkin form"] = &AiObjectContextInternal::moonkin_form; + creators["cat form"] = &AiObjectContextInternal::cat_form; + creators["tree form"] = &AiObjectContextInternal::tree_form; + creators["caster form"] = &AiObjectContextInternal::caster_form; + creators["mangle (bear)"] = &AiObjectContextInternal::mangle_bear; + creators["maul"] = &AiObjectContextInternal::maul; + creators["bash"] = &AiObjectContextInternal::bash; + creators["swipe"] = &AiObjectContextInternal::swipe; + creators["growl"] = &AiObjectContextInternal::growl; + creators["demoralizing roar"] = &AiObjectContextInternal::demoralizing_roar; + creators["hibernate"] = &AiObjectContextInternal::hibernate; + creators["entangling roots"] = &AiObjectContextInternal::entangling_roots; + creators["entangling roots on cc"] = &AiObjectContextInternal::entangling_roots_on_cc; + creators["wrath"] = &AiObjectContextInternal::wrath; + creators["starfall"] = &AiObjectContextInternal::starfall; + creators["insect swarm"] = &AiObjectContextInternal::insect_swarm; + creators["moonfire"] = &AiObjectContextInternal::moonfire; + creators["starfire"] = &AiObjectContextInternal::starfire; + creators["nature's grasp"] = &AiObjectContextInternal::natures_grasp; + creators["claw"] = &AiObjectContextInternal::claw; + creators["mangle (cat)"] = &AiObjectContextInternal::mangle_cat; + creators["swipe (cat)"] = &AiObjectContextInternal::swipe_cat; + creators["rake"] = &AiObjectContextInternal::rake; + creators["ferocious bite"] = &AiObjectContextInternal::ferocious_bite; + creators["rip"] = &AiObjectContextInternal::rip; + creators["cower"] = &AiObjectContextInternal::cower; + creators["survival instincts"] = &AiObjectContextInternal::survival_instincts; + creators["thorns"] = &AiObjectContextInternal::thorns; + creators["cure poison"] = &AiObjectContextInternal::cure_poison; + creators["cure poison on party"] = &AiObjectContextInternal::cure_poison_on_party; + creators["abolish poison"] = &AiObjectContextInternal::abolish_poison; + creators["abolish poison on party"] = &AiObjectContextInternal::abolish_poison_on_party; + creators["berserk"] = &AiObjectContextInternal::berserk; + creators["tiger's fury"] = &AiObjectContextInternal::tigers_fury; + creators["mark of the wild"] = &AiObjectContextInternal::mark_of_the_wild; + creators["mark of the wild on party"] = &AiObjectContextInternal::mark_of_the_wild_on_party; + creators["regrowth"] = &AiObjectContextInternal::regrowth; + creators["rejuvenation"] = &AiObjectContextInternal::rejuvenation; + creators["healing touch"] = &AiObjectContextInternal::healing_touch; + creators["regrowth on party"] = &AiObjectContextInternal::regrowth_on_party; + creators["rejuvenation on party"] = &AiObjectContextInternal::rejuvenation_on_party; + creators["healing touch on party"] = &AiObjectContextInternal::healing_touch_on_party; + creators["rebirth"] = &AiObjectContextInternal::rebirth; + creators["revive"] = &AiObjectContextInternal::revive; + creators["barskin"] = &AiObjectContextInternal::barskin; + creators["lacerate"] = &AiObjectContextInternal::lacerate; + creators["hurricane"] = &AiObjectContextInternal::hurricane; + creators["innervate"] = &AiObjectContextInternal::innervate; + creators["tranquility"] = &AiObjectContextInternal::tranquility; + creators["bash on enemy healer"] = &AiObjectContextInternal::bash_on_enemy_healer; + } + + private: + static Action* tranquility(PlayerbotAI* ai) { return new CastTranquilityAction(ai); } + static Action* feral_charge_bear(PlayerbotAI* ai) { return new CastFeralChargeBearAction(ai); } + static Action* feral_charge_cat(PlayerbotAI* ai) { return new CastFeralChargeCatAction(ai); } + static Action* swipe_bear(PlayerbotAI* ai) { return new CastSwipeBearAction(ai); } + static Action* faerie_fire_feral(PlayerbotAI* ai) { return new CastFaerieFireFeralAction(ai); } + static Action* faerie_fire(PlayerbotAI* ai) { return new CastFaerieFireAction(ai); } + static Action* bear_form(PlayerbotAI* ai) { return new CastBearFormAction(ai); } + static Action* dire_bear_form(PlayerbotAI* ai) { return new CastDireBearFormAction(ai); } + static Action* cat_form(PlayerbotAI* ai) { return new CastCatFormAction(ai); } + static Action* tree_form(PlayerbotAI* ai) { return new CastTreeFormAction(ai); } + static Action* caster_form(PlayerbotAI* ai) { return new CastCasterFormAction(ai); } + static Action* mangle_bear(PlayerbotAI* ai) { return new CastMangleBearAction(ai); } + static Action* maul(PlayerbotAI* ai) { return new CastMaulAction(ai); } + static Action* bash(PlayerbotAI* ai) { return new CastBashAction(ai); } + static Action* swipe(PlayerbotAI* ai) { return new CastSwipeAction(ai); } + static Action* growl(PlayerbotAI* ai) { return new CastGrowlAction(ai); } + static Action* demoralizing_roar(PlayerbotAI* ai) { return new CastDemoralizingRoarAction(ai); } + static Action* moonkin_form(PlayerbotAI* ai) { return new CastMoonkinFormAction(ai); } + static Action* hibernate(PlayerbotAI* ai) { return new CastHibernateAction(ai); } + static Action* entangling_roots(PlayerbotAI* ai) { return new CastEntanglingRootsAction(ai); } + static Action* entangling_roots_on_cc(PlayerbotAI* ai) { return new CastEntanglingRootsCcAction(ai); } + static Action* wrath(PlayerbotAI* ai) { return new CastWrathAction(ai); } + static Action* starfall(PlayerbotAI* ai) { return new CastStarfallAction(ai); } + static Action* insect_swarm(PlayerbotAI* ai) { return new CastInsectSwarmAction(ai); } + static Action* moonfire(PlayerbotAI* ai) { return new CastMoonfireAction(ai); } + static Action* starfire(PlayerbotAI* ai) { return new CastStarfireAction(ai); } + static Action* natures_grasp(PlayerbotAI* ai) { return new CastNaturesGraspAction(ai); } + static Action* claw(PlayerbotAI* ai) { return new CastClawAction(ai); } + static Action* mangle_cat(PlayerbotAI* ai) { return new CastMangleCatAction(ai); } + static Action* swipe_cat(PlayerbotAI* ai) { return new CastSwipeCatAction(ai); } + static Action* rake(PlayerbotAI* ai) { return new CastRakeAction(ai); } + static Action* ferocious_bite(PlayerbotAI* ai) { return new CastFerociousBiteAction(ai); } + static Action* rip(PlayerbotAI* ai) { return new CastRipAction(ai); } + static Action* cower(PlayerbotAI* ai) { return new CastCowerAction(ai); } + static Action* survival_instincts(PlayerbotAI* ai) { return new CastSurvivalInstinctsAction(ai); } + static Action* thorns(PlayerbotAI* ai) { return new CastThornsAction(ai); } + static Action* cure_poison(PlayerbotAI* ai) { return new CastCurePoisonAction(ai); } + static Action* cure_poison_on_party(PlayerbotAI* ai) { return new CastCurePoisonOnPartyAction(ai); } + static Action* abolish_poison(PlayerbotAI* ai) { return new CastAbolishPoisonAction(ai); } + static Action* abolish_poison_on_party(PlayerbotAI* ai) { return new CastAbolishPoisonOnPartyAction(ai); } + static Action* berserk(PlayerbotAI* ai) { return new CastBerserkAction(ai); } + static Action* tigers_fury(PlayerbotAI* ai) { return new CastTigersFuryAction(ai); } + static Action* mark_of_the_wild(PlayerbotAI* ai) { return new CastMarkOfTheWildAction(ai); } + static Action* mark_of_the_wild_on_party(PlayerbotAI* ai) { return new CastMarkOfTheWildOnPartyAction(ai); } + static Action* regrowth(PlayerbotAI* ai) { return new CastRegrowthAction(ai); } + static Action* rejuvenation(PlayerbotAI* ai) { return new CastRejuvenationAction(ai); } + static Action* healing_touch(PlayerbotAI* ai) { return new CastHealingTouchAction(ai); } + static Action* regrowth_on_party(PlayerbotAI* ai) { return new CastRegrowthOnPartyAction(ai); } + static Action* rejuvenation_on_party(PlayerbotAI* ai) { return new CastRejuvenationOnPartyAction(ai); } + static Action* healing_touch_on_party(PlayerbotAI* ai) { return new CastHealingTouchOnPartyAction(ai); } + static Action* rebirth(PlayerbotAI* ai) { return new CastRebirthAction(ai); } + static Action* revive(PlayerbotAI* ai) { return new CastReviveAction(ai); } + static Action* barskin(PlayerbotAI* ai) { return new CastBarskinAction(ai); } + static Action* lacerate(PlayerbotAI* ai) { return new CastLacerateAction(ai); } + static Action* hurricane(PlayerbotAI* ai) { return new CastHurricaneAction(ai); } + static Action* innervate(PlayerbotAI* ai) { return new CastInnervateAction(ai); } + static Action* bash_on_enemy_healer(PlayerbotAI* ai) { return new CastBashOnEnemyHealerAction(ai); } + }; + }; +}; + +DruidAiObjectContext::DruidAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::druid::StrategyFactoryInternal()); + strategyContexts.Add(new ai::druid::DruidStrategyFactoryInternal()); + actionContexts.Add(new ai::druid::AiObjectContextInternal()); + triggerContexts.Add(new ai::druid::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.h b/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.h new file mode 100644 index 000000000..b5d7a2cd1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class DruidAiObjectContext : public AiObjectContext + { + public: + DruidAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidBearActions.h b/src/modules/Bots/playerbot/strategy/druid/DruidBearActions.h new file mode 100644 index 000000000..dfb5dde97 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidBearActions.h @@ -0,0 +1,64 @@ +#pragma once + +namespace ai { + class CastFeralChargeBearAction : public CastReachTargetSpellAction + { + public: + CastFeralChargeBearAction(PlayerbotAI* ai) : CastReachTargetSpellAction(ai, "feral charge - bear", 1.5f) {} + }; + + class CastGrowlAction : public CastSpellAction + { + public: + CastGrowlAction(PlayerbotAI* ai) : CastSpellAction(ai, "growl") {} + }; + + class CastMaulAction : public CastMeleeSpellAction + { + public: + CastMaulAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "maul") {} + virtual bool isUseful() { return CastMeleeSpellAction::isUseful() && AI_VALUE2(uint8, "rage", "self target") >= 45; } + }; + + class CastBashAction : public CastMeleeSpellAction + { + public: + CastBashAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "bash") {} + }; + + class CastSwipeAction : public CastMeleeSpellAction + { + public: + CastSwipeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "swipe") {} + }; + + class CastDemoralizingRoarAction : public CastDebuffSpellAction + { + public: + CastDemoralizingRoarAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "demoralizing roar") {} + }; + + class CastMangleBearAction : public CastMeleeSpellAction + { + public: + CastMangleBearAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "mangle (bear)") {} + }; + + class CastSwipeBearAction : public CastMeleeSpellAction + { + public: + CastSwipeBearAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "swipe (bear)") {} + }; + + class CastLacerateAction : public CastMeleeSpellAction + { + public: + CastLacerateAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "lacerate") {} + }; + + class CastBashOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastBashOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "bash") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidCatActions.h b/src/modules/Bots/playerbot/strategy/druid/DruidCatActions.h new file mode 100644 index 000000000..d8c3ff603 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidCatActions.h @@ -0,0 +1,64 @@ +#pragma once + +namespace ai { + class CastFeralChargeCatAction : public CastReachTargetSpellAction + { + public: + CastFeralChargeCatAction(PlayerbotAI* ai) : CastReachTargetSpellAction(ai, "feral charge - cat", 1.5f) {} + }; + + class CastCowerAction : public CastBuffSpellAction + { + public: + CastCowerAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "cower") {} + }; + + + class CastBerserkAction : public CastBuffSpellAction + { + public: + CastBerserkAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "berserk") {} + }; + + class CastTigersFuryAction : public CastBuffSpellAction + { + public: + CastTigersFuryAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "tiger's fury") {} + }; + + class CastRakeAction : public CastDebuffSpellAction + { + public: + CastRakeAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "rake") {} + }; + + + class CastClawAction : public CastMeleeSpellAction { + public: + CastClawAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "claw") {} + }; + + class CastMangleCatAction : public CastMeleeSpellAction { + public: + CastMangleCatAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "mangle (cat)") {} + }; + + class CastSwipeCatAction : public CastMeleeSpellAction { + public: + CastSwipeCatAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "swipe (cat)") {} + }; + + class CastFerociousBiteAction : public CastMeleeSpellAction { + public: + CastFerociousBiteAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "ferocious bite") {} + }; + + + class CastRipAction : public CastMeleeSpellAction { + public: + CastRipAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "rip") {} + }; + + + +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.cpp b/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.cpp new file mode 100644 index 000000000..5b58a467a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "DruidActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.h b/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidShapeshiftActions.h b/src/modules/Bots/playerbot/strategy/druid/DruidShapeshiftActions.h new file mode 100644 index 000000000..831b25dbc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidShapeshiftActions.h @@ -0,0 +1,53 @@ +#pragma once + +namespace ai { + class CastBearFormAction : public CastBuffSpellAction { + public: + CastBearFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "bear form") {} + + virtual bool isPossible() { + return CastBuffSpellAction::isPossible() && !ai->HasAura("dire bear form", GetTarget()); + } + virtual bool isUseful() { + return CastBuffSpellAction::isUseful() && !ai->HasAura("dire bear form", GetTarget()); + } + }; + + class CastDireBearFormAction : public CastBuffSpellAction { + public: + CastDireBearFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "dire bear form") {} + + virtual NextAction** getAlternatives() { + return NextAction::merge(NextAction::array(0, new NextAction("bear form"), NULL), CastSpellAction::getAlternatives()); + } + }; + + class CastCatFormAction : public CastBuffSpellAction { + public: + CastCatFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "cat form") {} + }; + + class CastTreeFormAction : public CastBuffSpellAction { + public: + CastTreeFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "tree of life") {} + }; + + class CastMoonkinFormAction : public CastBuffSpellAction { + public: + CastMoonkinFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "moonkin form") {} + }; + + class CastCasterFormAction : public CastBuffSpellAction { + public: + CastCasterFormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "caster form") {} + + virtual bool isUseful() { + return ai->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form", + "flight form", "swift flight form", "moonkin form", "tree of life", NULL); + } + virtual bool isPossible() { return true; } + + virtual bool Execute(Event event); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.cpp b/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.cpp new file mode 100644 index 000000000..6fcb11394 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.cpp @@ -0,0 +1,7 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidTriggers.h" +#include "DruidActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.h b/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.h new file mode 100644 index 000000000..16255c79b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/DruidTriggers.h @@ -0,0 +1,127 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai { + class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger + { + public: + MarkOfTheWildOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "mark of the wild", 7) {} + }; + + class MarkOfTheWildTrigger : public BuffTrigger + { + public: + MarkOfTheWildTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "mark of the wild", 5) {} + }; + + class ThornsTrigger : public BuffTrigger + { + public: + ThornsTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "thorns") {} + }; + + class RakeTrigger : public DebuffTrigger + { + public: + RakeTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "rake") {} + }; + + class InsectSwarmTrigger : public DebuffTrigger + { + public: + InsectSwarmTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "insect swarm") {} + }; + + class MoonfireTrigger : public DebuffTrigger + { + public: + MoonfireTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "moonfire") {} + }; + + class FaerieFireTrigger : public DebuffTrigger + { + public: + FaerieFireTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "faerie fire") {} + }; + + class FaerieFireFeralTrigger : public DebuffTrigger + { + public: + FaerieFireFeralTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "faerie fire (feral)") {} + }; + + class BashInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + BashInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "bash") {} + }; + + class TigersFuryTrigger : public BoostTrigger + { + public: + TigersFuryTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "tiger's fury") {} + }; + + class NaturesGraspTrigger : public BoostTrigger + { + public: + NaturesGraspTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "nature's grasp") {} + }; + + class EntanglingRootsTrigger : public HasCcTargetTrigger + { + public: + EntanglingRootsTrigger(PlayerbotAI* ai) : HasCcTargetTrigger(ai, "entangling roots") {} + }; + + class CurePoisonTrigger : public NeedCureTrigger + { + public: + CurePoisonTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cure poison", DISPEL_POISON) {} + }; + + class PartyMemberCurePoisonTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCurePoisonTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cure poison", DISPEL_POISON) {} + }; + + class BearFormTrigger : public BuffTrigger + { + public: + BearFormTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "bear form") {} + virtual bool IsActive() { return !ai->HasAnyAuraOf(bot, "bear form", "dire bear form", NULL); } + }; + + class TreeFormTrigger : public BuffTrigger + { + public: + TreeFormTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "tree of life") {} + virtual bool IsActive() { return !ai->HasAura("tree of life", bot); } + }; + + class CatFormTrigger : public BuffTrigger + { + public: + CatFormTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "cat form") {} + virtual bool IsActive() { return !ai->HasAura("cat form", bot); } + }; + + class EclipseSolarTrigger : public HasAuraTrigger + { + public: + EclipseSolarTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "eclipse (solar)") {} + }; + + class EclipseLunarTrigger : public HasAuraTrigger + { + public: + EclipseLunarTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "eclipse (lunar)") {} + }; + + class BashInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger + { + public: + BashInterruptEnemyHealerSpellTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "bash") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.cpp new file mode 100644 index 000000000..f0038834e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.cpp @@ -0,0 +1,90 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "FeralDruidStrategy.h" + +using namespace ai; + +class FeralDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + FeralDruidStrategyActionNodeFactory() + { + creators["survival instincts"] = &survival_instincts; + creators["thorns"] = þs; + creators["cure poison"] = &cure_poison; + creators["cure poison on party"] = &cure_poison_on_party; + creators["abolish poison"] = &abolish_poison; + creators["abolish poison on party"] = &abolish_poison_on_party; + } +private: + static ActionNode* survival_instincts(PlayerbotAI* ai) + { + return new ActionNode ("survival instincts", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("barskin"), NULL), + /*C*/ NULL); + } + static ActionNode* thorns(PlayerbotAI* ai) + { + return new ActionNode ("thorns", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* cure_poison(PlayerbotAI* ai) + { + return new ActionNode ("cure poison", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* cure_poison_on_party(PlayerbotAI* ai) + { + return new ActionNode ("cure poison on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* abolish_poison(PlayerbotAI* ai) + { + return new ActionNode ("abolish poison", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* abolish_poison_on_party(PlayerbotAI* ai) + { + return new ActionNode ("abolish poison on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } +}; + +FeralDruidStrategy::FeralDruidStrategy(PlayerbotAI* ai) : GenericDruidStrategy(ai) +{ + actionNodeFactories.Add(new FeralDruidStrategyActionNodeFactory()); + actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory()); +} + +void FeralDruidStrategy::InitTriggers(std::list &triggers) +{ + GenericDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "not facing target", + NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL))); + + triggers.push_back(new TriggerNode( + "enemy out of melee", + NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), NULL))); + + triggers.push_back(new TriggerNode( + "enemy too close for melee", + NextAction::array(0, new NextAction("move out of enemy contact", ACTION_NORMAL + 8), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("survival instincts", ACTION_EMERGENCY + 1), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.h new file mode 100644 index 000000000..0ee4a8c07 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/FeralDruidStrategy.h @@ -0,0 +1,75 @@ +#pragma once + +#include "GenericDruidStrategy.h" +#include "DruidAiObjectContext.h" + +namespace ai +{ + class ShapeshiftDruidStrategyActionNodeFactory : public NamedObjectFactory + { + public: + ShapeshiftDruidStrategyActionNodeFactory() + { + creators["rejuvenation"] = &rejuvenation; + creators["regrowth"] = ®rowth; + creators["healing touch"] = &healing_touch; + creators["rejuvenation on party"] = &rejuvenation_on_party; + creators["regrowth on party"] = ®rowth_on_party; + creators["healing touch on party"] = &healing_touch_on_party; + } + private: + static ActionNode* regrowth(PlayerbotAI* ai) + { + return new ActionNode ("regrowth", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NextAction::array(0, new NextAction("healing touch"), NULL), + /*C*/ NextAction::array(0, new NextAction("melee", 10.0f), NULL)); + } + static ActionNode* rejuvenation(PlayerbotAI* ai) + { + return new ActionNode ("rejuvenation", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* healing_touch(PlayerbotAI* ai) + { + return new ActionNode ("healing touch", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* regrowth_on_party(PlayerbotAI* ai) + { + return new ActionNode ("regrowth on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NextAction::array(0, new NextAction("healing touch on party"), NULL), + /*C*/ NextAction::array(0, new NextAction("melee", 10.0f), NULL)); + } + static ActionNode* rejuvenation_on_party(PlayerbotAI* ai) + { + return new ActionNode ("rejuvenation on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* healing_touch_on_party(PlayerbotAI* ai) + { + return new ActionNode ("healing touch on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + }; + + class FeralDruidStrategy : public GenericDruidStrategy + { + protected: + FeralDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.cpp new file mode 100644 index 000000000..bc7d8a14e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.cpp @@ -0,0 +1,73 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "GenericDruidNonCombatStrategy.h" + +using namespace ai; + +class GenericDruidNonCombatStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericDruidNonCombatStrategyActionNodeFactory() + { + creators["mark of the wild"] = &mark_of_the_wild; + creators["mark of the wild on party"] = &mark_of_the_wild_on_party; + creators["innervate"] = &innervate; + } +private: + static ActionNode* mark_of_the_wild(PlayerbotAI* ai) + { + return new ActionNode ("mark of the wild", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* mark_of_the_wild_on_party(PlayerbotAI* ai) + { + return new ActionNode ("mark of the wild on party", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* innervate(PlayerbotAI* ai) + { + return new ActionNode ("innervate", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("drink"), NULL), + /*C*/ NULL); + } +}; + +GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericDruidNonCombatStrategyActionNodeFactory()); +} + +void GenericDruidNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "mark of the wild", + NextAction::array(0, new NextAction("mark of the wild", 12.0f), NULL))); + + triggers.push_back(new TriggerNode( + "mark of the wild on party", + NextAction::array(0, new NextAction("mark of the wild on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cure poison", + NextAction::array(0, new NextAction("abolish poison", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure poison", + NextAction::array(0, new NextAction("abolish poison on party", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member dead", + NextAction::array(0, new NextAction("revive", 22.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY + 5), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.h new file mode 100644 index 000000000..82cba1587 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/GenericDruidNonCombatStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericDruidNonCombatStrategy : public NonCombatStrategy + { + public: + GenericDruidNonCombatStrategy(PlayerbotAI* ai); + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.cpp new file mode 100644 index 000000000..11e1b9b12 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.cpp @@ -0,0 +1,135 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericDruidStrategy.h" +#include "DruidAiObjectContext.h" + +using namespace ai; + +class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericDruidStrategyActionNodeFactory() + { + creators["melee"] = &melee; + creators["caster form"] = &caster_form; + creators["cure poison"] = &cure_poison; + creators["cure poison on party"] = &cure_poison_on_party; + creators["abolish poison"] = &abolish_poison; + creators["abolish poison on party"] = &abolish_poison_on_party; + creators["rebirth"] = &rebirth; + creators["entangling roots on cc"] = &entangling_roots_on_cc; + creators["innervate"] = &innervate; + } + +private: + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* caster_form(PlayerbotAI* ai) + { + return new ActionNode ("caster form", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* cure_poison(PlayerbotAI* ai) + { + return new ActionNode ("cure poison", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* cure_poison_on_party(PlayerbotAI* ai) + { + return new ActionNode ("cure poison on party", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* abolish_poison(PlayerbotAI* ai) + { + return new ActionNode ("abolish poison", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* abolish_poison_on_party(PlayerbotAI* ai) + { + return new ActionNode ("abolish poison on party", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* rebirth(PlayerbotAI* ai) + { + return new ActionNode ("rebirth", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* entangling_roots_on_cc(PlayerbotAI* ai) + { + return new ActionNode ("entangling roots on cc", + /*P*/ NextAction::array(0, new NextAction("caster form"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* innervate(PlayerbotAI* ai) + { + return new ActionNode ("innervate", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mana potion"), NULL), + /*C*/ NULL); + } +}; + +GenericDruidStrategy::GenericDruidStrategy(PlayerbotAI* ai) : CombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericDruidStrategyActionNodeFactory()); +} + +void GenericDruidStrategy::InitTriggers(std::list &triggers) +{ + CombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("regrowth", ACTION_MEDIUM_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1), NULL))); + + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("regrowth", ACTION_CRITICAL_HEAL + 2), new NextAction("healing touch", ACTION_CRITICAL_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member critical health", + NextAction::array(0, new NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 1), new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 1), NULL))); + + + triggers.push_back(new TriggerNode( + "party member dead", + NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY + 5), NULL))); +} + +void DruidCureStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "cure poison", + NextAction::array(0, new NextAction("abolish poison", ACTION_DISPEL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure poison", + NextAction::array(0, new NextAction("abolish poison on party", ACTION_DISPEL + 1), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.h new file mode 100644 index 000000000..5d8872c3f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/GenericDruidStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class AiObjectContext; + + class GenericDruidStrategy : public CombatStrategy + { + protected: + GenericDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class DruidCureStrategy : public Strategy + { + public: + DruidCureStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cure"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.cpp b/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.cpp new file mode 100644 index 000000000..319a78ceb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.cpp @@ -0,0 +1,57 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DruidMultipliers.h" +#include "HealDruidStrategy.h" + +using namespace ai; + +class HealDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + HealDruidStrategyActionNodeFactory() + { + } +private: +}; + +HealDruidStrategy::HealDruidStrategy(PlayerbotAI* ai) : GenericDruidStrategy(ai) +{ + actionNodeFactories.Add(new HealDruidStrategyActionNodeFactory()); +} + +void HealDruidStrategy::InitTriggers(std::list &triggers) +{ + GenericDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "tree form", + NextAction::array(0, new NextAction("tree form", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("regrowth", ACTION_MEDIUM_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "almost full health", + NextAction::array(0, new NextAction("rejuvenation", ACTION_LIGHT_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member almost full health", + NextAction::array(0, new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe heal", + NextAction::array(0, new NextAction("tranquility", ACTION_MEDIUM_HEAL + 3), NULL))); + + triggers.push_back(new TriggerNode( + "entangling roots", + NextAction::array(0, new NextAction("entangling roots on cc", ACTION_HIGH + 1), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.h b/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.h new file mode 100644 index 000000000..a0440ba4b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/druid/HealDruidStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericDruidStrategy.h" + +namespace ai +{ + class HealDruidStrategy : public GenericDruidStrategy + { + public: + HealDruidStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "heal"; } + virtual int GetType() { return STRATEGY_TYPE_HEAL; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.cpp new file mode 100644 index 000000000..dc1165595 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.cpp @@ -0,0 +1,13 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AttackEnemyPlayersStrategy.h" + +using namespace ai; + +void AttackEnemyPlayersStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "enemy player is attacking", + NextAction::array(0, new NextAction("attack enemy player", 61.0f), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.h b/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.h new file mode 100644 index 000000000..cd9ce7cf9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/AttackEnemyPlayersStrategy.h @@ -0,0 +1,16 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class AttackEnemyPlayersStrategy : public NonCombatStrategy + { + public: + AttackEnemyPlayersStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "pvp"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.cpp new file mode 100644 index 000000000..9c045747f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.cpp @@ -0,0 +1,51 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CastTimeStrategy.h" +#include "../actions/GenericSpellActions.h" + +using namespace ai; + +float CastTimeMultiplier::GetValue(Action* action) +{ + if (action == NULL) return 1.0f; + + uint8 targetHealth = AI_VALUE2(uint8, "health", "current target"); + string name = action->getName(); + + if (action->GetTarget() != AI_VALUE(Unit*, "current target")) + { + return 1.0f; + } + + if (targetHealth < sPlayerbotAIConfig.lowHealth && dynamic_cast(action)) + { + uint32 spellId = AI_VALUE2(uint32, "spell id", name); + const SpellEntry* const pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) return 1.0f; + + if (spellId && pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION) + { + return 1.0f; + } + else if (spellId && pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION) + { + return 1.0f; + } + else if (spellId && GetSpellCastTime(pSpellInfo) >= 3000) + { + return 0.0f; + } + else if (spellId && GetSpellCastTime(pSpellInfo) >= 1500) + { + return 0.5f; + } + } + + return 1.0f; +} + + +void CastTimeStrategy::InitMultipliers(std::list &multipliers) +{ + multipliers.push_back(new CastTimeMultiplier(ai)); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.h b/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.h new file mode 100644 index 000000000..a483fc031 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/CastTimeStrategy.h @@ -0,0 +1,26 @@ +#pragma once + +namespace ai +{ + + class CastTimeMultiplier : public Multiplier + { + public: + CastTimeMultiplier(PlayerbotAI* ai) : Multiplier(ai, "cast time") {} + + public: + virtual float GetValue(Action* action); + }; + + class CastTimeStrategy : public Strategy + { + public: + CastTimeStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "cast time"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp new file mode 100644 index 000000000..5052014aa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -0,0 +1,189 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ChatCommandHandlerStrategy.h" + +using namespace ai; + +class ChatCommandActionNodeFactoryInternal : public NamedObjectFactory +{ +public: + ChatCommandActionNodeFactoryInternal() + { + creators["tank attack chat shortcut"] = &tank_attack_chat_shortcut; + } + +private: + static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* ai) + { + return new ActionNode ("tank attack chat shortcut", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NextAction::array(0, new NextAction("attack my target", 100.0f), NULL)); + } +}; + +void ChatCommandHandlerStrategy::InitTriggers(std::list &triggers) +{ + PassTroughStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "rep", + NextAction::array(0, new NextAction("reputation", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "q", + NextAction::array(0, + new NextAction("query quest", relevance), + new NextAction("query item usage", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "add all loot", + NextAction::array(0, new NextAction("add all loot", relevance), new NextAction("loot", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "u", + NextAction::array(0, new NextAction("use", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "c", + NextAction::array(0, new NextAction("item count", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "e", + NextAction::array(0, new NextAction("equip", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "ue", + NextAction::array(0, new NextAction("unequip", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "t", + NextAction::array(0, new NextAction("trade", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "nt", + NextAction::array(0, new NextAction("trade", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "s", + NextAction::array(0, new NextAction("sell", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "b", + NextAction::array(0, new NextAction("buy", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "r", + NextAction::array(0, new NextAction("reward", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "attack", + NextAction::array(0, new NextAction("attack my target", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "accept", + NextAction::array(0, new NextAction("accept quest", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "follow", + NextAction::array(0, new NextAction("follow chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "stay", + NextAction::array(0, new NextAction("stay chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "flee", + NextAction::array(0, new NextAction("flee chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "tank attack", + NextAction::array(0, new NextAction("tank attack chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "grind", + NextAction::array(0, new NextAction("grind chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "talk", + NextAction::array(0, new NextAction("gossip hello", relevance), new NextAction("talk to quest giver", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "cast", + NextAction::array(0, new NextAction("cast custom spell", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "revive", + NextAction::array(0, new NextAction("spirit healer", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "runaway", + NextAction::array(0, new NextAction("runaway chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "warning", + NextAction::array(0, new NextAction("runaway chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "max dps", + NextAction::array(0, new NextAction("max dps chat shortcut", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "attackers", + NextAction::array(0, new NextAction("tell attackers", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "ready", + NextAction::array(0, new NextAction("ready check", relevance), NULL))); +} + + + +ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) +{ + actionNodeFactories.Add(new ChatCommandActionNodeFactoryInternal()); + + supported.push_back("quests"); + supported.push_back("stats"); + supported.push_back("leave"); + supported.push_back("reputation"); + supported.push_back("log"); + supported.push_back("los"); + supported.push_back("drop"); + supported.push_back("share"); + supported.push_back("ll"); + supported.push_back("ss"); + supported.push_back("release"); + supported.push_back("teleport"); + supported.push_back("taxi"); + supported.push_back("repair"); + supported.push_back("talents"); + supported.push_back("spells"); + supported.push_back("co"); + supported.push_back("nc"); + supported.push_back("dead"); + supported.push_back("trainer"); + supported.push_back("chat"); + supported.push_back("home"); + supported.push_back("destroy"); + supported.push_back("reset ai"); + supported.push_back("emote"); + supported.push_back("buff"); + supported.push_back("help"); + supported.push_back("gb"); + supported.push_back("bank"); + supported.push_back("invite"); + supported.push_back("spell"); + supported.push_back("rti"); + supported.push_back("position"); + supported.push_back("summon"); + supported.push_back("who"); + supported.push_back("save mana"); + supported.push_back("formation"); + supported.push_back("sendmail"); + supported.push_back("mail"); + supported.push_back("outfit"); + supported.push_back("go"); + supported.push_back("debug"); + supported.push_back("cs"); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.h b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.h new file mode 100644 index 000000000..c8c93b2f0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ChatCommandHandlerStrategy.h @@ -0,0 +1,15 @@ +#pragma once +#include "PassTroughStrategy.h" + +namespace ai +{ + class ChatCommandHandlerStrategy : public PassTroughStrategy + { + public: + ChatCommandHandlerStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "chat"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.cpp new file mode 100644 index 000000000..d2889a1cf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.cpp @@ -0,0 +1,12 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CombatStrategy.h" + +using namespace ai; + +void CombatStrategy::InitTriggers(list &triggers) +{ + triggers.push_back(new TriggerNode( + "invalid target", + NextAction::array(0, new NextAction("drop target", 59), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.h new file mode 100644 index 000000000..475922f68 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/CombatStrategy.h @@ -0,0 +1,13 @@ +#pragma once + +namespace ai +{ + class CombatStrategy : public Strategy + { + public: + CombatStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual void InitTriggers(std::list &triggers); + virtual int GetType() { return STRATEGY_TYPE_COMBAT; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.cpp new file mode 100644 index 000000000..61b4f169c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.cpp @@ -0,0 +1,125 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ConserveManaStrategy.h" +#include "../../PlayerbotAIConfig.h" +#include "../actions/GenericSpellActions.h" +#include "../values/LastSpellCastValue.h" + +using namespace ai; + +float ConserveManaMultiplier::GetValue(Action* action) +{ + if (action == NULL) return 1.0f; + + uint8 health = AI_VALUE2(uint8, "health", "self target"); + uint8 targetHealth = AI_VALUE2(uint8, "health", "current target"); + uint8 mana = AI_VALUE2(uint8, "mana", "self target"); + bool hasMana = AI_VALUE2(bool, "has mana", "self target"); + bool mediumMana = hasMana && mana < sPlayerbotAIConfig.mediumMana; + + if (health < sPlayerbotAIConfig.lowHealth) + { + return 1.0f; + } + + if (mediumMana && dynamic_cast(action)) + { + return 0.0f; + } + + if (action->GetTarget() != AI_VALUE(Unit*, "current target")) + { + return 1.0f; + } + + CastSpellAction* spellAction = dynamic_cast(action); + if (!spellAction) + { + return 1.0f; + } + + string spell = spellAction->getName(); + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + const SpellEntry* const spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo || spellInfo->powerType != POWER_MANA) + { + return 1.0f; + } + + if (mediumMana && dynamic_cast(action)) + { + return 0.0f; + } + + if (AI_VALUE(uint8, "balance") <= 50) + { + return 1.0f; + } + + if ((mediumMana || targetHealth < sPlayerbotAIConfig.lowHealth) && dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float SaveManaMultiplier::GetValue(Action* action) +{ + if (action == NULL) + { + return 1.0f; + } + + if (action->GetTarget() != AI_VALUE(Unit*, "current target")) + { + return 1.0f; + } + + double saveLevel = AI_VALUE(double, "mana save level"); + if (saveLevel <= 1.0) + { + return 1.0f; + } + + CastSpellAction* spellAction = dynamic_cast(action); + if (!spellAction) + { + return 1.0f; + } + + string spell = spellAction->getName(); + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + const SpellEntry* const spellInfo = sSpellStore.LookupEntry(spellId); + if (!spellInfo || spellInfo->powerType != POWER_MANA) + { + return 1.0f; + } + + int32 cost = spellInfo->manaCost; + if (!cost) + { + return 1.0f; + } + + time_t lastCastTime = AI_VALUE2(time_t, "last spell cast time", spell); + if (!lastCastTime) + { + return 1.0f; + } + + time_t elapsed = time(0) - lastCastTime; + if ((double)elapsed < 10 * saveLevel) + { + return 0.0f; + } + + return 1.0f; +} + + +void ConserveManaStrategy::InitMultipliers(std::list &multipliers) +{ + multipliers.push_back(new ConserveManaMultiplier(ai)); + multipliers.push_back(new SaveManaMultiplier(ai)); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.h b/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.h new file mode 100644 index 000000000..a20693420 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ConserveManaStrategy.h @@ -0,0 +1,32 @@ +#pragma once + +namespace ai +{ + class ConserveManaMultiplier : public Multiplier + { + public: + ConserveManaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "conserve mana") {} + + public: + virtual float GetValue(Action* action); + }; + + class SaveManaMultiplier : public Multiplier + { + public: + SaveManaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "save mana") {} + + public: + virtual float GetValue(Action* action); + }; + + class ConserveManaStrategy : public Strategy + { + public: + ConserveManaStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "conserve mana"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.cpp new file mode 100644 index 000000000..b10c636eb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../Strategy.h" +#include "DeadStrategy.h" + +using namespace ai; + +void DeadStrategy::InitTriggers(std::list &triggers) +{ + PassTroughStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "dead", + NextAction::array(0, new NextAction("revive from corpse", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "resurrect request", + NextAction::array(0, new NextAction("accept resurrect", relevance), NULL))); +} + +DeadStrategy::DeadStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) +{ +} diff --git a/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.h b/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.h new file mode 100644 index 000000000..4008570dd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DeadStrategy.h @@ -0,0 +1,15 @@ +#pragma once +#include "PassTroughStrategy.h" + +namespace ai +{ + class DeadStrategy : public PassTroughStrategy + { + public: + DeadStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dead"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.cpp new file mode 100644 index 000000000..2f44540f9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.cpp @@ -0,0 +1,15 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DpsAssistStrategy.h" + +using namespace ai; + +void DpsAssistStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "not dps target active", + NextAction::array(0, new NextAction("dps assist", 60.0f), NULL))); +} + + + diff --git a/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.h b/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.h new file mode 100644 index 000000000..213c8694a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DpsAssistStrategy.h @@ -0,0 +1,16 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class DpsAssistStrategy : public NonCombatStrategy + { + public: + DpsAssistStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "dps assist"; } + virtual int GetType() { return STRATEGY_TYPE_DPS; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.cpp new file mode 100644 index 000000000..d1b1cb1f8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.cpp @@ -0,0 +1,24 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DuelStrategy.h" + +using namespace ai; + +void DuelStrategy::InitTriggers(std::list &triggers) +{ + PassTroughStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "duel requested", + NextAction::array(0, new NextAction("accept duel", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "no attackers", + NextAction::array(0, new NextAction("attack duel opponent", 70.0f), NULL))); +} + + + +DuelStrategy::DuelStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) +{ +} diff --git a/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.h b/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.h new file mode 100644 index 000000000..58284061f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/DuelStrategy.h @@ -0,0 +1,15 @@ +#pragma once +#include "PassTroughStrategy.h" + +namespace ai +{ + class DuelStrategy : public PassTroughStrategy + { + public: + DuelStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "duel"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.cpp new file mode 100644 index 000000000..56c8a447a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.cpp @@ -0,0 +1,28 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "EmoteStrategy.h" +#include "PlayerbotAIConfig.h" + +using namespace ai; + + +void EmoteStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "random", + NextAction::array(0, new NextAction("emote", 1.0f), NULL))); + + triggers.push_back(new TriggerNode( + "seldom", + NextAction::array(0, new NextAction("suggest what to do", 1.0f), NULL))); + + triggers.push_back(new TriggerNode( + "random", + NextAction::array(0, new NextAction("suggest trade", 1.0f), NULL))); + + if (sPlayerbotAIConfig.enableGreet){ + triggers.push_back(new TriggerNode( + "new player nearby", + NextAction::array(0, new NextAction("greet", 1.0f), NULL))); + } +} diff --git a/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.h b/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.h new file mode 100644 index 000000000..7acbc704b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/EmoteStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +namespace ai +{ + class EmoteStrategy : public Strategy + { + public: + EmoteStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "emote"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.cpp new file mode 100644 index 000000000..2ecbc2bb8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.cpp @@ -0,0 +1,26 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "FleeStrategy.h" + +using namespace ai; + +void FleeStrategy::InitTriggers(list &triggers) +{ + triggers.push_back(new TriggerNode( + "panic", + NextAction::array(0, new NextAction("flee", ACTION_EMERGENCY + 9), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), NULL)));} + +void FleeFromAddsStrategy::InitTriggers(list &triggers) +{ + triggers.push_back(new TriggerNode( + "has nearest adds", + NextAction::array(0, new NextAction("runaway", 50.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.h b/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.h new file mode 100644 index 000000000..4aa96b960 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/FleeStrategy.h @@ -0,0 +1,21 @@ +#pragma once + +namespace ai +{ + class FleeStrategy : public Strategy + { + public: + FleeStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "flee"; }; + }; + + class FleeFromAddsStrategy : public Strategy + { + public: + FleeFromAddsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "flee from adds"; }; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.cpp new file mode 100644 index 000000000..48e03dfd9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.cpp @@ -0,0 +1,17 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "FollowMasterStrategy.h" + +using namespace ai; + +NextAction** FollowMasterStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("follow", 1.0f), NULL); +} + +void FollowMasterStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "out of react range", + NextAction::array(0, new NextAction("tell out of react range", 10.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.h b/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.h new file mode 100644 index 000000000..a43e330c1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/FollowMasterStrategy.h @@ -0,0 +1,16 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class FollowMasterStrategy : public NonCombatStrategy + { + public: + FollowMasterStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "follow"; } + virtual NextAction** getDefaultActions(); + virtual void InitTriggers(std::list &triggers); + + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.cpp new file mode 100644 index 000000000..ed7d37448 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.cpp @@ -0,0 +1,20 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GrindingStrategy.h" + +using namespace ai; + + +NextAction** GrindingStrategy::getDefaultActions() +{ + return NULL; +} + +void GrindingStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "no target", + NextAction::array(0, + new NextAction("attack anything", 5.0f), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.h b/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.h new file mode 100644 index 000000000..ce4214070 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/GrindingStrategy.h @@ -0,0 +1,20 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class GrindingStrategy : public NonCombatStrategy + { + public: + GrindingStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "grind"; } + virtual int GetType() { return STRATEGY_TYPE_DPS; } + NextAction** getDefaultActions(); + + public: + virtual void InitTriggers(std::list &triggers); + }; + + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.cpp new file mode 100644 index 000000000..e93b78900 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.cpp @@ -0,0 +1,16 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GuardStrategy.h" + +using namespace ai; + + +NextAction** GuardStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("guard", 4.0f), NULL); +} + +void GuardStrategy::InitTriggers(std::list &triggers) +{ +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.h b/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.h new file mode 100644 index 000000000..8705ba3dc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/GuardStrategy.h @@ -0,0 +1,19 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class GuardStrategy : public NonCombatStrategy + { + public: + GuardStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "guard"; } + NextAction** getDefaultActions(); + + public: + virtual void InitTriggers(std::list &triggers); + }; + + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.cpp new file mode 100644 index 000000000..3370a65f0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.cpp @@ -0,0 +1,16 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "KiteStrategy.h" + +using namespace ai; + +KiteStrategy::KiteStrategy(PlayerbotAI* ai) : Strategy(ai) +{ +} + +void KiteStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "has aggro", + NextAction::array(0, new NextAction("runaway", 51.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.h b/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.h new file mode 100644 index 000000000..e88c7e563 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/KiteStrategy.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ai +{ + class KiteStrategy : public Strategy + { + public: + KiteStrategy(PlayerbotAI* ai); + virtual string getName() { return "kite"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.cpp new file mode 100644 index 000000000..130a34c82 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.cpp @@ -0,0 +1,40 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootNonCombatStrategy.h" + +using namespace ai; + +void LootNonCombatStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "loot available", + NextAction::array(0, new NextAction("loot", 6.0f), NULL))); + + triggers.push_back(new TriggerNode( + "far from loot target", + NextAction::array(0, new NextAction("move to loot", 7.0f), NULL))); + + triggers.push_back(new TriggerNode( + "can loot", + NextAction::array(0, new NextAction("open loot", 8.0f), NULL))); + + triggers.push_back(new TriggerNode( + "often", + NextAction::array(0, new NextAction("add all loot", 1.0f), NULL))); +} + +void GatherStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "timer", + NextAction::array(0, new NextAction("add gathering loot", 2.0f), NULL))); +} + +void RevealStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "often", + NextAction::array(0, new NextAction("reveal gathering item", 50.0f), NULL))); +} + + diff --git a/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.h new file mode 100644 index 000000000..5b1d757bb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/LootNonCombatStrategy.h @@ -0,0 +1,34 @@ +#pragma once + +namespace ai +{ + class LootNonCombatStrategy : public Strategy + { + public: + LootNonCombatStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "loot"; } + }; + + class GatherStrategy : public Strategy + { + public: + GatherStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "gather"; } + }; + + class RevealStrategy : public Strategy + { + public: + RevealStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "reveal"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.cpp new file mode 100644 index 000000000..bd3dc02e2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MeleeCombatStrategy.h" + +using namespace ai; + + +void MeleeCombatStrategy::InitTriggers(list &triggers) +{ + CombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "not facing target", + NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL))); + + triggers.push_back(new TriggerNode( + "enemy out of melee", + NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), NULL))); + + triggers.push_back(new TriggerNode( + "enemy too close for melee", + NextAction::array(0, new NextAction("move out of enemy contact", ACTION_NORMAL + 8), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.h new file mode 100644 index 000000000..eeeb90a35 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/MeleeCombatStrategy.h @@ -0,0 +1,16 @@ +#include "CombatStrategy.h" +#include "../generic/CombatStrategy.h" +#pragma once + +namespace ai +{ + class MeleeCombatStrategy : public CombatStrategy + { + public: + MeleeCombatStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + virtual void InitTriggers(std::list &triggers); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.cpp new file mode 100644 index 000000000..27ea8e13f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.cpp @@ -0,0 +1,13 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MoveRandomStrategy.h" + +using namespace ai; + +void MoveRandomStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "often", + NextAction::array(0, new NextAction("move random", 1.5f), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.h b/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.h new file mode 100644 index 000000000..ff453bf62 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/MoveRandomStrategy.h @@ -0,0 +1,16 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class MoveRandomStrategy : public NonCombatStrategy + { + public: + MoveRandomStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "move random"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.cpp new file mode 100644 index 000000000..6d00593c6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.cpp @@ -0,0 +1,28 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NonCombatStrategy.h" + +using namespace ai; + +void NonCombatStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "timer", + NextAction::array(0, new NextAction("check mount state", 1.0f), NULL))); +} + + +void LfgStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "no possible targets", + NextAction::array(0, new NextAction("lfg join", 1.0f), NULL))); + + triggers.push_back(new TriggerNode( + "lfg proposal", + NextAction::array(0, new NextAction("lfg accept", 1.0f), NULL))); + + triggers.push_back(new TriggerNode( + "lfg proposal active", + NextAction::array(0, new NextAction("lfg accept", 1.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.h new file mode 100644 index 000000000..d516f4aa0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/NonCombatStrategy.h @@ -0,0 +1,21 @@ +#pragma once + +namespace ai +{ + class NonCombatStrategy : public Strategy + { + public: + NonCombatStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual int GetType() { return STRATEGY_TYPE_NONCOMBAT; } + virtual void InitTriggers(std::list &triggers); + }; + + class LfgStrategy : public Strategy + { + public: + LfgStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual int GetType() { return STRATEGY_TYPE_NONCOMBAT; } + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "lfg"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/PassTroughStrategy.h b/src/modules/Bots/playerbot/strategy/generic/PassTroughStrategy.h new file mode 100644 index 000000000..14083d48b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/PassTroughStrategy.h @@ -0,0 +1,26 @@ +#pragma once + +namespace ai +{ + class PassTroughStrategy : public Strategy + { + public: + PassTroughStrategy(PlayerbotAI* ai, float relevance = 100.0f) : Strategy(ai), relevance(relevance) {} + + virtual void InitTriggers(std::list &triggers) + { + for (list::iterator i = supported.begin(); i != supported.end(); i++) + { + string s = i->c_str(); + + triggers.push_back(new TriggerNode( + s, + NextAction::array(0, new NextAction(s, relevance), NULL))); + } + } + + protected: + list supported; + float relevance; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.cpp new file mode 100644 index 000000000..90a22791a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.cpp @@ -0,0 +1,13 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PassiveStrategy.h" +#include "../PassiveMultiplier.h" + +using namespace ai; + + +void PassiveStrategy::InitMultipliers(std::list &multipliers) +{ + multipliers.push_back(new PassiveMultiplier(ai)); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.h b/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.h new file mode 100644 index 000000000..8e963885a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/PassiveStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +namespace ai +{ + class PassiveStrategy : public Strategy + { + public: + PassiveStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "passive"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/PullStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/PullStrategy.cpp new file mode 100644 index 000000000..6e46b00d6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/PullStrategy.cpp @@ -0,0 +1,54 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../PassiveMultiplier.h" +#include "PullStrategy.h" + +using namespace ai; + +class MagePullMultiplier : public PassiveMultiplier +{ +public: + MagePullMultiplier(PlayerbotAI* ai, string& action) : PassiveMultiplier(ai) + { + this->action = action; + } + +public: + virtual float GetValue(Action* action); + +private: + string action; +}; + +float MagePullMultiplier::GetValue(Action* action) +{ + if (!action) + { + return 1.0f; + } + + string name = action->getName(); + if (this->action == name || + name == "reach spell" || + name == "change strategy") + return 1.0f; + + return PassiveMultiplier::GetValue(action); +} + +NextAction** PullStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction(action, 105.0f), new NextAction("follow", 104.0f), new NextAction("end pull", 103.0f), NULL); +} + +void PullStrategy::InitTriggers(std::list &triggers) +{ + RangedCombatStrategy::InitTriggers(triggers); +} + +void PullStrategy::InitMultipliers(std::list &multipliers) +{ + multipliers.push_back(new MagePullMultiplier(ai, action)); + RangedCombatStrategy::InitMultipliers(multipliers); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/PullStrategy.h b/src/modules/Bots/playerbot/strategy/generic/PullStrategy.h new file mode 100644 index 000000000..946d47c9f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/PullStrategy.h @@ -0,0 +1,24 @@ +#pragma once + +#include "RangedCombatStrategy.h" + +namespace ai +{ + class PullStrategy : public RangedCombatStrategy + { + public: + PullStrategy(PlayerbotAI* ai, string action) : RangedCombatStrategy(ai) + { + this->action = action; + } + + public: + virtual void InitTriggers(std::list &triggers); + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "pull"; } + virtual NextAction** getDefaultActions(); + + private: + string action; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.cpp b/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.cpp new file mode 100644 index 000000000..a33c6244a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.cpp @@ -0,0 +1,69 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "QuestStrategies.h" + +using namespace ai; + +QuestStrategy::QuestStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) +{ + supported.push_back("accept quest"); +} + +void QuestStrategy::InitTriggers(std::list &triggers) +{ + PassTroughStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "quest share", + NextAction::array(0, new NextAction("accept quest share", relevance), NULL))); +} + + +void DefaultQuestStrategy::InitTriggers(std::list &triggers) +{ + QuestStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "use game object", + NextAction::array(0, + new NextAction("talk to quest giver", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "gossip hello", + NextAction::array(0, + new NextAction("talk to quest giver", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "complete quest", + NextAction::array(0, new NextAction("talk to quest giver", relevance), NULL))); +} + +DefaultQuestStrategy::DefaultQuestStrategy(PlayerbotAI* ai) : QuestStrategy(ai) +{ +} + + + +void AcceptAllQuestsStrategy::InitTriggers(std::list &triggers) +{ + QuestStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "use game object", + NextAction::array(0, + new NextAction("talk to quest giver", relevance), new NextAction("accept all quests", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "gossip hello", + NextAction::array(0, + new NextAction("talk to quest giver", relevance), new NextAction("accept all quests", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "complete quest", + NextAction::array(0, + new NextAction("talk to quest giver", relevance), new NextAction("accept all quests", relevance), NULL))); +} + +AcceptAllQuestsStrategy::AcceptAllQuestsStrategy(PlayerbotAI* ai) : QuestStrategy(ai) +{ +} diff --git a/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.h b/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.h new file mode 100644 index 000000000..08423dd49 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/QuestStrategies.h @@ -0,0 +1,34 @@ +#pragma once +#include "PassTroughStrategy.h" + +namespace ai +{ + class QuestStrategy : public PassTroughStrategy + { + public: + QuestStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class DefaultQuestStrategy : public QuestStrategy + { + public: + DefaultQuestStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "quest"; } + }; + + class AcceptAllQuestsStrategy : public QuestStrategy + { + public: + AcceptAllQuestsStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "accept all quests"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.cpp new file mode 100644 index 000000000..640e7cdd0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.cpp @@ -0,0 +1,39 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RacialsStrategy.h" + +using namespace ai; + + +class RacialsStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + RacialsStrategyActionNodeFactory() + { + creators["lifeblood"] = &lifeblood; + } +private: + static ActionNode* lifeblood(PlayerbotAI* ai) + { + return new ActionNode ("lifeblood", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("gift of the naaru"), NULL), + /*C*/ NULL); + } +}; + +void RacialsStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("lifeblood", 71.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("arcane torrent", ACTION_EMERGENCY + 6), NULL))); +} + +RacialsStrategy::RacialsStrategy(PlayerbotAI* ai) : Strategy(ai) +{ + actionNodeFactories.Add(new RacialsStrategyActionNodeFactory()); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.h b/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.h new file mode 100644 index 000000000..f5a6c9661 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RacialsStrategy.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ai +{ + class RacialsStrategy : public Strategy + { + public: + RacialsStrategy(PlayerbotAI* ai); + virtual string getName() { return "racials"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.cpp new file mode 100644 index 000000000..96ddb809b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.cpp @@ -0,0 +1,15 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RangedCombatStrategy.h" + +using namespace ai; + + +void RangedCombatStrategy::InitTriggers(list &triggers) +{ + CombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.h new file mode 100644 index 000000000..a00cbc1c3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RangedCombatStrategy.h @@ -0,0 +1,15 @@ +#include "CombatStrategy.h" +#pragma once + +namespace ai +{ + class RangedCombatStrategy : public CombatStrategy + { + public: + RangedCombatStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + virtual void InitTriggers(std::list &triggers); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_RANGED; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.cpp new file mode 100644 index 000000000..79b0fe503 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.cpp @@ -0,0 +1,13 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RunawayStrategy.h" + +using namespace ai; + + +void RunawayStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "enemy too close for spell", + NextAction::array(0, new NextAction("flee", 50.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.h b/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.h new file mode 100644 index 000000000..10b97e76b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/RunawayStrategy.h @@ -0,0 +1,15 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class RunawayStrategy : public NonCombatStrategy + { + public: + RunawayStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "runaway"; } + virtual void InitTriggers(std::list &triggers); + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/SayStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/SayStrategy.cpp new file mode 100644 index 000000000..143778e38 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/SayStrategy.cpp @@ -0,0 +1,29 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SayStrategy.h" + +using namespace ai; + + +void SayStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("say::critical health", 99.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("say::low health", 99.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("say::low mana", 99.0f), NULL))); + + triggers.push_back(new TriggerNode( + "tank aoe", + NextAction::array(0, new NextAction("say::taunt", 99.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("say::aoe", 99.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/SayStrategy.h b/src/modules/Bots/playerbot/strategy/generic/SayStrategy.h new file mode 100644 index 000000000..396b39bb3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/SayStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +namespace ai +{ + class SayStrategy : public Strategy + { + public: + SayStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "say"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/StayStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/StayStrategy.cpp new file mode 100644 index 000000000..291b75058 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/StayStrategy.cpp @@ -0,0 +1,11 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "StayStrategy.h" + +using namespace ai; + +NextAction** StayStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("stay", 1.0f), NULL); +} + diff --git a/src/modules/Bots/playerbot/strategy/generic/StayStrategy.h b/src/modules/Bots/playerbot/strategy/generic/StayStrategy.h new file mode 100644 index 000000000..d9f4229e5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/StayStrategy.h @@ -0,0 +1,14 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class StayStrategy : public NonCombatStrategy + { + public: + StayStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "stay"; } + virtual NextAction** getDefaultActions(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.cpp new file mode 100644 index 000000000..5a407f73d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.cpp @@ -0,0 +1,12 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TankAoeStrategy.h" + +using namespace ai; + +void TankAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "tank aoe", + NextAction::array(0, new NextAction("tank assist", 50.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.h b/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.h new file mode 100644 index 000000000..f9ed2fb73 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/TankAoeStrategy.h @@ -0,0 +1,18 @@ +#include "../generic/NonCombatStrategy.h" +#pragma once + +namespace ai +{ + class TankAoeStrategy : public NonCombatStrategy + { + public: + TankAoeStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "tank aoe"; } + virtual int GetType() { return STRATEGY_TYPE_TANK; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.cpp new file mode 100644 index 000000000..32a1e9524 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.cpp @@ -0,0 +1,13 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TellTargetStrategy.h" + +using namespace ai; + + +void TellTargetStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "target changed", + NextAction::array(0, new NextAction("tell target", 51.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.h b/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.h new file mode 100644 index 000000000..22b2e2f2e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/TellTargetStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +namespace ai +{ + class TellTargetStrategy : public Strategy + { + public: + TellTargetStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "TellTarget"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.cpp new file mode 100644 index 000000000..4b3554459 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.cpp @@ -0,0 +1,38 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ThreatStrategy.h" +#include "../../PlayerbotAIConfig.h" +#include "../actions/GenericSpellActions.h" + +using namespace ai; + +float ThreatMultiplier::GetValue(Action* action) +{ + if (action == NULL || action->getThreatType() == ACTION_THREAT_NONE) + { + return 1.0f; + } + + if (action->getThreatType() == ACTION_THREAT_AOE) + { + uint8 threat = AI_VALUE2(uint8, "threat", "aoe"); + if (threat >= 90) + { + return 0.0f; + } + } + + uint8 threat = AI_VALUE2(uint8, "threat", "current target"); + + if (threat >= 90) + { + return 0.0f; + } + + return 1.0f; +} + +void ThreatStrategy::InitMultipliers(std::list &multipliers) +{ + multipliers.push_back(new ThreatMultiplier(ai)); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.h b/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.h new file mode 100644 index 000000000..406f43320 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/ThreatStrategy.h @@ -0,0 +1,25 @@ +#pragma once + +namespace ai +{ + class ThreatMultiplier : public Multiplier + { + public: + ThreatMultiplier(PlayerbotAI* ai) : Multiplier(ai, "threat") {} + + public: + virtual float GetValue(Action* action); + }; + + class ThreatStrategy : public Strategy + { + public: + ThreatStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "threat"; } + }; + + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp new file mode 100644 index 000000000..081ed1c6e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp @@ -0,0 +1,18 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "UseFoodStrategy.h" + +using namespace ai; + +void UseFoodStrategy::InitTriggers(std::list &triggers) +{ + Strategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("food", 2.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("drink", 2.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.h b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.h new file mode 100644 index 000000000..b68ab35e7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ai +{ + class UseFoodStrategy : public Strategy + { + public: + UseFoodStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "food"; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.cpp new file mode 100644 index 000000000..4abcf746f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.cpp @@ -0,0 +1,18 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "UsePotionsStrategy.h" + +using namespace ai; + +void UsePotionsStrategy::InitTriggers(std::list &triggers) +{ + Strategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("healing potion", ACTION_MEDIUM_HEAL), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("mana potion", ACTION_EMERGENCY), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.h b/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.h new file mode 100644 index 000000000..b48f89f98 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/UsePotionsStrategy.h @@ -0,0 +1,15 @@ +#pragma once + +namespace ai +{ + class UsePotionsStrategy : public Strategy + { + public: + UsePotionsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "potions"; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.cpp new file mode 100644 index 000000000..89493871e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.cpp @@ -0,0 +1,112 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WorldPacketHandlerStrategy.h" + +using namespace ai; + +void WorldPacketHandlerStrategy::InitTriggers(std::list &triggers) +{ + PassTroughStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "group invite", + NextAction::array(0, new NextAction("accept invitation", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "group set leader", + NextAction::array(0, new NextAction("leader", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "not enough money", + NextAction::array(0, new NextAction("tell not enough money", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "not enough reputation", + NextAction::array(0, new NextAction("tell not enough reputation", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "cannot equip", + NextAction::array(0, new NextAction("tell cannot equip", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "use game object", + NextAction::array(0, + new NextAction("add loot", relevance), + new NextAction("use meeting stone", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "gossip hello", + NextAction::array(0, + new NextAction("trainer", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "activate taxi", + NextAction::array(0, new NextAction("remember taxi", relevance), new NextAction("taxi", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "taxi done", + NextAction::array(0, new NextAction("taxi", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "trade status", + NextAction::array(0, new NextAction("accept trade", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "area trigger", + NextAction::array(0, new NextAction("reach area trigger", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "within area trigger", + NextAction::array(0, new NextAction("area trigger", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "loot response", + NextAction::array(0, new NextAction("store loot", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "item push result", + NextAction::array(0, new NextAction("query item usage", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "ready check finished", + NextAction::array(0, new NextAction("finish ready check", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "often", + NextAction::array(0, new NextAction("security check", relevance), new NextAction("check mail", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "guild invite", + NextAction::array(0, new NextAction("guild accept", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "seldom", + NextAction::array(0, new NextAction("lfg leave", relevance), NULL))); + + triggers.push_back(new TriggerNode( + "no non bot players around", + NextAction::array(0, new NextAction("delay", relevance), NULL))); + +} + +WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) +{ + supported.push_back("loot roll"); + supported.push_back("check mount state"); + supported.push_back("quest objective completed"); + supported.push_back("party command"); + supported.push_back("ready check"); + supported.push_back("uninvite"); + supported.push_back("lfg role check"); + supported.push_back("lfg teleport"); + supported.push_back("random bot update"); + supported.push_back("inventory change failure"); +} + + +void ReadyCheckStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "timer", + NextAction::array(0, new NextAction("ready check", relevance), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.h b/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.h new file mode 100644 index 000000000..6a5a44d83 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/generic/WorldPacketHandlerStrategy.h @@ -0,0 +1,25 @@ +#pragma once +#include "PassTroughStrategy.h" + +namespace ai +{ + class WorldPacketHandlerStrategy : public PassTroughStrategy + { + public: + WorldPacketHandlerStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "default"; } + }; + + class ReadyCheckStrategy : public PassTroughStrategy + { + public: + ReadyCheckStrategy(PlayerbotAI* ai) : PassTroughStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "ready check"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.cpp b/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.cpp new file mode 100644 index 000000000..329f4fdfc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.cpp @@ -0,0 +1,122 @@ +#include "botpch.h" +#include "../../playerbot.h" + +#include "HunterMultipliers.h" +#include "DpsHunterStrategy.h" + +using namespace ai; + +class DpsHunterStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + DpsHunterStrategyActionNodeFactory() + { + creators["aimed shot"] = &aimed_shot; + creators["chimera shot"] = &chimera_shot; + creators["explosive shot"] = &explosive_shot; + creators["concussive shot"] = &concussive_shot; + creators["viper sting"] = &viper_sting; + } +private: + static ActionNode* viper_sting(PlayerbotAI* ai) + { + return new ActionNode ("viper sting", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mana potion", 10.0f), NULL), + /*C*/ NULL); + } + static ActionNode* aimed_shot(PlayerbotAI* ai) + { + return new ActionNode ("aimed shot", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("chimera shot", 10.0f), NULL), + /*C*/ NULL); + } + static ActionNode* chimera_shot(PlayerbotAI* ai) + { + return new ActionNode ("chimera shot", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("arcane shot", 10.0f), NULL), + /*C*/ NULL); + } + static ActionNode* explosive_shot(PlayerbotAI* ai) + { + return new ActionNode ("explosive shot", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("aimed shot"), NULL), + /*C*/ NULL); + } + static ActionNode* concussive_shot(PlayerbotAI* ai) + { + return new ActionNode ("concussive shot", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + +}; + +DpsHunterStrategy::DpsHunterStrategy(PlayerbotAI* ai) : GenericHunterStrategy(ai) +{ + actionNodeFactories.Add(new DpsHunterStrategyActionNodeFactory()); +} + +NextAction** DpsHunterStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("explosive shot", 11.0f), new NextAction("auto shot", 10.0f), NULL); +} + +void DpsHunterStrategy::InitTriggers(std::list &triggers) +{ + GenericHunterStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "black arrow", + NextAction::array(0, new NextAction("black arrow", 51.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("viper sting", ACTION_EMERGENCY + 5), NULL))); + + triggers.push_back(new TriggerNode( + "no pet", + NextAction::array(0, new NextAction("call pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hunters pet low health", + NextAction::array(0, new NextAction("mend pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hunter's mark", + NextAction::array(0, new NextAction("hunter's mark", 52.0f), NULL))); + + triggers.push_back(new TriggerNode( + "freezing trap", + NextAction::array(0, new NextAction("freezing trap", 83.0f), NULL))); + + triggers.push_back(new TriggerNode( + "concussive shot on snare target", + NextAction::array(0, new NextAction("concussive shot", 83.0f), NULL))); +} + +void DpsAoeHunterStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("multi-shot", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "high aoe", + NextAction::array(0, new NextAction("volley", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "serpent sting on attacker", + NextAction::array(0, new NextAction("serpent sting on attacker", 49.0f), NULL))); +} + +void DpsHunterDebuffStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "no stings", + NextAction::array(0, new NextAction("serpent sting", 50.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.h b/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.h new file mode 100644 index 000000000..4992e0e15 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/DpsHunterStrategy.h @@ -0,0 +1,39 @@ +#pragma once + +#include "GenericHunterStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class DpsHunterStrategy : public GenericHunterStrategy + { + public: + DpsHunterStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps"; } + virtual NextAction** getDefaultActions(); + + }; + + class DpsAoeHunterStrategy : public CombatStrategy + { + public: + DpsAoeHunterStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "aoe"; } + }; + + class DpsHunterDebuffStrategy : public CombatStrategy + { + public: + DpsHunterDebuffStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps debuff"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.cpp new file mode 100644 index 000000000..887996b1b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.cpp @@ -0,0 +1,62 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HunterMultipliers.h" +#include "GenericHunterNonCombatStrategy.h" + +using namespace ai; + +class GenericHunterNonCombatStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericHunterNonCombatStrategyActionNodeFactory() + { + creators["rapid fire"] = &rapid_fire; + creators["boost"] = &rapid_fire; + creators["aspect of the pack"] = &aspect_of_the_pack; + } +private: + static ActionNode* rapid_fire(PlayerbotAI* ai) + { + return new ActionNode ("rapid fire", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("readiness"), NULL), + /*C*/ NULL); + } + static ActionNode* aspect_of_the_pack(PlayerbotAI* ai) + { + return new ActionNode ("aspect of the pack", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("aspect of the cheetah"), NULL), + /*C*/ NULL); + } +}; + +GenericHunterNonCombatStrategy::GenericHunterNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericHunterNonCombatStrategyActionNodeFactory()); +} + +void GenericHunterNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "trueshot aura", + NextAction::array(0, new NextAction("trueshot aura", 2.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no pet", + NextAction::array(0, new NextAction("call pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hunters pet dead", + NextAction::array(0, new NextAction("revive pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hunters pet low health", + NextAction::array(0, new NextAction("mend pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "pet not happy", + NextAction::array(0, new NextAction("feed pet", 60.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.h new file mode 100644 index 000000000..d01486d6d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterNonCombatStrategy.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericHunterNonCombatStrategy : public NonCombatStrategy + { + public: + GenericHunterNonCombatStrategy(PlayerbotAI* ai); + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.cpp b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.cpp new file mode 100644 index 000000000..04ffab681 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.cpp @@ -0,0 +1,70 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericHunterStrategy.h" +#include "HunterAiObjectContext.h" + +using namespace ai; + +class GenericHunterStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericHunterStrategyActionNodeFactory() + { + creators["rapid fire"] = &rapid_fire; + creators["boost"] = &rapid_fire; + creators["aspect of the pack"] = &aspect_of_the_pack; + creators["feign death"] = &feign_death; + } +private: + static ActionNode* rapid_fire(PlayerbotAI* ai) + { + return new ActionNode ("rapid fire", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("readiness"), NULL), + /*C*/ NULL); + } + static ActionNode* aspect_of_the_pack(PlayerbotAI* ai) + { + return new ActionNode ("aspect of the pack", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("aspect of the cheetah"), NULL), + /*C*/ NULL); + } + static ActionNode* feign_death(PlayerbotAI* ai) + { + return new ActionNode ("feign death", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("flee"), NULL), + /*C*/ NULL); + } +}; + +GenericHunterStrategy::GenericHunterStrategy(PlayerbotAI* ai) : RangedCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericHunterStrategyActionNodeFactory()); +} + +void GenericHunterStrategy::InitTriggers(std::list &triggers) +{ + RangedCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy too close for shoot", + NextAction::array(0, new NextAction("flee", 49.0f), NULL))); + + triggers.push_back(new TriggerNode( + "enemy is close", + NextAction::array(0, new NextAction("wing clip", 50.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium threat", + NextAction::array(0, new NextAction("feign death", 52.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hunters pet low health", + NextAction::array(0, new NextAction("mend pet", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "rapid fire", + NextAction::array(0, new NextAction("rapid fire", 55.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.h b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.h new file mode 100644 index 000000000..f2db28cea --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/GenericHunterStrategy.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/RangedCombatStrategy.h" + +namespace ai +{ + class AiObjectContext; + + class GenericHunterStrategy : public RangedCombatStrategy + { + public: + GenericHunterStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "hunter"; } + }; +} + diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterActions.cpp b/src/modules/Bots/playerbot/strategy/hunter/HunterActions.cpp new file mode 100644 index 000000000..a705efd62 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterActions.cpp @@ -0,0 +1,37 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../actions/GenericActions.h" +#include "HunterActions.h" + +using namespace ai; + +bool CastSerpentStingAction::isUseful() +{ + return AI_VALUE2(uint8, "health", "current target") > 50; +} + +bool CastViperStingAction::isUseful() +{ + return AI_VALUE2(uint8, "mana", "self target") < 50 && AI_VALUE2(uint8, "mana", "current target") >= 30; +} + +bool CastAspectOfTheCheetahAction::isUseful() +{ + return !ai->HasAnyAuraOf(GetTarget(), "aspect of the cheetah", "aspect of the pack", NULL); +} + +Value* CastFreezingTrap::GetTargetValue() +{ + return context->GetValue("cc target", "freezing trap"); +} + +bool FeedPetAction::Execute(Event event) +{ + Pet* pet = bot->GetPet(); + if (pet && pet->getPetType() == HUNTER_PET && pet->GetHappinessState() != HAPPY) + { + pet->SetPower(POWER_HAPPINESS, HAPPINESS_LEVEL_SIZE * 2); + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterActions.h b/src/modules/Bots/playerbot/strategy/hunter/HunterActions.h new file mode 100644 index 000000000..3d7230371 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterActions.h @@ -0,0 +1,168 @@ +#pragma once + +#include "../actions/GenericActions.h" + +namespace ai +{ + BEGIN_RANGED_SPELL_ACTION(CastHuntersMarkAction, "hunter's mark") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastAutoShotAction, "auto shot") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastArcaneShotAction, "arcane shot") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastExplosiveShotAction, "explosive shot") + END_SPELL_ACTION() + + + BEGIN_RANGED_SPELL_ACTION(CastAimedShotAction, "aimed shot") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastChimeraShotAction, "chimera shot") + END_SPELL_ACTION() + + class CastConcussiveShotAction : public CastSnareSpellAction + { + public: + CastConcussiveShotAction(PlayerbotAI* ai) : CastSnareSpellAction(ai, "concussive shot") {} + }; + + BEGIN_RANGED_SPELL_ACTION(CastDistractingShotAction, "distracting shot") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastMultiShotAction, "multi-shot") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastVolleyAction, "volley") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastSerpentStingAction, "serpent sting") + virtual bool isUseful(); + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastWyvernStingAction, "wyvern sting") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastViperStingAction, "viper sting") + virtual bool isUseful(); + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastScorpidStingAction, "scorpid sting") + END_SPELL_ACTION() + + class CastAspectOfTheHawkAction : public CastBuffSpellAction + { + public: + CastAspectOfTheHawkAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "aspect of the hawk") {} + }; + + class CastAspectOfTheWildAction : public CastBuffSpellAction + { + public: + CastAspectOfTheWildAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "aspect of the wild") {} + }; + + class CastAspectOfTheCheetahAction : public CastBuffSpellAction + { + public: + CastAspectOfTheCheetahAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "aspect of the cheetah") {} + virtual bool isUseful(); + }; + + class CastAspectOfThePackAction : public CastBuffSpellAction + { + public: + CastAspectOfThePackAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "aspect of the pack") {} + }; + + class CastAspectOfTheViperAction : public CastBuffSpellAction + { + public: + CastAspectOfTheViperAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "aspect of the viper") {} + }; + + class CastCallPetAction : public CastBuffSpellAction + { + public: + CastCallPetAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "call pet") {} + }; + + class CastMendPetAction : public CastAuraSpellAction + { + public: + CastMendPetAction(PlayerbotAI* ai) : CastAuraSpellAction(ai, "mend pet") {} + virtual string GetTargetName() { return "pet target"; } + }; + + class CastRevivePetAction : public CastBuffSpellAction + { + public: + CastRevivePetAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "revive pet") {} + }; + + class CastTrueshotAuraAction : public CastBuffSpellAction + { + public: + CastTrueshotAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "trueshot aura") {} + }; + + class CastFeignDeathAction : public CastBuffSpellAction + { + public: + CastFeignDeathAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "feign death") {} + }; + + class CastRapidFireAction : public CastBuffSpellAction + { + public: + CastRapidFireAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "rapid fire") {} + }; + + class CastReadinessAction : public CastBuffSpellAction + { + public: + CastReadinessAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "readiness") {} + }; + + class CastBlackArrow : public CastDebuffSpellAction + { + public: + CastBlackArrow(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "black arrow") {} + }; + + class CastFreezingTrap : public CastDebuffSpellAction + { + public: + CastFreezingTrap(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "freezing trap") {} + virtual Value* GetTargetValue(); + }; + + class CastWingClipAction : public CastMeleeSpellAction + { + public: + CastWingClipAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "wing clip") {} + virtual bool isUseful() + { + return CastMeleeSpellAction::isUseful() && !ai->HasAura(spell, GetTarget()); + } + virtual NextAction** getPrerequisites() + { + return NULL; + } + }; + + class CastSerpentStingOnAttackerAction : public CastDebuffSpellOnAttackerAction + { + public: + CastSerpentStingOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "serpent sting") {} + }; + + class FeedPetAction : public Action + { + public: + FeedPetAction(PlayerbotAI* ai) : Action(ai, "feed pet") {} + virtual bool Execute(Event event); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.cpp new file mode 100644 index 000000000..1fd9b7adb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.cpp @@ -0,0 +1,197 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HunterActions.h" +#include "HunterTriggers.h" +#include "HunterAiObjectContext.h" +#include "DpsHunterStrategy.h" +#include "GenericHunterNonCombatStrategy.h" +#include "HunterBuffStrategies.h" +#include "../NamedObjectContext.h" + +using namespace ai; + + +namespace ai +{ + namespace hunter + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["dps"] = &hunter::StrategyFactoryInternal::dps; + creators["nc"] = &hunter::StrategyFactoryInternal::nc; + creators["aoe"] = &hunter::StrategyFactoryInternal::aoe; + creators["dps debuff"] = &hunter::StrategyFactoryInternal::dps_debuff; + } + + private: + static Strategy* aoe(PlayerbotAI* ai) { return new DpsAoeHunterStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new DpsHunterStrategy(ai); } + static Strategy* nc(PlayerbotAI* ai) { return new GenericHunterNonCombatStrategy(ai); } + static Strategy* dps_debuff(PlayerbotAI* ai) { return new DpsHunterDebuffStrategy(ai); } + }; + + class BuffStrategyFactoryInternal : public NamedObjectContext + { + public: + BuffStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["bspeed"] = &hunter::BuffStrategyFactoryInternal::bspeed; + creators["bdps"] = &hunter::BuffStrategyFactoryInternal::bdps; + creators["bmana"] = &hunter::BuffStrategyFactoryInternal::bmana; + creators["rnature"] = &hunter::BuffStrategyFactoryInternal::rnature; + } + + private: + static Strategy* bspeed(PlayerbotAI* ai) { return new HunterBuffSpeedStrategy(ai); } + static Strategy* bdps(PlayerbotAI* ai) { return new HunterBuffDpsStrategy(ai); } + static Strategy* bmana(PlayerbotAI* ai) { return new HunterBuffManaStrategy(ai); } + static Strategy* rnature(PlayerbotAI* ai) { return new HunterNatureResistanceStrategy(ai); } + }; + }; +}; + + +namespace ai +{ + namespace hunter + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["aspect of the viper"] = &TriggerFactoryInternal::aspect_of_the_viper; + creators["black arrow"] = &TriggerFactoryInternal::black_arrow; + creators["no stings"] = &TriggerFactoryInternal::NoStings; + creators["hunters pet dead"] = &TriggerFactoryInternal::hunters_pet_dead; + creators["hunters pet low health"] = &TriggerFactoryInternal::hunters_pet_low_health; + creators["hunter's mark"] = &TriggerFactoryInternal::hunters_mark; + creators["freezing trap"] = &TriggerFactoryInternal::freezing_trap; + creators["aspect of the pack"] = &TriggerFactoryInternal::aspect_of_the_pack; + creators["rapid fire"] = &TriggerFactoryInternal::rapid_fire; + creators["aspect of the hawk"] = &TriggerFactoryInternal::aspect_of_the_hawk; + creators["aspect of the wild"] = &TriggerFactoryInternal::aspect_of_the_wild; + creators["aspect of the viper"] = &TriggerFactoryInternal::aspect_of_the_viper; + creators["trueshot aura"] = &TriggerFactoryInternal::trueshot_aura; + creators["serpent sting on attacker"] = &TriggerFactoryInternal::serpent_sting_on_attacker; + creators["pet not happy"] = &TriggerFactoryInternal::pet_not_happy; + creators["concussive shot on snare target"] = &TriggerFactoryInternal::concussive_shot_on_snare_target; + } + + private: + static Trigger* concussive_shot_on_snare_target(PlayerbotAI* ai) { return new ConsussiveShotSnareTrigger(ai); } + static Trigger* pet_not_happy(PlayerbotAI* ai) { return new HunterPetNotHappy(ai); } + static Trigger* serpent_sting_on_attacker(PlayerbotAI* ai) { return new SerpentStingOnAttackerTrigger(ai); } + static Trigger* trueshot_aura(PlayerbotAI* ai) { return new TrueshotAuraTrigger(ai); } + static Trigger* aspect_of_the_viper(PlayerbotAI* ai) { return new HunterAspectOfTheViperTrigger(ai); } + static Trigger* black_arrow(PlayerbotAI* ai) { return new BlackArrowTrigger(ai); } + static Trigger* NoStings(PlayerbotAI* ai) { return new HunterNoStingsActiveTrigger(ai); } + static Trigger* hunters_pet_dead(PlayerbotAI* ai) { return new HuntersPetDeadTrigger(ai); } + static Trigger* hunters_pet_low_health(PlayerbotAI* ai) { return new HuntersPetLowHealthTrigger(ai); } + static Trigger* hunters_mark(PlayerbotAI* ai) { return new HuntersMarkTrigger(ai); } + static Trigger* freezing_trap(PlayerbotAI* ai) { return new FreezingTrapTrigger(ai); } + static Trigger* aspect_of_the_pack(PlayerbotAI* ai) { return new HunterAspectOfThePackTrigger(ai); } + static Trigger* rapid_fire(PlayerbotAI* ai) { return new RapidFireTrigger(ai); } + static Trigger* aspect_of_the_hawk(PlayerbotAI* ai) { return new HunterAspectOfTheHawkTrigger(ai); } + static Trigger* aspect_of_the_wild(PlayerbotAI* ai) { return new HunterAspectOfTheWildTrigger(ai); } + }; + }; +}; + + + +namespace ai +{ + namespace hunter + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["auto shot"] = &AiObjectContextInternal::auto_shot; + creators["aimed shot"] = &AiObjectContextInternal::aimed_shot; + creators["chimera shot"] = &AiObjectContextInternal::chimera_shot; + creators["explosive shot"] = &AiObjectContextInternal::explosive_shot; + creators["arcane shot"] = &AiObjectContextInternal::arcane_shot; + creators["concussive shot"] = &AiObjectContextInternal::concussive_shot; + creators["distracting shot"] = &AiObjectContextInternal::distracting_shot; + creators["multi-shot"] = &AiObjectContextInternal::multi_shot; + creators["volley"] = &AiObjectContextInternal::volley; + creators["serpent sting"] = &AiObjectContextInternal::serpent_sting; + creators["serpent sting on attacker"] = &AiObjectContextInternal::serpent_sting_on_attacker; + creators["wyvern sting"] = &AiObjectContextInternal::wyvern_sting; + creators["viper sting"] = &AiObjectContextInternal::viper_sting; + creators["scorpid sting"] = &AiObjectContextInternal::scorpid_sting; + creators["hunter's mark"] = &AiObjectContextInternal::hunters_mark; + creators["mend pet"] = &AiObjectContextInternal::mend_pet; + creators["revive pet"] = &AiObjectContextInternal::revive_pet; + creators["call pet"] = &AiObjectContextInternal::call_pet; + creators["black arrow"] = &AiObjectContextInternal::black_arrow; + creators["freezing trap"] = &AiObjectContextInternal::freezing_trap; + creators["rapid fire"] = &AiObjectContextInternal::rapid_fire; + creators["boost"] = &AiObjectContextInternal::rapid_fire; + creators["readiness"] = &AiObjectContextInternal::readiness; + creators["aspect of the hawk"] = &AiObjectContextInternal::aspect_of_the_hawk; + creators["aspect of the wild"] = &AiObjectContextInternal::aspect_of_the_wild; + creators["aspect of the viper"] = &AiObjectContextInternal::aspect_of_the_viper; + creators["aspect of the pack"] = &AiObjectContextInternal::aspect_of_the_pack; + creators["aspect of the cheetah"] = &AiObjectContextInternal::aspect_of_the_cheetah; + creators["trueshot aura"] = &AiObjectContextInternal::trueshot_aura; + creators["feign death"] = &AiObjectContextInternal::feign_death; + creators["wing clip"] = &AiObjectContextInternal::wing_clip; + creators["feed pet"] = &AiObjectContextInternal::feed_pet; + } + + private: + static Action* feed_pet(PlayerbotAI* ai) { return new FeedPetAction(ai); } + static Action* feign_death(PlayerbotAI* ai) { return new CastFeignDeathAction(ai); } + static Action* trueshot_aura(PlayerbotAI* ai) { return new CastTrueshotAuraAction(ai); } + static Action* auto_shot(PlayerbotAI* ai) { return new CastAutoShotAction(ai); } + static Action* aimed_shot(PlayerbotAI* ai) { return new CastAimedShotAction(ai); } + static Action* chimera_shot(PlayerbotAI* ai) { return new CastChimeraShotAction(ai); } + static Action* explosive_shot(PlayerbotAI* ai) { return new CastExplosiveShotAction(ai); } + static Action* arcane_shot(PlayerbotAI* ai) { return new CastArcaneShotAction(ai); } + static Action* concussive_shot(PlayerbotAI* ai) { return new CastConcussiveShotAction(ai); } + static Action* distracting_shot(PlayerbotAI* ai) { return new CastDistractingShotAction(ai); } + static Action* multi_shot(PlayerbotAI* ai) { return new CastMultiShotAction(ai); } + static Action* volley(PlayerbotAI* ai) { return new CastVolleyAction(ai); } + static Action* serpent_sting(PlayerbotAI* ai) { return new CastSerpentStingAction(ai); } + static Action* serpent_sting_on_attacker(PlayerbotAI* ai) { return new CastSerpentStingOnAttackerAction(ai); } + static Action* wyvern_sting(PlayerbotAI* ai) { return new CastWyvernStingAction(ai); } + static Action* viper_sting(PlayerbotAI* ai) { return new CastViperStingAction(ai); } + static Action* scorpid_sting(PlayerbotAI* ai) { return new CastScorpidStingAction(ai); } + static Action* hunters_mark(PlayerbotAI* ai) { return new CastHuntersMarkAction(ai); } + static Action* mend_pet(PlayerbotAI* ai) { return new CastMendPetAction(ai); } + static Action* revive_pet(PlayerbotAI* ai) { return new CastRevivePetAction(ai); } + static Action* call_pet(PlayerbotAI* ai) { return new CastCallPetAction(ai); } + static Action* black_arrow(PlayerbotAI* ai) { return new CastBlackArrow(ai); } + static Action* freezing_trap(PlayerbotAI* ai) { return new CastFreezingTrap(ai); } + static Action* rapid_fire(PlayerbotAI* ai) { return new CastRapidFireAction(ai); } + static Action* readiness(PlayerbotAI* ai) { return new CastReadinessAction(ai); } + static Action* aspect_of_the_hawk(PlayerbotAI* ai) { return new CastAspectOfTheHawkAction(ai); } + static Action* aspect_of_the_wild(PlayerbotAI* ai) { return new CastAspectOfTheWildAction(ai); } + static Action* aspect_of_the_viper(PlayerbotAI* ai) { return new CastAspectOfTheViperAction(ai); } + static Action* aspect_of_the_pack(PlayerbotAI* ai) { return new CastAspectOfThePackAction(ai); } + static Action* aspect_of_the_cheetah(PlayerbotAI* ai) { return new CastAspectOfTheCheetahAction(ai); } + static Action* wing_clip(PlayerbotAI* ai) { return new CastWingClipAction(ai); } + }; + }; +}; + +HunterAiObjectContext::HunterAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::hunter::StrategyFactoryInternal()); + strategyContexts.Add(new ai::hunter::BuffStrategyFactoryInternal()); + actionContexts.Add(new ai::hunter::AiObjectContextInternal()); + triggerContexts.Add(new ai::hunter::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.h b/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.h new file mode 100644 index 000000000..8891a5489 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class HunterAiObjectContext : public AiObjectContext + { + public: + HunterAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.cpp b/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.cpp new file mode 100644 index 000000000..970ed690d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.cpp @@ -0,0 +1,35 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HunterMultipliers.h" +#include "HunterBuffStrategies.h" + +using namespace ai; + +void HunterBuffDpsStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "aspect of the hawk", + NextAction::array(0, new NextAction("aspect of the hawk", 90.0f), NULL))); +} + +void HunterNatureResistanceStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "aspect of the wild", + NextAction::array(0, new NextAction("aspect of the wild", 90.0f), NULL))); +} + + +void HunterBuffSpeedStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "aspect of the pack", + NextAction::array(0, new NextAction("aspect of the pack", 10.0f), NULL))); +} + +void HunterBuffManaStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "aspect of the viper", + NextAction::array(0, new NextAction("aspect of the viper", 10.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.h b/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.h new file mode 100644 index 000000000..7630b1229 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterBuffStrategies.h @@ -0,0 +1,47 @@ +#pragma once + +#include "GenericHunterStrategy.h" +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class HunterBuffSpeedStrategy : public NonCombatStrategy + { + public: + HunterBuffSpeedStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "bspeed"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class HunterBuffManaStrategy : public NonCombatStrategy + { + public: + HunterBuffManaStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "bmana"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class HunterBuffDpsStrategy : public NonCombatStrategy + { + public: + HunterBuffDpsStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "bdps"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class HunterNatureResistanceStrategy : public NonCombatStrategy + { + public: + HunterNatureResistanceStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "rnature"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.cpp b/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.cpp new file mode 100644 index 000000000..3efbca961 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.cpp @@ -0,0 +1,5 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HunterMultipliers.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.h b/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.cpp b/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.cpp new file mode 100644 index 000000000..a63185fb9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.cpp @@ -0,0 +1,34 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HunterTriggers.h" +#include "HunterActions.h" + +using namespace ai; + +bool HunterNoStingsActiveTrigger::IsActive() +{ + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(uint8, "health", "current target") > 40 && + !ai->HasAura("serpent sting", target) && + !ai->HasAura("scorpid sting", target) && + !ai->HasAura("viper sting", target); +} + +bool HuntersPetDeadTrigger::IsActive() +{ + return AI_VALUE(bool, "pet dead") && !AI_VALUE2(bool, "mounted", "self target"); +} + +bool HuntersPetLowHealthTrigger::IsActive() +{ + Unit* pet = AI_VALUE(Unit*, "pet target"); + return pet && AI_VALUE2(uint8, "health", "pet target") < 40 && + !AI_VALUE2(bool, "dead", "pet target") && !AI_VALUE2(bool, "mounted", "self target"); +} + +bool HunterPetNotHappy::IsActive() +{ + return !AI_VALUE(bool, "pet happy") && !AI_VALUE2(bool, "mounted", "self target"); +} + + diff --git a/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.h b/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.h new file mode 100644 index 000000000..485a52499 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/hunter/HunterTriggers.h @@ -0,0 +1,95 @@ +#pragma once + +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + BEGIN_TRIGGER(HunterNoStingsActiveTrigger, Trigger) + END_TRIGGER() + + class HunterAspectOfTheHawkTrigger : public BuffTrigger + { + public: + HunterAspectOfTheHawkTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "aspect of the hawk") { + checkInterval = 1; + } + }; + + class HunterAspectOfTheWildTrigger : public BuffTrigger + { + public: + HunterAspectOfTheWildTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "aspect of the wild") { + checkInterval = 1; + } + }; + + class HunterAspectOfTheViperTrigger : public BuffTrigger + { + public: + HunterAspectOfTheViperTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "aspect of the viper") {} + virtual bool IsActive() + { + return SpellTrigger::IsActive() && !ai->HasAura(spell, GetTarget()); + } + }; + + class HunterAspectOfThePackTrigger : public BuffTrigger + { + public: + HunterAspectOfThePackTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "aspect of the pack") {} + virtual bool IsActive() { + return BuffTrigger::IsActive() && !ai->HasAura("aspect of the cheetah", GetTarget()); + }; + }; + + BEGIN_TRIGGER(HuntersPetDeadTrigger, Trigger) + END_TRIGGER() + + BEGIN_TRIGGER(HuntersPetLowHealthTrigger, Trigger) + END_TRIGGER() + + class BlackArrowTrigger : public DebuffTrigger + { + public: + BlackArrowTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "black arrow") {} + }; + + class HuntersMarkTrigger : public DebuffTrigger + { + public: + HuntersMarkTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "hunter's mark") {} + }; + + class FreezingTrapTrigger : public HasCcTargetTrigger + { + public: + FreezingTrapTrigger(PlayerbotAI* ai) : HasCcTargetTrigger(ai, "freezing trap") {} + }; + + class RapidFireTrigger : public BoostTrigger + { + public: + RapidFireTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "rapid fire") {} + }; + + class TrueshotAuraTrigger : public BuffTrigger + { + public: + TrueshotAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "trueshot aura") {} + }; + + class SerpentStingOnAttackerTrigger : public DebuffOnAttackerTrigger + { + public: + SerpentStingOnAttackerTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "serpent sting") {} + }; + + BEGIN_TRIGGER(HunterPetNotHappy, Trigger) + END_TRIGGER() + + class ConsussiveShotSnareTrigger : public SnareTargetTrigger + { + public: + ConsussiveShotSnareTrigger(PlayerbotAI* ai) : SnareTargetTrigger(ai, "concussive shot") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.cpp new file mode 100644 index 000000000..8a9510cfb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.cpp @@ -0,0 +1,64 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "ArcaneMageStrategy.h" + +using namespace ai; + +class ArcaneMageStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + ArcaneMageStrategyActionNodeFactory() + { + creators["arcane blast"] = &arcane_blast; + creators["arcane barrage"] = &arcane_barrage; + creators["arcane missiles"] = &arcane_missiles; + } +private: + static ActionNode* arcane_blast(PlayerbotAI* ai) + { + return new ActionNode ("arcane blast", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("arcane missiles"), NULL), + /*C*/ NULL); + } + static ActionNode* arcane_barrage(PlayerbotAI* ai) + { + return new ActionNode ("arcane barrage", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("arcane missiles"), NULL), + /*C*/ NULL); + } + static ActionNode* arcane_missiles(PlayerbotAI* ai) + { + return new ActionNode ("arcane missiles", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } +}; + +ArcaneMageStrategy::ArcaneMageStrategy(PlayerbotAI* ai) : GenericMageStrategy(ai) +{ + actionNodeFactories.Add(new ArcaneMageStrategyActionNodeFactory()); +} + +NextAction** ArcaneMageStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("arcane barrage", 10.0f), NULL); +} + +void ArcaneMageStrategy::InitTriggers(std::list &triggers) +{ + GenericMageStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "arcane blast", + NextAction::array(0, new NextAction("arcane blast", 15.0f), NULL))); + + triggers.push_back(new TriggerNode( + "missile barrage", + NextAction::array(0, new NextAction("arcane missiles", 15.0f), NULL))); + +} + diff --git a/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.h b/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.h new file mode 100644 index 000000000..ac053a821 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/ArcaneMageStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericMageStrategy.h" + +namespace ai +{ + class ArcaneMageStrategy : public GenericMageStrategy + { + public: + ArcaneMageStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "arcane"; } + virtual NextAction** getDefaultActions(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.cpp new file mode 100644 index 000000000..8a361326b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.cpp @@ -0,0 +1,44 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "FireMageStrategy.h" + +using namespace ai; + +NextAction** FireMageStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("scorch", 7.0f), new NextAction("fireball", 6.0f), new NextAction("fire blast", 5.0f), NULL); +} + +void FireMageStrategy::InitTriggers(std::list &triggers) +{ + GenericMageStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "pyroblast", + NextAction::array(0, new NextAction("pyroblast", 10.0f), NULL))); + + triggers.push_back(new TriggerNode( + "hot streak", + NextAction::array(0, new NextAction("pyroblast", 25.0f), NULL))); + + triggers.push_back(new TriggerNode( + "combustion", + NextAction::array(0, new NextAction("combustion", 50.0f), NULL))); + + triggers.push_back(new TriggerNode( + "enemy too close for spell", + NextAction::array(0, new NextAction("dragon's breath", 70.0f), NULL))); +} + +void FireMageAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("flamestrike", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "living bomb", + NextAction::array(0, new NextAction("living bomb", 25.0f), NULL))); +} + diff --git a/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.h b/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.h new file mode 100644 index 000000000..fc0cd7f85 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/FireMageStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GenericMageStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class FireMageStrategy : public GenericMageStrategy + { + public: + FireMageStrategy(PlayerbotAI* ai) : GenericMageStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "fire"; } + virtual NextAction** getDefaultActions(); + }; + + class FireMageAoeStrategy : public CombatStrategy + { + public: + FireMageAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "fire aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.cpp new file mode 100644 index 000000000..ed55afb1c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.cpp @@ -0,0 +1,32 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "FrostMageStrategy.h" + +using namespace ai; + + +FrostMageStrategy::FrostMageStrategy(PlayerbotAI* ai) : GenericMageStrategy(ai) +{ +} + +NextAction** FrostMageStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("frostbolt", 7.0f), NULL); +} + +void FrostMageStrategy::InitTriggers(std::list &triggers) +{ + GenericMageStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "icy veins", + NextAction::array(0, new NextAction("icy veins", 50.0f), NULL))); +} + +void FrostMageAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "high aoe", + NextAction::array(0, new NextAction("blizzard", 40.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.h b/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.h new file mode 100644 index 000000000..15fac2f61 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/FrostMageStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GenericMageStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class FrostMageStrategy : public GenericMageStrategy + { + public: + FrostMageStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "frost"; } + virtual NextAction** getDefaultActions(); + }; + + class FrostMageAoeStrategy : public CombatStrategy + { + public: + FrostMageAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "frost aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp new file mode 100644 index 000000000..d16d5619f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "GenericMageNonCombatStrategy.h" + +using namespace ai; + +class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericMageNonCombatStrategyActionNodeFactory() + { + creators["molten armor"] = &molten_armor; + creators["mage armor"] = &mage_armor; + creators["ice armor"] = &ice_armor; + } +private: + static ActionNode* molten_armor(PlayerbotAI* ai) + { + return new ActionNode ("molten armor", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mage armor"), NULL), + /*C*/ NULL); + } + static ActionNode* mage_armor(PlayerbotAI* ai) + { + return new ActionNode ("mage armor", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("ice armor"), NULL), + /*C*/ NULL); + } + static ActionNode* ice_armor(PlayerbotAI* ai) + { + return new ActionNode ("ice armor", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("frost armor"), NULL), + /*C*/ NULL); + } +}; + +GenericMageNonCombatStrategy::GenericMageNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericMageNonCombatStrategyActionNodeFactory()); +} + +void GenericMageNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "arcane intellect", + NextAction::array(0, new NextAction("arcane intellect", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "arcane intellect on party", + NextAction::array(0, new NextAction("arcane intellect on party", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no drink", + NextAction::array(0, new NextAction("conjure water", 16.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no food", + NextAction::array(0, new NextAction("conjure food", 15.0f), NULL))); +} + +void MageBuffManaStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "mage armor", + NextAction::array(0, new NextAction("mage armor", 19.0f), NULL))); +} + +void MageBuffDpsStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "mage armor", + NextAction::array(0, new NextAction("molten armor", 19.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.h new file mode 100644 index 000000000..5319bf9be --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.h @@ -0,0 +1,37 @@ +#pragma once + +#include "GenericMageStrategy.h" +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericMageNonCombatStrategy : public NonCombatStrategy + { + public: + GenericMageNonCombatStrategy(PlayerbotAI* ai); + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class MageBuffManaStrategy : public Strategy + { + public: + MageBuffManaStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bmana"; } + }; + + class MageBuffDpsStrategy : public Strategy + { + public: + MageBuffDpsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bdps"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp new file mode 100644 index 000000000..bc742ed2a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp @@ -0,0 +1,161 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "GenericMageStrategy.h" + +using namespace ai; + +class GenericMageStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericMageStrategyActionNodeFactory() + { + creators["frostbolt"] = &frostbolt; + creators["fire blast"] = &fire_blast; + creators["scorch"] = &scorch; + creators["frost nova"] = &frost_nova; + creators["icy veins"] = &icy_veins; + creators["combustion"] = &combustion; + creators["evocation"] = &evocation; + creators["dragon's breath"] = &dragons_breath; + creators["blast wave"] = &blast_wave; + creators["remove curse"] = &remove_curse; + creators["remove curse on party"] = &remove_curse_on_party; + } +private: + static ActionNode* frostbolt(PlayerbotAI* ai) + { + return new ActionNode ("frostbolt", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } + static ActionNode* fire_blast(PlayerbotAI* ai) + { + return new ActionNode ("fire blast", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("scorch"), NULL), + /*C*/ NULL); + } + static ActionNode* scorch(PlayerbotAI* ai) + { + return new ActionNode ("scorch", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } + static ActionNode* frost_nova(PlayerbotAI* ai) + { + return new ActionNode ("frost nova", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* icy_veins(PlayerbotAI* ai) + { + return new ActionNode ("icy veins", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* combustion(PlayerbotAI* ai) + { + return new ActionNode ("combustion", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* evocation(PlayerbotAI* ai) + { + return new ActionNode ("evocation", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mana potion"), NULL), + /*C*/ NULL); + } + static ActionNode* dragons_breath(PlayerbotAI* ai) + { + return new ActionNode ("dragon's breath", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("blast wave"), NULL), + /*C*/ NextAction::array(0, new NextAction("flamestrike", 71.0f), NULL)); + } + static ActionNode* blast_wave(PlayerbotAI* ai) + { + return new ActionNode ("blast wave", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("frost nova"), NULL), + /*C*/ NextAction::array(0, new NextAction("flamestrike", 71.0f), NULL)); + } + static ActionNode* remove_curse(PlayerbotAI* ai) + { + return new ActionNode ("remove curse", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("remove lesser curse"), NULL), + /*C*/ NULL); + } + static ActionNode* remove_curse_on_party(PlayerbotAI* ai) + { + return new ActionNode ("remove curse on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("remove lesser curse on party"), NULL), + /*C*/ NULL); + } +}; + +GenericMageStrategy::GenericMageStrategy(PlayerbotAI* ai) : RangedCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericMageStrategyActionNodeFactory()); +} + +void GenericMageStrategy::InitTriggers(std::list &triggers) +{ + RangedCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy too close for spell", + NextAction::array(0, new NextAction("flee", 49.0f), NULL))); + + triggers.push_back(new TriggerNode( + "enemy is close", + NextAction::array(0, new NextAction("frost nova", 50.0f), NULL))); + + triggers.push_back(new TriggerNode( + "counterspell", + NextAction::array(0, new NextAction("counterspell", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "counterspell on enemy healer", + NextAction::array(0, new NextAction("counterspell on enemy healer", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("ice block", 80.0f), NULL))); + + triggers.push_back(new TriggerNode( + "polymorph", + NextAction::array(0, new NextAction("polymorph", 30.0f), NULL))); + + triggers.push_back(new TriggerNode( + "spellsteal", + NextAction::array(0, new NextAction("spellsteal", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium threat", + NextAction::array(0, new NextAction("invisibility", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("evocation", ACTION_EMERGENCY + 5), NULL))); +} + +void MageCureStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "remove curse", + NextAction::array(0, new NextAction("remove curse", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "remove curse on party", + NextAction::array(0, new NextAction("remove curse on party", 40.0f), NULL))); + +} diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.h b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.h new file mode 100644 index 000000000..aad481712 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/RangedCombatStrategy.h" + +namespace ai +{ + class GenericMageStrategy : public RangedCombatStrategy + { + public: + GenericMageStrategy(PlayerbotAI* ai); + virtual string getName() { return "mage"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; + + class MageCureStrategy : public Strategy + { + public: + MageCureStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cure"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp b/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp new file mode 100644 index 000000000..438802219 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageActions.h" + +using namespace ai; + +Value* CastPolymorphAction::GetTargetValue() +{ + return context->GetValue("cc target", getName()); +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageActions.h b/src/modules/Bots/playerbot/strategy/mage/MageActions.h new file mode 100644 index 000000000..a624b8d34 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageActions.h @@ -0,0 +1,217 @@ +#pragma once + +#include "../actions/GenericActions.h" + +namespace ai +{ + class CastFireballAction : public CastSpellAction + { + public: + CastFireballAction(PlayerbotAI* ai) : CastSpellAction(ai, "fireball") {} + }; + + class CastScorchAction : public CastSpellAction + { + public: + CastScorchAction(PlayerbotAI* ai) : CastSpellAction(ai, "scorch") {} + }; + + class CastFireBlastAction : public CastSpellAction + { + public: + CastFireBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "fire blast") {} + }; + + class CastArcaneBlastAction : public CastBuffSpellAction + { + public: + CastArcaneBlastAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "arcane blast") {} + virtual string GetTargetName() { return "current target"; } + }; + + class CastArcaneBarrageAction : public CastSpellAction + { + public: + CastArcaneBarrageAction(PlayerbotAI* ai) : CastSpellAction(ai, "arcane barrage") {} + }; + + class CastArcaneMissilesAction : public CastSpellAction + { + public: + CastArcaneMissilesAction(PlayerbotAI* ai) : CastSpellAction(ai, "arcane missiles") {} + }; + + class CastPyroblastAction : public CastSpellAction + { + public: + CastPyroblastAction(PlayerbotAI* ai) : CastSpellAction(ai, "pyroblast") {} + }; + + class CastFlamestrikeAction : public CastSpellAction + { + public: + CastFlamestrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "flamestrike") {} + }; + + class CastFrostNovaAction : public CastSpellAction + { + public: + CastFrostNovaAction(PlayerbotAI* ai) : CastSpellAction(ai, "frost nova") {} + virtual bool isUseful() { return AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig.tooCloseDistance; } + }; + + class CastFrostboltAction : public CastSpellAction + { + public: + CastFrostboltAction(PlayerbotAI* ai) : CastSpellAction(ai, "frostbolt") {} + }; + + class CastBlizzardAction : public CastSpellAction + { + public: + CastBlizzardAction(PlayerbotAI* ai) : CastSpellAction(ai, "blizzard") {} + }; + + class CastArcaneIntellectAction : public CastBuffSpellAction + { + public: + CastArcaneIntellectAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "arcane intellect") {} + }; + + class CastArcaneIntellectOnPartyAction : public BuffOnPartyAction + { + public: + CastArcaneIntellectOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "arcane intellect") {} + }; + + class CastRemoveCurseAction : public CastCureSpellAction + { + public: + CastRemoveCurseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "remove curse") {} + }; + + class CastRemoveLesserCurseAction : public CastCureSpellAction + { + public: + CastRemoveLesserCurseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "remove lesser curse") {} + }; + + class CastIcyVeinsAction : public CastBuffSpellAction + { + public: + CastIcyVeinsAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "icy veins") {} + }; + + class CastCombustionAction : public CastBuffSpellAction + { + public: + CastCombustionAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "combustion") {} + }; + + BEGIN_SPELL_ACTION(CastCounterspellAction, "counterspell") + END_SPELL_ACTION() + + class CastRemoveCurseOnPartyAction : public CurePartyMemberAction + { + public: + CastRemoveCurseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "remove curse", DISPEL_CURSE) {} + }; + + class CastRemoveLesserCurseOnPartyAction : public CurePartyMemberAction + { + public: + CastRemoveLesserCurseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "remove lesser curse", DISPEL_CURSE) {} + }; + + class CastConjureFoodAction : public CastBuffSpellAction + { + public: + CastConjureFoodAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "conjure food") {} + }; + + class CastConjureWaterAction : public CastBuffSpellAction + { + public: + CastConjureWaterAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "conjure water") {} + }; + + class CastIceBlockAction : public CastBuffSpellAction + { + public: + CastIceBlockAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "ice block") {} + }; + + class CastMoltenArmorAction : public CastBuffSpellAction + { + public: + CastMoltenArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "molten armor") {} + }; + + class CastMageArmorAction : public CastBuffSpellAction + { + public: + CastMageArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "mage armor") {} + }; + + class CastIceArmorAction : public CastBuffSpellAction + { + public: + CastIceArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "ice armor") {} + }; + + class CastFrostArmorAction : public CastBuffSpellAction + { + public: + CastFrostArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "frost armor") {} + }; + + class CastPolymorphAction : public CastBuffSpellAction + { + public: + CastPolymorphAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "polymorph") {} + virtual Value* GetTargetValue(); + }; + + class CastSpellstealAction : public CastSpellAction + { + public: + CastSpellstealAction(PlayerbotAI* ai) : CastSpellAction(ai, "spellsteal") {} + }; + + class CastLivingBombAction : public CastDebuffSpellAction + { + public: + CastLivingBombAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "living bomb") {} + }; + + class CastDragonsBreathAction : public CastSpellAction + { + public: + CastDragonsBreathAction(PlayerbotAI* ai) : CastSpellAction(ai, "dragon's breath") {} + }; + + class CastBlastWaveAction : public CastSpellAction + { + public: + CastBlastWaveAction(PlayerbotAI* ai) : CastSpellAction(ai, "blast wave") {} + }; + + class CastInvisibilityAction : public CastBuffSpellAction + { + public: + CastInvisibilityAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "invisibility") {} + }; + + class CastEvocationAction : public CastSpellAction + { + public: + CastEvocationAction(PlayerbotAI* ai) : CastSpellAction(ai, "evocation") {} + virtual string GetTargetName() { return "self target"; } + }; + + class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastCounterspellOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "counterspell") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp new file mode 100644 index 000000000..5035b1811 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp @@ -0,0 +1,227 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "../Strategy.h" +#include "MageActions.h" +#include "MageAiObjectContext.h" +#include "FrostMageStrategy.h" +#include "ArcaneMageStrategy.h" +#include "GenericMageNonCombatStrategy.h" +#include "FireMageStrategy.h" +#include "../generic/PullStrategy.h" +#include "MageTriggers.h" +#include "../NamedObjectContext.h" + +using namespace ai; + + +namespace ai +{ + namespace mage + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &mage::StrategyFactoryInternal::nc; + creators["pull"] = &mage::StrategyFactoryInternal::pull; + creators["fire aoe"] = &mage::StrategyFactoryInternal::fire_aoe; + creators["frost aoe"] = &mage::StrategyFactoryInternal::frost_aoe; + creators["cure"] = &mage::StrategyFactoryInternal::cure; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new GenericMageNonCombatStrategy(ai); } + static Strategy* pull(PlayerbotAI* ai) { return new PullStrategy(ai, "shoot"); } + static Strategy* fire_aoe(PlayerbotAI* ai) { return new FireMageAoeStrategy(ai); } + static Strategy* frost_aoe(PlayerbotAI* ai) { return new FrostMageAoeStrategy(ai); } + static Strategy* cure(PlayerbotAI* ai) { return new MageCureStrategy(ai); } + }; + + class MageStrategyFactoryInternal : public NamedObjectContext + { + public: + MageStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["frost"] = &mage::MageStrategyFactoryInternal::frost; + creators["fire"] = &mage::MageStrategyFactoryInternal::fire; + creators["arcane"] = &mage::MageStrategyFactoryInternal::arcane; + } + + private: + static Strategy* frost(PlayerbotAI* ai) { return new FrostMageStrategy(ai); } + static Strategy* fire(PlayerbotAI* ai) { return new FireMageStrategy(ai); } + static Strategy* arcane(PlayerbotAI* ai) { return new ArcaneMageStrategy(ai); } + }; + + class MageBuffStrategyFactoryInternal : public NamedObjectContext + { + public: + MageBuffStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["bmana"] = &mage::MageBuffStrategyFactoryInternal::bmana; + creators["bdps"] = &mage::MageBuffStrategyFactoryInternal::bdps; + } + + private: + static Strategy* bmana(PlayerbotAI* ai) { return new MageBuffManaStrategy(ai); } + static Strategy* bdps(PlayerbotAI* ai) { return new MageBuffDpsStrategy(ai); } + }; + }; +}; + + +namespace ai +{ + namespace mage + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["fireball"] = &TriggerFactoryInternal::fireball; + creators["pyroblast"] = &TriggerFactoryInternal::pyroblast; + creators["combustion"] = &TriggerFactoryInternal::combustion; + creators["icy veins"] = &TriggerFactoryInternal::icy_veins; + creators["arcane intellect"] = &TriggerFactoryInternal::arcane_intellect; + creators["arcane intellect on party"] = &TriggerFactoryInternal::arcane_intellect_on_party; + creators["mage armor"] = &TriggerFactoryInternal::mage_armor; + creators["remove curse"] = &TriggerFactoryInternal::remove_curse; + creators["remove curse on party"] = &TriggerFactoryInternal::remove_curse_on_party; + creators["counterspell"] = &TriggerFactoryInternal::counterspell; + creators["polymorph"] = &TriggerFactoryInternal::polymorph; + creators["spellsteal"] = &TriggerFactoryInternal::spellsteal; + creators["hot streak"] = &TriggerFactoryInternal::hot_streak; + creators["living bomb"] = &TriggerFactoryInternal::living_bomb; + creators["missile barrage"] = &TriggerFactoryInternal::missile_barrage; + creators["arcane blast"] = &TriggerFactoryInternal::arcane_blast; + creators["counterspell on enemy healer"] = &TriggerFactoryInternal::counterspell_enemy_healer; + + } + + private: + static Trigger* hot_streak(PlayerbotAI* ai) { return new HotStreakTrigger(ai); } + static Trigger* fireball(PlayerbotAI* ai) { return new FireballTrigger(ai); } + static Trigger* pyroblast(PlayerbotAI* ai) { return new PyroblastTrigger(ai); } + static Trigger* combustion(PlayerbotAI* ai) { return new CombustionTrigger(ai); } + static Trigger* icy_veins(PlayerbotAI* ai) { return new IcyVeinsTrigger(ai); } + static Trigger* arcane_intellect(PlayerbotAI* ai) { return new ArcaneIntellectTrigger(ai); } + static Trigger* arcane_intellect_on_party(PlayerbotAI* ai) { return new ArcaneIntellectOnPartyTrigger(ai); } + static Trigger* mage_armor(PlayerbotAI* ai) { return new MageArmorTrigger(ai); } + static Trigger* remove_curse(PlayerbotAI* ai) { return new RemoveCurseTrigger(ai); } + static Trigger* remove_curse_on_party(PlayerbotAI* ai) { return new PartyMemberRemoveCurseTrigger(ai); } + static Trigger* counterspell(PlayerbotAI* ai) { return new CounterspellInterruptSpellTrigger(ai); } + static Trigger* polymorph(PlayerbotAI* ai) { return new PolymorphTrigger(ai); } + static Trigger* spellsteal(PlayerbotAI* ai) { return new SpellstealTrigger(ai); } + static Trigger* living_bomb(PlayerbotAI* ai) { return new LivingBombTrigger(ai); } + static Trigger* missile_barrage(PlayerbotAI* ai) { return new MissileBarrageTrigger(ai); } + static Trigger* arcane_blast(PlayerbotAI* ai) { return new ArcaneBlastTrigger(ai); } + static Trigger* counterspell_enemy_healer(PlayerbotAI* ai) { return new CounterspellEnemyHealerTrigger(ai); } + }; + }; +}; + + +namespace ai +{ + namespace mage + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["frostbolt"] = &AiObjectContextInternal::frostbolt; + creators["blizzard"] = &AiObjectContextInternal::blizzard; + creators["frost nova"] = &AiObjectContextInternal::frost_nova; + creators["arcane intellect"] = &AiObjectContextInternal::arcane_intellect; + creators["arcane intellect on party"] = &AiObjectContextInternal::arcane_intellect_on_party; + creators["conjure water"] = &AiObjectContextInternal::conjure_water; + creators["conjure food"] = &AiObjectContextInternal::conjure_food; + creators["molten armor"] = &AiObjectContextInternal::molten_armor; + creators["mage armor"] = &AiObjectContextInternal::mage_armor; + creators["ice armor"] = &AiObjectContextInternal::ice_armor; + creators["frost armor"] = &AiObjectContextInternal::frost_armor; + creators["fireball"] = &AiObjectContextInternal::fireball; + creators["pyroblast"] = &AiObjectContextInternal::pyroblast; + creators["flamestrike"] = &AiObjectContextInternal::flamestrike; + creators["fire blast"] = &AiObjectContextInternal::fire_blast; + creators["scorch"] = &AiObjectContextInternal::scorch; + creators["counterspell"] = &AiObjectContextInternal::counterspell; + creators["remove curse"] = &AiObjectContextInternal::remove_curse; + creators["remove curse on party"] = &AiObjectContextInternal::remove_curse_on_party; + creators["remove lesser curse"] = &AiObjectContextInternal::remove_lesser_curse; + creators["remove lesser curse on party"] = &AiObjectContextInternal::remove_lesser_curse_on_party; + creators["icy veins"] = &AiObjectContextInternal::icy_veins; + creators["combustion"] = &AiObjectContextInternal::combustion; + creators["ice block"] = &AiObjectContextInternal::ice_block; + creators["polymorph"] = &AiObjectContextInternal::polymorph; + creators["spellsteal"] = &AiObjectContextInternal::spellsteal; + creators["living bomb"] = &AiObjectContextInternal::living_bomb; + creators["dragon's breath"] = &AiObjectContextInternal::dragons_breath; + creators["blast wave"] = &AiObjectContextInternal::blast_wave; + creators["invisibility"] = &AiObjectContextInternal::invisibility; + creators["evocation"] = &AiObjectContextInternal::evocation; + creators["arcane blast"] = &AiObjectContextInternal::arcane_blast; + creators["arcane barrage"] = &AiObjectContextInternal::arcane_barrage; + creators["arcane missiles"] = &AiObjectContextInternal::arcane_missiles; + creators["counterspell on enemy healer"] = &AiObjectContextInternal::counterspell_on_enemy_healer; + } + + private: + static Action* arcane_missiles(PlayerbotAI* ai) { return new CastArcaneMissilesAction(ai); } + static Action* arcane_barrage(PlayerbotAI* ai) { return new CastArcaneBarrageAction(ai); } + static Action* arcane_blast(PlayerbotAI* ai) { return new CastArcaneBlastAction(ai); } + static Action* frostbolt(PlayerbotAI* ai) { return new CastFrostboltAction(ai); } + static Action* blizzard(PlayerbotAI* ai) { return new CastBlizzardAction(ai); } + static Action* frost_nova(PlayerbotAI* ai) { return new CastFrostNovaAction(ai); } + static Action* arcane_intellect(PlayerbotAI* ai) { return new CastArcaneIntellectAction(ai); } + static Action* arcane_intellect_on_party(PlayerbotAI* ai) { return new CastArcaneIntellectOnPartyAction(ai); } + static Action* conjure_water(PlayerbotAI* ai) { return new CastConjureWaterAction(ai); } + static Action* conjure_food(PlayerbotAI* ai) { return new CastConjureFoodAction(ai); } + static Action* molten_armor(PlayerbotAI* ai) { return new CastMoltenArmorAction(ai); } + static Action* mage_armor(PlayerbotAI* ai) { return new CastMageArmorAction(ai); } + static Action* ice_armor(PlayerbotAI* ai) { return new CastIceArmorAction(ai); } + static Action* frost_armor(PlayerbotAI* ai) { return new CastFrostArmorAction(ai); } + static Action* fireball(PlayerbotAI* ai) { return new CastFireballAction(ai); } + static Action* pyroblast(PlayerbotAI* ai) { return new CastPyroblastAction(ai); } + static Action* flamestrike(PlayerbotAI* ai) { return new CastFlamestrikeAction(ai); } + static Action* fire_blast(PlayerbotAI* ai) { return new CastFireBlastAction(ai); } + static Action* scorch(PlayerbotAI* ai) { return new CastScorchAction(ai); } + static Action* counterspell(PlayerbotAI* ai) { return new CastCounterspellAction(ai); } + static Action* remove_curse(PlayerbotAI* ai) { return new CastRemoveCurseAction(ai); } + static Action* remove_curse_on_party(PlayerbotAI* ai) { return new CastRemoveCurseOnPartyAction(ai); } + static Action* remove_lesser_curse(PlayerbotAI* ai) { return new CastRemoveLesserCurseAction(ai); } + static Action* remove_lesser_curse_on_party(PlayerbotAI* ai) { return new CastRemoveLesserCurseOnPartyAction(ai); } + static Action* icy_veins(PlayerbotAI* ai) { return new CastIcyVeinsAction(ai); } + static Action* combustion(PlayerbotAI* ai) { return new CastCombustionAction(ai); } + static Action* ice_block(PlayerbotAI* ai) { return new CastIceBlockAction(ai); } + static Action* polymorph(PlayerbotAI* ai) { return new CastPolymorphAction(ai); } + static Action* spellsteal(PlayerbotAI* ai) { return new CastSpellstealAction(ai); } + static Action* living_bomb(PlayerbotAI* ai) { return new CastLivingBombAction(ai); } + static Action* dragons_breath(PlayerbotAI* ai) { return new CastDragonsBreathAction(ai); } + static Action* blast_wave(PlayerbotAI* ai) { return new CastBlastWaveAction(ai); } + static Action* invisibility(PlayerbotAI* ai) { return new CastInvisibilityAction(ai); } + static Action* evocation(PlayerbotAI* ai) { return new CastEvocationAction(ai); } + static Action* counterspell_on_enemy_healer(PlayerbotAI* ai) { return new CastCounterspellOnEnemyHealerAction(ai); } + }; + }; +}; + + + +MageAiObjectContext::MageAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::mage::StrategyFactoryInternal()); + strategyContexts.Add(new ai::mage::MageStrategyFactoryInternal()); + strategyContexts.Add(new ai::mage::MageBuffStrategyFactoryInternal()); + actionContexts.Add(new ai::mage::AiObjectContextInternal()); + triggerContexts.Add(new ai::mage::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.h b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.h new file mode 100644 index 000000000..0fe90e2a0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class MageAiObjectContext : public AiObjectContext + { + public: + MageAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.cpp b/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.cpp new file mode 100644 index 000000000..8406324e0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageMultipliers.h" +#include "MageActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.h b/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp new file mode 100644 index 000000000..f42c47ec7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp @@ -0,0 +1,15 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "MageTriggers.h" +#include "MageActions.h" + +using namespace ai; + +bool MageArmorTrigger::IsActive() +{ + Unit* target = GetTarget(); + return !ai->HasAura("ice armor", target) && + !ai->HasAura("frost armor", target) && + !ai->HasAura("molten armor", target) && + !ai->HasAura("mage armor", target); +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h new file mode 100644 index 000000000..054df3004 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h @@ -0,0 +1,99 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger { + public: + ArcaneIntellectOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "arcane intellect", 7) {} + }; + + class ArcaneIntellectTrigger : public BuffTrigger { + public: + ArcaneIntellectTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "arcane intellect", 5) {} + }; + + class MageArmorTrigger : public BuffTrigger { + public: + MageArmorTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "mage armor", 5) {} + virtual bool IsActive(); + }; + + class LivingBombTrigger : public DebuffTrigger { + public: + LivingBombTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "living bomb") {} + }; + + class FireballTrigger : public DebuffTrigger { + public: + FireballTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "fireball") {} + }; + + class PyroblastTrigger : public DebuffTrigger { + public: + PyroblastTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "pyroblast") {} + }; + + class HotStreakTrigger : public HasAuraTrigger { + public: + HotStreakTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "hot streak") {} + }; + + class MissileBarrageTrigger : public HasAuraTrigger { + public: + MissileBarrageTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "missile barrage") {} + }; + + class ArcaneBlastTrigger : public BuffTrigger { + public: + ArcaneBlastTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "arcane blast") {} + }; + + class CounterspellInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + CounterspellInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "counterspell") {} + }; + + class CombustionTrigger : public BoostTrigger + { + public: + CombustionTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "combustion") {} + }; + + class IcyVeinsTrigger : public BoostTrigger + { + public: + IcyVeinsTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "icy veins") {} + }; + + class PolymorphTrigger : public HasCcTargetTrigger + { + public: + PolymorphTrigger(PlayerbotAI* ai) : HasCcTargetTrigger(ai, "polymorph") {} + }; + + class RemoveCurseTrigger : public NeedCureTrigger + { + public: + RemoveCurseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "remove curse", DISPEL_CURSE) {} + }; + + class PartyMemberRemoveCurseTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberRemoveCurseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "remove curse", DISPEL_CURSE) {} + }; + + class SpellstealTrigger : public TargetAuraDispelTrigger + { + public: + SpellstealTrigger(PlayerbotAI* ai) : TargetAuraDispelTrigger(ai, "spellsteal", DISPEL_MAGIC) {} + }; + + class CounterspellEnemyHealerTrigger : public InterruptEnemyHealerTrigger + { + public: + CounterspellEnemyHealerTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "counterspell") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.cpp b/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.cpp new file mode 100644 index 000000000..20c607e35 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "DpsPaladinStrategy.h" + +using namespace ai; + +class DpsPaladinStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + DpsPaladinStrategyActionNodeFactory() + { + creators["seal of vengeance"] = &seal_of_vengeance; + creators["seal of command"] = &seal_of_command; + creators["blessing of might"] = &blessing_of_might; + creators["crusader strike"] = &crusader_strike; + } + +private: + static ActionNode* seal_of_vengeance(PlayerbotAI* ai) + { + return new ActionNode ("seal of vengeance", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("seal of command"), NULL), + /*C*/ NULL); + } + static ActionNode* seal_of_command(PlayerbotAI* ai) + { + return new ActionNode ("seal of command", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("seal of wisdom"), NULL), + /*C*/ NULL); + } + static ActionNode* blessing_of_might(PlayerbotAI* ai) + { + return new ActionNode ("blessing of might", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("blessing of kings"), NULL), + /*C*/ NULL); + } + static ActionNode* crusader_strike(PlayerbotAI* ai) + { + return new ActionNode ("crusader strike", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } +}; + +DpsPaladinStrategy::DpsPaladinStrategy(PlayerbotAI* ai) : GenericPaladinStrategy(ai) +{ + actionNodeFactories.Add(new DpsPaladinStrategyActionNodeFactory()); +} + +NextAction** DpsPaladinStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("crusader strike", ACTION_NORMAL + 1), NULL); +} + +void DpsPaladinStrategy::InitTriggers(std::list &triggers) +{ + GenericPaladinStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("divine shield", ACTION_CRITICAL_HEAL + 2), new NextAction("holy light", ACTION_CRITICAL_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "judgement of wisdom", + NextAction::array(0, new NextAction("judgement of wisdom", ACTION_NORMAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("divine storm", ACTION_HIGH + 1), new NextAction("consecration", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "art of war", + NextAction::array(0, new NextAction("exorcism", ACTION_HIGH + 2), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.h b/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.h new file mode 100644 index 000000000..cff5fd224 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/DpsPaladinStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericPaladinStrategy.h" + +namespace ai +{ + class DpsPaladinStrategy : public GenericPaladinStrategy + { + public: + DpsPaladinStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.cpp new file mode 100644 index 000000000..f007e12e8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.cpp @@ -0,0 +1,61 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "GenericPaladinNonCombatStrategy.h" +#include "GenericPaladinStrategyActionNodeFactory.h" + +using namespace ai; + +GenericPaladinNonCombatStrategy::GenericPaladinNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericPaladinStrategyActionNodeFactory()); +} + +void GenericPaladinNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "party member dead", + NextAction::array(0, new NextAction("redemption", 30.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("flash of light", 25.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("flash of light on party", 26.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("holy light", 50.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("holy light on party", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse cure disease", + NextAction::array(0, new NextAction("cleanse disease", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure disease", + NextAction::array(0, new NextAction("cleanse disease on party", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse cure poison", + NextAction::array(0, new NextAction("cleanse poison", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure poison", + NextAction::array(0, new NextAction("cleanse poison on party", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse cure magic", + NextAction::array(0, new NextAction("cleanse magic", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure magic", + NextAction::array(0, new NextAction("cleanse magic on party", 40.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.h new file mode 100644 index 000000000..3bcb4e614 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinNonCombatStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericPaladinNonCombatStrategy : public NonCombatStrategy + { + public: + GenericPaladinNonCombatStrategy(PlayerbotAI* ai); + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.cpp b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.cpp new file mode 100644 index 000000000..e5f851143 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.cpp @@ -0,0 +1,76 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericPaladinStrategy.h" +#include "GenericPaladinStrategyActionNodeFactory.h" + +using namespace ai; + + +GenericPaladinStrategy::GenericPaladinStrategy(PlayerbotAI* ai) : MeleeCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericPaladinStrategyActionNodeFactory()); +} + +void GenericPaladinStrategy::InitTriggers(std::list &triggers) +{ + MeleeCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("divine protection", ACTION_CRITICAL_HEAL + 2), new NextAction("holy light", ACTION_CRITICAL_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("holy light on party", ACTION_CRITICAL_HEAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "hammer of justice interrupt", + NextAction::array(0, new NextAction("hammer of justice", ACTION_INTERRUPT), NULL))); + + triggers.push_back(new TriggerNode( + "hammer of justice on enemy healer", + NextAction::array(0, new NextAction("hammer of justice on enemy healer", ACTION_INTERRUPT), NULL))); + + triggers.push_back(new TriggerNode( + "hammer of justice on snare target", + NextAction::array(0, new NextAction("hammer of justice on snare target", ACTION_MOVE + 1), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("lay on hands", ACTION_EMERGENCY), NULL))); + + triggers.push_back(new TriggerNode( + "party member critical health", + NextAction::array(0, new NextAction("lay on hands on party", ACTION_EMERGENCY), NULL))); + + triggers.push_back(new TriggerNode( + "target critical health", + NextAction::array(0, new NextAction("hammer of wrath", ACTION_HIGH + 1), NULL))); +} + +void PaladinCureStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "cleanse cure disease", + NextAction::array(0, new NextAction("cleanse disease", ACTION_DISPEL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure disease", + NextAction::array(0, new NextAction("cleanse disease on party", ACTION_DISPEL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse cure poison", + NextAction::array(0, new NextAction("cleanse poison", ACTION_DISPEL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure poison", + NextAction::array(0, new NextAction("cleanse poison on party", ACTION_DISPEL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse cure magic", + NextAction::array(0, new NextAction("cleanse magic", ACTION_DISPEL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse party member cure magic", + NextAction::array(0, new NextAction("cleanse magic on party", ACTION_DISPEL + 1), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.h b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.h new file mode 100644 index 000000000..e2b32912b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../Strategy.h" +#include "PaladinAiObjectContext.h" +#include "../generic/MeleeCombatStrategy.h" + +namespace ai +{ + class GenericPaladinStrategy : public MeleeCombatStrategy + { + public: + GenericPaladinStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "paladin"; } + }; + + class PaladinCureStrategy : public Strategy + { + public: + PaladinCureStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cure"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategyActionNodeFactory.h b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategyActionNodeFactory.h new file mode 100644 index 000000000..6997f7ee2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/GenericPaladinStrategyActionNodeFactory.h @@ -0,0 +1,198 @@ +#pragma once + +namespace ai +{ + class GenericPaladinStrategyActionNodeFactory : public NamedObjectFactory + { + public: + GenericPaladinStrategyActionNodeFactory() + { + creators["seal of light"] = &seal_of_light; + creators["cleanse poison"] = &cleanse_poison; + creators["cleanse disease"] = &cleanse_disease; + creators["cleanse magic"] = &cleanse_magic; + creators["cleanse poison on party"] = &cleanse_poison_on_party; + creators["cleanse disease on party"] = &cleanse_disease_on_party; + creators["seal of wisdom"] = &seal_of_wisdom; + creators["seal of justice"] = &seal_of_justice; + creators["hand of reckoning"] = &hand_of_reckoning; + creators["judgement of wisdom"] = &judgement_of_wisdom; + creators["divine shield"] = &divine_shield; + creators["flash of light"] = &flash_of_light; + creators["flash of light on party"] = &flash_of_light_on_party; + creators["holy wrath"] = &holy_wrath; + creators["lay on hands"] = &lay_on_hands; + creators["lay on hands on party"] = &lay_on_hands_on_party; + creators["hammer of wrath"] = &hammer_of_wrath; + creators["retribution aura"] = &retribution_aura; + creators["blessing of kings"] = &blessing_of_kings; + creators["blessing of wisdom"] = &blessing_of_wisdom; + creators["blessing of kings on party"] = &blessing_of_kings_on_party; + creators["blessing of wisdom on party"] = &blessing_of_wisdom_on_party; + creators["blessing of sanctuary"] = &blessing_of_sanctuary; + } + private: + static ActionNode* blessing_of_sanctuary(PlayerbotAI* ai) + { + return new ActionNode ("blessing of sanctuary", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* blessing_of_kings(PlayerbotAI* ai) + { + return new ActionNode ("blessing of kings", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* blessing_of_wisdom(PlayerbotAI* ai) + { + return new ActionNode ("blessing of wisdom", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* blessing_of_kings_on_party(PlayerbotAI* ai) + { + return new ActionNode ("blessing of kings on party", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* blessing_of_wisdom_on_party(PlayerbotAI* ai) + { + return new ActionNode ("blessing of wisdom on party", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* retribution_aura(PlayerbotAI* ai) + { + return new ActionNode ("retribution aura", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("devotion aura"), NULL), + /*C*/ NULL); + } + static ActionNode* lay_on_hands(PlayerbotAI* ai) + { + return new ActionNode ("lay on hands", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("divine shield"), new NextAction("flash of light"), NULL), + /*C*/ NULL); + } + static ActionNode* lay_on_hands_on_party(PlayerbotAI* ai) + { + return new ActionNode ("lay on hands on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("flash of light"), NULL), + /*C*/ NULL); + } + static ActionNode* seal_of_light(PlayerbotAI* ai) + { + return new ActionNode ("seal of light", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("seal of justice"), NULL), + /*C*/ NULL); + } + static ActionNode* cleanse_poison(PlayerbotAI* ai) + { + return new ActionNode ("cleanse poison", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("purify poison"), NULL), + /*C*/ NULL); + } + static ActionNode* cleanse_magic(PlayerbotAI* ai) + { + return new ActionNode ("cleanse magic", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* cleanse_disease(PlayerbotAI* ai) + { + return new ActionNode ("cleanse disease", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("purify disease"), NULL), + /*C*/ NULL); + } + static ActionNode* cleanse_poison_on_party(PlayerbotAI* ai) + { + return new ActionNode ("cleanse poison on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("purify poison on party"), NULL), + /*C*/ NULL); + } + static ActionNode* cleanse_disease_on_party(PlayerbotAI* ai) + { + return new ActionNode ("cleanse disease on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("purify disease on party"), NULL), + /*C*/ NULL); + } + static ActionNode* seal_of_wisdom(PlayerbotAI* ai) + { + return new ActionNode ("seal of wisdom", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("seal of justice"), NULL), + /*C*/ NULL); + } + static ActionNode* seal_of_justice(PlayerbotAI* ai) + { + return new ActionNode ("seal of justice", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("seal of righteousness"), NULL), + /*C*/ NULL); + } + static ActionNode* hand_of_reckoning(PlayerbotAI* ai) + { + return new ActionNode ("hand of reckoning", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("judgement of justice"), NULL), + /*C*/ NULL); + } + static ActionNode* judgement_of_wisdom(PlayerbotAI* ai) + { + return new ActionNode ("judgement of wisdom", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("judgement of light"), NULL), + /*C*/ NULL); + } + static ActionNode* divine_shield(PlayerbotAI* ai) + { + return new ActionNode ("divine shield", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("divine protection"), NULL), + /*C*/ NULL); + } + static ActionNode* flash_of_light(PlayerbotAI* ai) + { + return new ActionNode ("flash of light", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("holy light"), NULL), + /*C*/ NULL); + } + static ActionNode* flash_of_light_on_party(PlayerbotAI* ai) + { + return new ActionNode ("flash of light on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("holy light on party"), NULL), + /*C*/ NULL); + } + static ActionNode* holy_wrath(PlayerbotAI* ai) + { + return new ActionNode ("holy wrath", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("consecration"), NULL), + /*C*/ NULL); + } + static ActionNode* hammer_of_wrath(PlayerbotAI* ai) + { + return new ActionNode ("hammer of wrath", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + }; + +}; diff --git a/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.cpp b/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.cpp new file mode 100644 index 000000000..acfe72c07 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.cpp @@ -0,0 +1,32 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "HealPaladinStrategy.h" + +using namespace ai; + +HealPaladinStrategy::HealPaladinStrategy(PlayerbotAI* ai) : GenericPaladinStrategy(ai) +{ +} + +NextAction** HealPaladinStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("melee", ACTION_NORMAL), NULL); +} + +void HealPaladinStrategy::InitTriggers(std::list &triggers) +{ + GenericPaladinStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("flash of light", ACTION_MEDIUM_HEAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("flash of light on party", ACTION_MEDIUM_HEAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of sanctuary", ACTION_HIGH + 9), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.h b/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.h new file mode 100644 index 000000000..7846bfd08 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/HealPaladinStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericPaladinStrategy.h" + +namespace ai +{ + class HealPaladinStrategy : public GenericPaladinStrategy + { + public: + HealPaladinStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "heal"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_HEAL | STRATEGY_TYPE_MELEE; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.cpp b/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.cpp new file mode 100644 index 000000000..1cebca718 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.h b/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.h new file mode 100644 index 000000000..0f3cdf43a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinActions.h @@ -0,0 +1,371 @@ +#pragma once +#include "../actions/GenericActions.h" + +namespace ai +{ + class CastJudgementOfLightAction : public CastMeleeSpellAction + { + public: + CastJudgementOfLightAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "judgement of light") {} + }; + + class CastJudgementOfWisdomAction : public CastMeleeSpellAction + { + public: + CastJudgementOfWisdomAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "judgement of wisdom") {} + }; + + class CastJudgementOfJusticeAction : public CastMeleeSpellAction + { + public: + CastJudgementOfJusticeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "judgement of justice") {} + }; + + class CastRighteousFuryAction : public CastBuffSpellAction + { + public: + CastRighteousFuryAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "righteous fury") {} + }; + + class CastDevotionAuraAction : public CastBuffSpellAction + { + public: + CastDevotionAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "devotion aura") {} + }; + + class CastRetributionAuraAction : public CastBuffSpellAction + { + public: + CastRetributionAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "retribution aura") {} + }; + + class CastConcentrationAuraAction : public CastBuffSpellAction + { + public: + CastConcentrationAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "concentration aura") {} + }; + + class CastDivineStormAction : public CastBuffSpellAction + { + public: + CastDivineStormAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine storm") {} + }; + + class CastCrusaderStrikeAction : public CastMeleeSpellAction + { + public: + CastCrusaderStrikeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "crusader strike") {} + }; + + class CastShadowResistanceAuraAction : public CastBuffSpellAction + { + public: + CastShadowResistanceAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "shadow resistance aura") {} + }; + + class CastFrostResistanceAuraAction : public CastBuffSpellAction + { + public: + CastFrostResistanceAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "frost resistance aura") {} + }; + + class CastFireResistanceAuraAction : public CastBuffSpellAction + { + public: + CastFireResistanceAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "fire resistance aura") {} + }; + + class CastCrusaderAuraAction : public CastBuffSpellAction + { + public: + CastCrusaderAuraAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "crusader aura") {} + }; + + class CastSealOfRighteousnessAction : public CastBuffSpellAction + { + public: + CastSealOfRighteousnessAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of righteousness") {} + }; + + class CastSealOfJusticeAction : public CastBuffSpellAction + { + public: + CastSealOfJusticeAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of justice") {} + }; + + + class CastSealOfLightAction : public CastBuffSpellAction + { + public: + CastSealOfLightAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of light") {} + }; + + class CastSealOfWisdomAction : public CastBuffSpellAction + { + public: + CastSealOfWisdomAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of wisdom") {} + }; + + class CastSealOfCommandAction : public CastBuffSpellAction + { + public: + CastSealOfCommandAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of command") {} + }; + + class CastSealOfVengeanceAction : public CastBuffSpellAction + { + public: + CastSealOfVengeanceAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "seal of vengeance") {} + }; + + + class CastBlessingOfMightAction : public CastBuffSpellAction + { + public: + CastBlessingOfMightAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "blessing of might") {} + }; + + class CastBlessingOfMightOnPartyAction : public BuffOnPartyAction + { + public: + CastBlessingOfMightOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "blessing of might") {} + virtual string getName() { return "blessing of might on party";} + }; + + class CastBlessingOfWisdomAction : public CastBuffSpellAction + { + public: + CastBlessingOfWisdomAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "blessing of wisdom") {} + }; + + class CastBlessingOfWisdomOnPartyAction : public BuffOnPartyAction + { + public: + CastBlessingOfWisdomOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "blessing of wisdom") {} + virtual string getName() { return "blessing of wisdom on party";} + }; + + class CastBlessingOfKingsAction : public CastBuffSpellAction + { + public: + CastBlessingOfKingsAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "blessing of kings") {} + }; + + class CastBlessingOfKingsOnPartyAction : public BuffOnPartyAction + { + public: + CastBlessingOfKingsOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "blessing of kings") {} + virtual string getName() { return "blessing of kings on party";} + }; + + class CastBlessingOfSanctuaryAction : public CastBuffSpellAction + { + public: + CastBlessingOfSanctuaryAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "blessing of sanctuary") {} + }; + + class CastBlessingOfSanctuaryOnPartyAction : public BuffOnPartyAction + { + public: + CastBlessingOfSanctuaryOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "blessing of sanctuary") {} + virtual string getName() { return "blessing of sanctuary on party";} + }; + + class CastHolyLightAction : public CastHealingSpellAction + { + public: + CastHolyLightAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "holy light") {} + }; + + class CastHolyLightOnPartyAction : public HealPartyMemberAction + { + public: + CastHolyLightOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "holy light") {} + + virtual string getName() { return "holy light on party"; } + }; + + class CastFlashOfLightAction : public CastHealingSpellAction + { + public: + CastFlashOfLightAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "flash of light") {} + }; + + class CastFlashOfLightOnPartyAction : public HealPartyMemberAction + { + public: + CastFlashOfLightOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "flash of light") {} + + virtual string getName() { return "flash of light on party"; } + }; + + class CastLayOnHandsAction : public CastHealingSpellAction + { + public: + CastLayOnHandsAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "lay on hands") {} + }; + + class CastLayOnHandsOnPartyAction : public HealPartyMemberAction + { + public: + CastLayOnHandsOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "lay on hands") {} + + virtual string getName() { return "lay on hands on party"; } + }; + + class CastDivineProtectionAction : public CastBuffSpellAction + { + public: + CastDivineProtectionAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine protection") {} + }; + + class CastDivineProtectionOnPartyAction : public HealPartyMemberAction + { + public: + CastDivineProtectionOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "divine protection") {} + + virtual string getName() { return "divine protection on party"; } + }; + + class CastDivineShieldAction: public CastBuffSpellAction + { + public: + CastDivineShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine shield") {} + }; + + class CastConsecrationAction : public CastMeleeSpellAction + { + public: + CastConsecrationAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "consecration") {} + }; + + class CastHolyWrathAction : public CastMeleeSpellAction + { + public: + CastHolyWrathAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "holy wrath") {} + }; + + class CastHammerOfJusticeAction : public CastMeleeSpellAction + { + public: + CastHammerOfJusticeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "hammer of justice") {} + }; + + class CastHammerOfWrathAction : public CastMeleeSpellAction + { + public: + CastHammerOfWrathAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "hammer of wrath") {} + }; + + class CastHammerOfTheRighteousAction : public CastMeleeSpellAction + { + public: + CastHammerOfTheRighteousAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "hammer of the righteous") {} + }; + + class CastPurifyPoisonAction : public CastCureSpellAction + { + public: + CastPurifyPoisonAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "purify") {} + }; + + class CastPurifyDiseaseAction : public CastCureSpellAction + { + public: + CastPurifyDiseaseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "purify") {} + }; + + class CastPurifyPoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastPurifyPoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "purify", DISPEL_POISON) {} + + virtual string getName() { return "purify poison on party"; } + }; + + class CastPurifyDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastPurifyDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "purify", DISPEL_DISEASE) {} + + virtual string getName() { return "purify disease on party"; } + }; + + class CastHandOfReckoningAction : public CastSpellAction + { + public: + CastHandOfReckoningAction(PlayerbotAI* ai) : CastSpellAction(ai, "hand of reckoning") {} + }; + + class CastCleansePoisonAction : public CastCureSpellAction + { + public: + CastCleansePoisonAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cleanse") {} + }; + + class CastCleanseDiseaseAction : public CastCureSpellAction + { + public: + CastCleanseDiseaseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cleanse") {} + }; + + class CastCleanseMagicAction : public CastCureSpellAction + { + public: + CastCleanseMagicAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cleanse") {} + }; + + class CastCleansePoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastCleansePoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse", DISPEL_POISON) {} + + virtual string getName() { return "cleanse poison on party"; } + }; + + class CastCleanseDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastCleanseDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse", DISPEL_DISEASE) {} + + virtual string getName() { return "cleanse disease on party"; } + }; + + class CastCleanseMagicOnPartyAction : public CurePartyMemberAction + { + public: + CastCleanseMagicOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse", DISPEL_MAGIC) {} + + virtual string getName() { return "cleanse magic on party"; } + }; + + BEGIN_SPELL_ACTION(CastAvengersShieldAction, "avenger's shield") + END_SPELL_ACTION() + + BEGIN_SPELL_ACTION(CastExorcismAction, "exorcism") + END_SPELL_ACTION() + + class CastHolyShieldAction : public CastBuffSpellAction + { + public: + CastHolyShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "holy shield") {} + }; + + class CastRedemptionAction : public ResurrectPartyMemberAction + { + public: + CastRedemptionAction(PlayerbotAI* ai) : ResurrectPartyMemberAction(ai, "redemption") {} + }; + + class CastHammerOfJusticeOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastHammerOfJusticeOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "hammer of justice") {} + }; + + class CastHammerOfJusticeSnareAction : public CastSnareSpellAction + { + public: + CastHammerOfJusticeSnareAction(PlayerbotAI* ai) : CastSnareSpellAction(ai, "hammer of justice") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.cpp new file mode 100644 index 000000000..e69385c5f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.cpp @@ -0,0 +1,296 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinActions.h" +#include "PaladinTriggers.h" +#include "PaladinAiObjectContext.h" +#include "GenericPaladinNonCombatStrategy.h" +#include "TankPaladinStrategy.h" +#include "DpsPaladinStrategy.h" +#include "PaladinBuffStrategies.h" +#include "../NamedObjectContext.h" +#include "HealPaladinStrategy.h" + +using namespace ai; + +namespace ai +{ + namespace paladin + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &paladin::StrategyFactoryInternal::nc; + creators["cure"] = &paladin::StrategyFactoryInternal::cure; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new GenericPaladinNonCombatStrategy(ai); } + static Strategy* cure(PlayerbotAI* ai) { return new PaladinCureStrategy(ai); } + }; + + class ResistanceStrategyFactoryInternal : public NamedObjectContext + { + public: + ResistanceStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["rshadow"] = &paladin::ResistanceStrategyFactoryInternal::rshadow; + creators["rfrost"] = &paladin::ResistanceStrategyFactoryInternal::rfrost; + creators["rfire"] = &paladin::ResistanceStrategyFactoryInternal::rfire; + } + + private: + static Strategy* rshadow(PlayerbotAI* ai) { return new PaladinShadowResistanceStrategy(ai); } + static Strategy* rfrost(PlayerbotAI* ai) { return new PaladinFrostResistanceStrategy(ai); } + static Strategy* rfire(PlayerbotAI* ai) { return new PaladinFireResistanceStrategy(ai); } + }; + + class BuffStrategyFactoryInternal : public NamedObjectContext + { + public: + BuffStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["bhealth"] = &paladin::BuffStrategyFactoryInternal::bhealth; + creators["bmana"] = &paladin::BuffStrategyFactoryInternal::bmana; + creators["bdps"] = &paladin::BuffStrategyFactoryInternal::bdps; + creators["barmor"] = &paladin::BuffStrategyFactoryInternal::barmor; + creators["bspeed"] = &paladin::BuffStrategyFactoryInternal::bspeed; + creators["bthreat"] = &paladin::BuffStrategyFactoryInternal::bthreat; + } + + private: + static Strategy* bhealth(PlayerbotAI* ai) { return new PaladinBuffHealthStrategy(ai); } + static Strategy* bmana(PlayerbotAI* ai) { return new PaladinBuffManaStrategy(ai); } + static Strategy* bdps(PlayerbotAI* ai) { return new PaladinBuffDpsStrategy(ai); } + static Strategy* barmor(PlayerbotAI* ai) { return new PaladinBuffArmorStrategy(ai); } + static Strategy* bspeed(PlayerbotAI* ai) { return new PaladinBuffSpeedStrategy(ai); } + static Strategy* bthreat(PlayerbotAI* ai) { return new PaladinBuffThreatStrategy(ai); } + }; + + class CombatStrategyFactoryInternal : public NamedObjectContext + { + public: + CombatStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["tank"] = &paladin::CombatStrategyFactoryInternal::tank; + creators["dps"] = &paladin::CombatStrategyFactoryInternal::dps; + creators["heal"] = &paladin::CombatStrategyFactoryInternal::heal; + } + + private: + static Strategy* tank(PlayerbotAI* ai) { return new TankPaladinStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new DpsPaladinStrategy(ai); } + static Strategy* heal(PlayerbotAI* ai) { return new HealPaladinStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace paladin + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["judgement of wisdom"] = &TriggerFactoryInternal::judgement_of_wisdom; + creators["judgement of light"] = &TriggerFactoryInternal::judgement_of_light; + creators["blessing"] = &TriggerFactoryInternal::blessing; + creators["seal"] = &TriggerFactoryInternal::seal; + creators["art of war"] = &TriggerFactoryInternal::art_of_war; + creators["blessing on party"] = &TriggerFactoryInternal::blessing_on_party; + creators["crusader aura"] = &TriggerFactoryInternal::crusader_aura; + creators["retribution aura"] = &TriggerFactoryInternal::retribution_aura; + creators["devotion aura"] = &TriggerFactoryInternal::devotion_aura; + creators["shadow resistance aura"] = &TriggerFactoryInternal::shadow_resistance_aura; + creators["frost resistance aura"] = &TriggerFactoryInternal::frost_resistance_aura; + creators["fire resistance aura"] = &TriggerFactoryInternal::fire_resistance_aura; + creators["hammer of justice snare"] = &TriggerFactoryInternal::hammer_of_justice_snare; + creators["hammer of justice interrupt"] = &TriggerFactoryInternal::hammer_of_justice_interrupt; + creators["cleanse cure disease"] = &TriggerFactoryInternal::CleanseCureDisease; + creators["cleanse party member cure disease"] = &TriggerFactoryInternal::CleanseCurePartyMemberDisease; + creators["cleanse cure poison"] = &TriggerFactoryInternal::CleanseCurePoison; + creators["cleanse party member cure poison"] = &TriggerFactoryInternal::CleanseCurePartyMemberPoison; + creators["cleanse cure magic"] = &TriggerFactoryInternal::CleanseCureMagic; + creators["cleanse party member cure magic"] = &TriggerFactoryInternal::CleanseCurePartyMemberMagic; + creators["righteous fury"] = &TriggerFactoryInternal::righteous_fury; + creators["holy shield"] = &TriggerFactoryInternal::holy_shield; + creators["hammer of justice on enemy healer"] = &TriggerFactoryInternal::hammer_of_justice_on_enemy_target; + creators["hammer of justice on snare target"] = &TriggerFactoryInternal::hammer_of_justice_on_snare_target; + } + + private: + static Trigger* holy_shield(PlayerbotAI* ai) { return new HolyShieldTrigger(ai); } + static Trigger* righteous_fury(PlayerbotAI* ai) { return new RighteousFuryTrigger(ai); } + static Trigger* judgement_of_wisdom(PlayerbotAI* ai) { return new JudgementOfWisdomTrigger(ai); } + static Trigger* judgement_of_light(PlayerbotAI* ai) { return new JudgementOfLightTrigger(ai); } + static Trigger* blessing(PlayerbotAI* ai) { return new BlessingTrigger(ai); } + static Trigger* seal(PlayerbotAI* ai) { return new SealTrigger(ai); } + static Trigger* art_of_war(PlayerbotAI* ai) { return new ArtOfWarTrigger(ai); } + static Trigger* blessing_on_party(PlayerbotAI* ai) { return new BlessingOnPartyTrigger(ai); } + static Trigger* crusader_aura(PlayerbotAI* ai) { return new CrusaderAuraTrigger(ai); } + static Trigger* retribution_aura(PlayerbotAI* ai) { return new RetributionAuraTrigger(ai); } + static Trigger* devotion_aura(PlayerbotAI* ai) { return new DevotionAuraTrigger(ai); } + static Trigger* shadow_resistance_aura(PlayerbotAI* ai) { return new ShadowResistanceAuraTrigger(ai); } + static Trigger* frost_resistance_aura(PlayerbotAI* ai) { return new FrostResistanceAuraTrigger(ai); } + static Trigger* fire_resistance_aura(PlayerbotAI* ai) { return new FireResistanceAuraTrigger(ai); } + static Trigger* hammer_of_justice_snare(PlayerbotAI* ai) { return new HammerOfJusticeSnareTrigger(ai); } + static Trigger* hammer_of_justice_interrupt(PlayerbotAI* ai) { return new HammerOfJusticeInterruptSpellTrigger(ai); } + static Trigger* CleanseCureDisease(PlayerbotAI* ai) { return new CleanseCureDiseaseTrigger(ai); } + static Trigger* CleanseCurePartyMemberDisease(PlayerbotAI* ai) { return new CleanseCurePartyMemberDiseaseTrigger(ai); } + static Trigger* CleanseCurePoison(PlayerbotAI* ai) { return new CleanseCurePoisonTrigger(ai); } + static Trigger* CleanseCurePartyMemberPoison(PlayerbotAI* ai) { return new CleanseCurePartyMemberPoisonTrigger(ai); } + static Trigger* CleanseCureMagic(PlayerbotAI* ai) { return new CleanseCureMagicTrigger(ai); } + static Trigger* CleanseCurePartyMemberMagic(PlayerbotAI* ai) { return new CleanseCurePartyMemberMagicTrigger(ai); } + static Trigger* hammer_of_justice_on_enemy_target(PlayerbotAI* ai) { return new HammerOfJusticeEnemyHealerTrigger(ai); } + static Trigger* hammer_of_justice_on_snare_target(PlayerbotAI* ai) { return new HammerOfJusticeSnareTrigger(ai); } + }; + }; +}; + +namespace ai +{ + namespace paladin + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["seal of command"] = &AiObjectContextInternal::seal_of_command; + creators["seal of vengeance"] = &AiObjectContextInternal::seal_of_vengeance; + creators["blessing of might"] = &AiObjectContextInternal::blessing_of_might; + creators["blessing of wisdom"] = &AiObjectContextInternal::blessing_of_wisdom; + creators["blessing of kings"] = &AiObjectContextInternal::blessing_of_kings; + creators["blessing of sanctuary"] = &AiObjectContextInternal::blessing_of_sanctuary; + creators["divine storm"] = &AiObjectContextInternal::divine_storm; + creators["blessing of kings on party"] = &AiObjectContextInternal::blessing_of_kings_on_party; + creators["blessing of might on party"] = &AiObjectContextInternal::blessing_of_might_on_party; + creators["blessing of wisdom on party"] = &AiObjectContextInternal::blessing_of_wisdom_on_party; + creators["redemption"] = &AiObjectContextInternal::redemption; + creators["crusader strike"] = &AiObjectContextInternal::crusader_strike; + creators["crusader aura"] = &AiObjectContextInternal::crusader_aura; + creators["seal of light"] = &AiObjectContextInternal::seal_of_light; + creators["devotion aura"] = &AiObjectContextInternal::devotion_aura; + creators["holy wrath"] = &AiObjectContextInternal::holy_wrath; + creators["consecration"] = &AiObjectContextInternal::consecration; + creators["cleanse disease"] = &AiObjectContextInternal::cleanse_disease; + creators["cleanse poison"] = &AiObjectContextInternal::cleanse_poison; + creators["cleanse magic"] = &AiObjectContextInternal::cleanse_magic; + creators["purify disease"] = &AiObjectContextInternal::purify_disease; + creators["purify poison"] = &AiObjectContextInternal::purify_poison; + creators["cleanse poison on party"] = &AiObjectContextInternal::cleanse_poison_on_party; + creators["cleanse disease on party"] = &AiObjectContextInternal::cleanse_disease_on_party; + creators["cleanse magic on party"] = &AiObjectContextInternal::cleanse_magic_on_party; + creators["purify poison on party"] = &AiObjectContextInternal::purify_poison_on_party; + creators["purify disease on party"] = &AiObjectContextInternal::purify_disease_on_party; + creators["seal of wisdom"] = &AiObjectContextInternal::seal_of_wisdom; + creators["seal of justice"] = &AiObjectContextInternal::seal_of_justice; + creators["seal of righteousness"] = &AiObjectContextInternal::seal_of_righteousness; + creators["flash of light"] = &AiObjectContextInternal::flash_of_light; + creators["hand of reckoning"] = &AiObjectContextInternal::hand_of_reckoning; + creators["avenger's shield"] = &AiObjectContextInternal::avengers_shield; + creators["exorcism"] = &AiObjectContextInternal::exorcism; + creators["judgement of light"] = &AiObjectContextInternal::judgement_of_light; + creators["judgement of wisdom"] = &AiObjectContextInternal::judgement_of_wisdom; + creators["divine shield"] = &AiObjectContextInternal::divine_shield; + creators["divine protection"] = &AiObjectContextInternal::divine_protection; + creators["divine protection on party"] =&AiObjectContextInternal::divine_protection_on_party; + creators["hammer of justice"] = &AiObjectContextInternal::hammer_of_justice; + creators["flash of light on party"] = &AiObjectContextInternal::flash_of_light_on_party; + creators["holy light"] = &AiObjectContextInternal::holy_light; + creators["holy light on party"] = &AiObjectContextInternal::holy_light_on_party; + creators["lay on hands"] = &AiObjectContextInternal::lay_on_hands; + creators["lay on hands on party"] = &AiObjectContextInternal::lay_on_hands_on_party; + creators["judgement of justice"] = &AiObjectContextInternal::judgement_of_justice; + creators["hammer of wrath"] = &AiObjectContextInternal::hammer_of_wrath; + creators["holy shield"] = &AiObjectContextInternal::holy_shield; + creators["hammer of the righteous"] = &AiObjectContextInternal::hammer_of_the_righteous; + creators["retribution aura"] = &AiObjectContextInternal::retribution_aura; + creators["shadow resistance aura"] = &AiObjectContextInternal::shadow_resistance_aura; + creators["frost resistance aura"] = &AiObjectContextInternal::frost_resistance_aura; + creators["fire resistance aura"] = &AiObjectContextInternal::fire_resistance_aura; + creators["righteous fury"] = &AiObjectContextInternal::righteous_fury; + creators["hammer of justice on enemy healer"] = &AiObjectContextInternal::hammer_of_justice_on_enemy_healer; + creators["hammer of justice on snare target"] = &AiObjectContextInternal::hammer_of_justice_on_snare_target; + } + + private: + static Action* righteous_fury(PlayerbotAI* ai) { return new CastRighteousFuryAction(ai); } + static Action* seal_of_command(PlayerbotAI* ai) { return new CastSealOfCommandAction(ai); } + static Action* seal_of_vengeance(PlayerbotAI* ai) { return new CastSealOfVengeanceAction(ai); } + static Action* blessing_of_sanctuary(PlayerbotAI* ai) { return new CastBlessingOfSanctuaryAction(ai); } + static Action* blessing_of_might(PlayerbotAI* ai) { return new CastBlessingOfMightAction(ai); } + static Action* blessing_of_wisdom(PlayerbotAI* ai) { return new CastBlessingOfWisdomAction(ai); } + static Action* blessing_of_kings(PlayerbotAI* ai) { return new CastBlessingOfKingsAction(ai); } + static Action* divine_storm(PlayerbotAI* ai) { return new CastDivineStormAction(ai); } + static Action* blessing_of_kings_on_party(PlayerbotAI* ai) { return new CastBlessingOfKingsOnPartyAction(ai); } + static Action* blessing_of_might_on_party(PlayerbotAI* ai) { return new CastBlessingOfMightOnPartyAction(ai); } + static Action* blessing_of_wisdom_on_party(PlayerbotAI* ai) { return new CastBlessingOfWisdomOnPartyAction(ai); } + static Action* redemption(PlayerbotAI* ai) { return new CastRedemptionAction(ai); } + static Action* crusader_strike(PlayerbotAI* ai) { return new CastCrusaderStrikeAction(ai); } + static Action* crusader_aura(PlayerbotAI* ai) { return new CastCrusaderAuraAction(ai); } + static Action* seal_of_light(PlayerbotAI* ai) { return new CastSealOfLightAction(ai); } + static Action* devotion_aura(PlayerbotAI* ai) { return new CastDevotionAuraAction(ai); } + static Action* holy_wrath(PlayerbotAI* ai) { return new CastHolyWrathAction(ai); } + static Action* consecration(PlayerbotAI* ai) { return new CastConsecrationAction(ai); } + static Action* cleanse_poison(PlayerbotAI* ai) { return new CastCleansePoisonAction(ai); } + static Action* cleanse_disease(PlayerbotAI* ai) { return new CastCleanseDiseaseAction(ai); } + static Action* cleanse_magic(PlayerbotAI* ai) { return new CastCleanseMagicAction(ai); } + static Action* purify_poison(PlayerbotAI* ai) { return new CastPurifyPoisonAction(ai); } + static Action* purify_disease(PlayerbotAI* ai) { return new CastPurifyDiseaseAction(ai); } + static Action* cleanse_poison_on_party(PlayerbotAI* ai) { return new CastCleansePoisonOnPartyAction(ai); } + static Action* cleanse_disease_on_party(PlayerbotAI* ai) { return new CastCleanseDiseaseOnPartyAction(ai); } + static Action* cleanse_magic_on_party(PlayerbotAI* ai) { return new CastCleanseMagicOnPartyAction(ai); } + static Action* purify_poison_on_party(PlayerbotAI* ai) { return new CastPurifyPoisonOnPartyAction(ai); } + static Action* purify_disease_on_party(PlayerbotAI* ai) { return new CastPurifyDiseaseOnPartyAction(ai); } + static Action* seal_of_wisdom(PlayerbotAI* ai) { return new CastSealOfWisdomAction(ai); } + static Action* seal_of_justice(PlayerbotAI* ai) { return new CastSealOfJusticeAction(ai); } + static Action* seal_of_righteousness(PlayerbotAI* ai) { return new CastSealOfRighteousnessAction(ai); } + static Action* flash_of_light(PlayerbotAI* ai) { return new CastFlashOfLightAction(ai); } + static Action* hand_of_reckoning(PlayerbotAI* ai) { return new CastHandOfReckoningAction(ai); } + static Action* avengers_shield(PlayerbotAI* ai) { return new CastAvengersShieldAction(ai); } + static Action* exorcism(PlayerbotAI* ai) { return new CastExorcismAction(ai); } + static Action* judgement_of_light(PlayerbotAI* ai) { return new CastJudgementOfLightAction(ai); } + static Action* judgement_of_wisdom(PlayerbotAI* ai) { return new CastJudgementOfWisdomAction(ai); } + static Action* divine_shield(PlayerbotAI* ai) { return new CastDivineShieldAction(ai); } + static Action* divine_protection(PlayerbotAI* ai) { return new CastDivineProtectionAction(ai); } + static Action* divine_protection_on_party(PlayerbotAI* ai) { return new CastDivineProtectionOnPartyAction(ai); } + static Action* hammer_of_justice(PlayerbotAI* ai) { return new CastHammerOfJusticeAction(ai); } + static Action* flash_of_light_on_party(PlayerbotAI* ai) { return new CastFlashOfLightOnPartyAction(ai); } + static Action* holy_light(PlayerbotAI* ai) { return new CastHolyLightAction(ai); } + static Action* holy_light_on_party(PlayerbotAI* ai) { return new CastHolyLightOnPartyAction(ai); } + static Action* lay_on_hands(PlayerbotAI* ai) { return new CastLayOnHandsAction(ai); } + static Action* lay_on_hands_on_party(PlayerbotAI* ai) { return new CastLayOnHandsOnPartyAction(ai); } + static Action* judgement_of_justice(PlayerbotAI* ai) { return new CastJudgementOfJusticeAction(ai); } + static Action* hammer_of_wrath(PlayerbotAI* ai) { return new CastHammerOfWrathAction(ai); } + static Action* holy_shield(PlayerbotAI* ai) { return new CastHolyShieldAction(ai); } + static Action* hammer_of_the_righteous(PlayerbotAI* ai) { return new CastHammerOfTheRighteousAction(ai); } + static Action* retribution_aura(PlayerbotAI* ai) { return new CastRetributionAuraAction(ai); } + static Action* shadow_resistance_aura(PlayerbotAI* ai) { return new CastShadowResistanceAuraAction(ai); } + static Action* frost_resistance_aura(PlayerbotAI* ai) { return new CastFrostResistanceAuraAction(ai); } + static Action* fire_resistance_aura(PlayerbotAI* ai) { return new CastFireResistanceAuraAction(ai); } + static Action* hammer_of_justice_on_enemy_healer(PlayerbotAI* ai) { return new CastHammerOfJusticeOnEnemyHealerAction(ai); } + static Action* hammer_of_justice_on_snare_target(PlayerbotAI* ai) { return new CastHammerOfJusticeSnareAction(ai); } + }; + }; +}; + + +PaladinAiObjectContext::PaladinAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::paladin::StrategyFactoryInternal()); + strategyContexts.Add(new ai::paladin::CombatStrategyFactoryInternal()); + strategyContexts.Add(new ai::paladin::BuffStrategyFactoryInternal()); + strategyContexts.Add(new ai::paladin::ResistanceStrategyFactoryInternal()); + actionContexts.Add(new ai::paladin::AiObjectContextInternal()); + triggerContexts.Add(new ai::paladin::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.h b/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.h new file mode 100644 index 000000000..1af334341 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class PaladinAiObjectContext : public AiObjectContext + { + public: + PaladinAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.cpp b/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.cpp new file mode 100644 index 000000000..ac2ac226a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.cpp @@ -0,0 +1,110 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "PaladinBuffStrategies.h" + +using namespace ai; + +void PaladinBuffManaStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "seal", + NextAction::array(0, new NextAction("seal of wisdom", 90.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing on party", + NextAction::array(0, new NextAction("blessing of wisdom on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of wisdom", ACTION_HIGH + 8), NULL))); +} + +void PaladinBuffHealthStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "seal", + NextAction::array(0, new NextAction("seal of light", 90.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing on party", + NextAction::array(0, new NextAction("blessing of kings on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of kings", ACTION_HIGH + 8), NULL))); +} + +void PaladinBuffSpeedStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "crusader aura", + NextAction::array(0, new NextAction("crusader aura", 40.0f), NULL))); +} + +void PaladinBuffDpsStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "seal", + NextAction::array(0, new NextAction("seal of vengeance", 89.0f), NULL))); + + triggers.push_back(new TriggerNode( + "retribution aura", + NextAction::array(0, new NextAction("retribution aura", 90.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing on party", + NextAction::array(0, new NextAction("blessing of might on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of might", ACTION_HIGH + 8), NULL))); +} + +void PaladinShadowResistanceStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "shadow resistance aura", + NextAction::array(0, new NextAction("shadow resistance aura", 90.0f), NULL))); +} + +void PaladinFrostResistanceStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "frost resistance aura", + NextAction::array(0, new NextAction("frost resistance aura", 90.0f), NULL))); +} + +void PaladinFireResistanceStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "fire resistance aura", + NextAction::array(0, new NextAction("fire resistance aura", 90.0f), NULL))); +} + + +void PaladinBuffArmorStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "devotion aura", + NextAction::array(0, new NextAction("devotion aura", 90.0f), NULL))); +} + +void PaladinBuffThreatStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "seal", + NextAction::array(0, new NextAction("seal of light", 89.0f), NULL))); + + triggers.push_back(new TriggerNode( + "retribution aura", + NextAction::array(0, new NextAction("retribution aura", 90.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing on party", + NextAction::array(0, new NextAction("blessing of kings on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of sanctuary", ACTION_HIGH + 8), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.h b/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.h new file mode 100644 index 000000000..3c1029b2e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinBuffStrategies.h @@ -0,0 +1,96 @@ +#pragma once + +#include "GenericPaladinStrategy.h" + +namespace ai +{ + class PaladinBuffManaStrategy : public Strategy + { + public: + PaladinBuffManaStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bmana"; } + }; + + class PaladinBuffHealthStrategy : public Strategy + { + public: + PaladinBuffHealthStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bhealth"; } + }; + + class PaladinBuffDpsStrategy : public Strategy + { + public: + PaladinBuffDpsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bdps"; } + }; + + class PaladinBuffArmorStrategy : public Strategy + { + public: + PaladinBuffArmorStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "barmor"; } + }; + + class PaladinBuffThreatStrategy : public Strategy + { + public: + PaladinBuffThreatStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bthreat"; } + }; + + class PaladinBuffSpeedStrategy : public Strategy + { + public: + PaladinBuffSpeedStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bspeed"; } + }; + + class PaladinShadowResistanceStrategy : public Strategy + { + public: + PaladinShadowResistanceStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "rshadow"; } + }; + + class PaladinFrostResistanceStrategy : public Strategy + { + public: + PaladinFrostResistanceStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "rfrost"; } + }; + + class PaladinFireResistanceStrategy : public Strategy + { + public: + PaladinFireResistanceStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "rfire"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.cpp b/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.cpp new file mode 100644 index 000000000..8c1c5d28c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "PaladinActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.h b/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.cpp b/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.cpp new file mode 100644 index 000000000..e810fe810 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinTriggers.h" +#include "PaladinActions.h" + +using namespace ai; + +bool SealTrigger::IsActive() +{ + Unit* target = GetTarget(); + return !ai->HasAura("seal of justice", target) && + !ai->HasAura("seal of command", target) && + !ai->HasAura("seal of vengeance", target) && + !ai->HasAura("seal of righteousness", target) && + !ai->HasAura("seal of light", target) && + !ai->HasAura("seal of wisdom", target); +} + +bool CrusaderAuraTrigger::IsActive() +{ + Unit* target = GetTarget(); + return AI_VALUE2(bool, "mounted", "self target") && !ai->HasAura("crusader aura", target); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.h b/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.h new file mode 100644 index 000000000..70ac65b37 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/PaladinTriggers.h @@ -0,0 +1,123 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + BUFF_TRIGGER(HolyShieldTrigger, "holy shield", "holy shield") + BUFF_TRIGGER(RighteousFuryTrigger, "righteous fury", "righteous fury") + + BUFF_TRIGGER(RetributionAuraTrigger, "retribution aura", "retribution aura") + + class CrusaderAuraTrigger : public BuffTrigger + { + public: + CrusaderAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "crusader aura") {} + virtual bool IsActive(); + }; + + class SealTrigger : public BuffTrigger + { + public: + SealTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "seal of justice") {} + virtual bool IsActive(); + }; + + DEBUFF_TRIGGER(JudgementOfLightTrigger, "judgement of light", "judgement of light") + DEBUFF_TRIGGER(JudgementOfWisdomTrigger, "judgement of wisdom", "judgement of wisdom") + + class BlessingOnPartyTrigger : public BuffOnPartyTrigger + { + public: + BlessingOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "blessing of kings", 7) {} + }; + + class BlessingTrigger : public BuffTrigger + { + public: + BlessingTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "blessing of sanctuary", 5) {} + }; + + class HammerOfJusticeInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + HammerOfJusticeInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "hammer of justice") {} + }; + + class HammerOfJusticeSnareTrigger : public SnareTargetTrigger + { + public: + HammerOfJusticeSnareTrigger(PlayerbotAI* ai) : SnareTargetTrigger(ai, "hammer of justice") {} + }; + + class ArtOfWarTrigger : public HasAuraTrigger + { + public: + ArtOfWarTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "the art of war") {} + }; + + class ShadowResistanceAuraTrigger : public BuffTrigger + { + public: + ShadowResistanceAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "shadow resistance aura") {} + }; + + class FrostResistanceAuraTrigger : public BuffTrigger + { + public: + FrostResistanceAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "frost resistance aura") {} + }; + + class FireResistanceAuraTrigger : public BuffTrigger + { + public: + FireResistanceAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "fire resistance aura") {} + }; + + class DevotionAuraTrigger : public BuffTrigger + { + public: + DevotionAuraTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "devotion aura") {} + }; + + class CleanseCureDiseaseTrigger : public NeedCureTrigger + { + public: + CleanseCureDiseaseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse", DISPEL_DISEASE) {} + }; + + class CleanseCurePartyMemberDiseaseTrigger : public PartyMemberNeedCureTrigger + { + public: + CleanseCurePartyMemberDiseaseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse", DISPEL_DISEASE) {} + }; + + class CleanseCurePoisonTrigger : public NeedCureTrigger + { + public: + CleanseCurePoisonTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse", DISPEL_POISON) {} + }; + + class CleanseCurePartyMemberPoisonTrigger : public PartyMemberNeedCureTrigger + { + public: + CleanseCurePartyMemberPoisonTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse", DISPEL_POISON) {} + }; + + class CleanseCureMagicTrigger : public NeedCureTrigger + { + public: + CleanseCureMagicTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse", DISPEL_MAGIC) {} + }; + + class CleanseCurePartyMemberMagicTrigger : public PartyMemberNeedCureTrigger + { + public: + CleanseCurePartyMemberMagicTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse", DISPEL_MAGIC) {} + }; + + class HammerOfJusticeEnemyHealerTrigger : public InterruptEnemyHealerTrigger + { + public: + HammerOfJusticeEnemyHealerTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "hammer of justice") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.cpp b/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.cpp new file mode 100644 index 000000000..c7b1abcfe --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.cpp @@ -0,0 +1,52 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PaladinMultipliers.h" +#include "TankPaladinStrategy.h" + +using namespace ai; + +TankPaladinStrategy::TankPaladinStrategy(PlayerbotAI* ai) : GenericPaladinStrategy(ai) +{ +} + +NextAction** TankPaladinStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("melee", ACTION_NORMAL), NULL); +} + +void TankPaladinStrategy::InitTriggers(std::list &triggers) +{ + GenericPaladinStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "judgement of light", + NextAction::array(0, new NextAction("judgement of light", ACTION_NORMAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "medium mana", + NextAction::array(0, new NextAction("judgement of wisdom", ACTION_NORMAL + 3), NULL))); + + triggers.push_back(new TriggerNode( + "righteous fury", + NextAction::array(0, new NextAction("righteous fury", ACTION_HIGH + 8), NULL))); + + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("hammer of the righteous", ACTION_HIGH + 6), new NextAction("avenger's shield", ACTION_HIGH + 6), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("consecration", ACTION_HIGH + 6), NULL))); + + triggers.push_back(new TriggerNode( + "lose aggro", + NextAction::array(0, new NextAction("hand of reckoning", ACTION_HIGH + 7), NULL))); + + triggers.push_back(new TriggerNode( + "holy shield", + NextAction::array(0, new NextAction("holy shield", ACTION_HIGH + 7), NULL))); + + triggers.push_back(new TriggerNode( + "blessing", + NextAction::array(0, new NextAction("blessing of sanctuary", ACTION_HIGH + 9), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.h b/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.h new file mode 100644 index 000000000..ca96457ce --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/paladin/TankPaladinStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericPaladinStrategy.h" + +namespace ai +{ + class TankPaladinStrategy : public GenericPaladinStrategy + { + public: + TankPaladinStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "tank"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.cpp b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.cpp new file mode 100644 index 000000000..9f8afe7f0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.cpp @@ -0,0 +1,68 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "HealPriestStrategy.h" +#include "GenericPriestStrategyActionNodeFactory.h" + +using namespace ai; + +GenericPriestStrategy::GenericPriestStrategy(PlayerbotAI* ai) : CombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericPriestStrategyActionNodeFactory()); +} + +void GenericPriestStrategy::InitTriggers(std::list &triggers) +{ + CombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("flash heal", 25.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("flash heal on party", 20.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("power word: shield", 70.0f), new NextAction("flash heal", 70.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member critical health", + NextAction::array(0, new NextAction("power word: shield on party", 60.0f), new NextAction("flash heal on party", 60.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("power word: shield", 60.0f), new NextAction("greater heal", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("power word: shield on party", 50.0f), new NextAction("greater heal on party", 50.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium threat", + NextAction::array(0, new NextAction("psychic scream", 50.0f), NULL))); + +} + +void PriestCureStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "dispel magic", + NextAction::array(0, new NextAction("dispel magic", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "dispel magic on party", + NextAction::array(0, new NextAction("dispel magic on party", 40.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "cure disease", + NextAction::array(0, new NextAction("abolish disease", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure disease", + NextAction::array(0, new NextAction("abolish disease on party", 30.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.h b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.h new file mode 100644 index 000000000..a009f36d4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategy.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class GenericPriestStrategy : public CombatStrategy + { + public: + GenericPriestStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + + }; + + class PriestCureStrategy : public Strategy + { + public: + PriestCureStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cure"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategyActionNodeFactory.h b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategyActionNodeFactory.h new file mode 100644 index 000000000..d3ef00b30 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/GenericPriestStrategyActionNodeFactory.h @@ -0,0 +1,173 @@ +#pragma once + +namespace ai +{ + class GenericPriestStrategyActionNodeFactory : public NamedObjectFactory + { + public: + GenericPriestStrategyActionNodeFactory() + { + creators["inner fire"] = &inner_fire; + creators["holy nova"] = &holy_nova; + creators["power word: fortitude"] = &power_word_fortitude; + creators["power word: fortitude on party"] = &power_word_fortitude_on_party; + creators["divine spirit"] = &divine_spirit; + creators["divine spirit on party"] = &divine_spirit_on_party; + creators["power word: shield"] = &power_word_shield; + creators["power word: shield on party"] = &power_word_shield_on_party; + creators["renew"] = &renew; + creators["renew on party"] = &renew_on_party; + creators["greater heal"] = &greater_heal; + creators["greater heal on party"] = &greater_heal_on_party; + creators["heal"] = &heal; + creators["heal on party"] = &heal_on_party; + creators["lesser heal"] = &lesser_heal; + creators["lesser heal on party"] = &lesser_heal_on_party; + creators["flash heal"] = &flash_heal; + creators["flash heal on party"] = &flash_heal_on_party; + creators["psychic scream"] = &psychic_scream; + creators["fade"] = &fade; + } + private: + static ActionNode* inner_fire(PlayerbotAI* ai) + { + return new ActionNode ("inner fire", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* holy_nova(PlayerbotAI* ai) + { + return new ActionNode ("holy nova", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* power_word_fortitude(PlayerbotAI* ai) + { + return new ActionNode ("power word: fortitude", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* power_word_fortitude_on_party(PlayerbotAI* ai) + { + return new ActionNode ("power word: fortitude on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* divine_spirit(PlayerbotAI* ai) + { + return new ActionNode ("divine spirit", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* divine_spirit_on_party(PlayerbotAI* ai) + { + return new ActionNode ("divine spirit on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* power_word_shield(PlayerbotAI* ai) + { + return new ActionNode ("power word: shield", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("renew", 50.0f), NULL), + /*C*/ NULL); + } + static ActionNode* power_word_shield_on_party(PlayerbotAI* ai) + { + return new ActionNode ("power word: shield on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("renew on party", 50.0f), NULL), + /*C*/ NULL); + } + static ActionNode* renew(PlayerbotAI* ai) + { + return new ActionNode ("renew", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* renew_on_party(PlayerbotAI* ai) + { + return new ActionNode ("renew on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* greater_heal(PlayerbotAI* ai) + { + return new ActionNode ("greater heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("heal"), NULL), + /*C*/ NULL); + } + static ActionNode* greater_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("greater heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("heal on party"), NULL), + /*C*/ NULL); + } + static ActionNode* heal(PlayerbotAI* ai) + { + return new ActionNode ("heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("lesser heal"), NULL), + /*C*/ NULL); + } + static ActionNode* heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("lesser heal on party"), NULL), + /*C*/ NULL); + } + static ActionNode* lesser_heal(PlayerbotAI* ai) + { + return new ActionNode ("lesser heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* lesser_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("lesser heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* flash_heal(PlayerbotAI* ai) + { + return new ActionNode ("flash heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("greater heal"), NULL), + /*C*/ NULL); + } + static ActionNode* flash_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("flash heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("greater heal on party"), NULL), + /*C*/ NULL); + } + static ActionNode* psychic_scream(PlayerbotAI* ai) + { + return new ActionNode ("psychic scream", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("fade"), NULL), + /*C*/ NULL); + } + static ActionNode* fade(PlayerbotAI* ai) + { + return new ActionNode ("fade", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("flee"), NULL), + /*C*/ NULL); + } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.cpp b/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.cpp new file mode 100644 index 000000000..8433f232d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.cpp @@ -0,0 +1,36 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "HealPriestStrategy.h" + +using namespace ai; + +NextAction** HealPriestStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("shoot", 10.0f), NULL); +} + +void HealPriestStrategy::InitTriggers(std::list &triggers) +{ + GenericPriestStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe heal", + NextAction::array(0, new NextAction("circle of healing", 27.0f), NULL))); + + triggers.push_back(new TriggerNode( + "almost full health", + NextAction::array(0, new NextAction("renew", 15.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member almost full health", + NextAction::array(0, new NextAction("renew on party", 10.0f), NULL))); + + triggers.push_back(new TriggerNode( + "enemy too close for spell", + NextAction::array(0, new NextAction("fade", 50.0f), new NextAction("flee", 49.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.h b/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.h new file mode 100644 index 000000000..c4a5fd5f9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/HealPriestStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericPriestStrategy.h" + +namespace ai +{ + class HealPriestStrategy : public GenericPriestStrategy + { + public: + HealPriestStrategy(PlayerbotAI* ai) : GenericPriestStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + virtual string getName() { return "heal"; } + virtual int GetType() { return STRATEGY_TYPE_HEAL; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.cpp b/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.cpp new file mode 100644 index 000000000..cc69cbd85 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.cpp @@ -0,0 +1,46 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "HolyPriestStrategy.h" + +namespace ai +{ + class HolyPriestStrategyActionNodeFactory : public NamedObjectFactory + { + public: + HolyPriestStrategyActionNodeFactory() + { + creators["smite"] = &smite; + } + private: + static ActionNode* smite(PlayerbotAI* ai) + { + return new ActionNode ("smite", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } + }; +}; + +using namespace ai; + +HolyPriestStrategy::HolyPriestStrategy(PlayerbotAI* ai) : HealPriestStrategy(ai) +{ + actionNodeFactories.Add(new HolyPriestStrategyActionNodeFactory()); +} + +NextAction** HolyPriestStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("holy fire", 10.0f), new NextAction("smite", 10.0f), NULL); +} + +void HolyPriestStrategy::InitTriggers(std::list &triggers) +{ + HealPriestStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + +} diff --git a/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.h b/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.h new file mode 100644 index 000000000..c21bcf178 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/HolyPriestStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "HealPriestStrategy.h" + +namespace ai +{ + class HolyPriestStrategy : public HealPriestStrategy + { + public: + HolyPriestStrategy(PlayerbotAI* ai); + + public: + virtual NextAction** getDefaultActions(); + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "holy"; } + virtual int GetType() { return STRATEGY_TYPE_DPS|STRATEGY_TYPE_RANGED; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestActions.cpp b/src/modules/Bots/playerbot/strategy/priest/PriestActions.cpp new file mode 100644 index 000000000..bcf7ed643 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestActions.cpp @@ -0,0 +1,17 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestActions.h" + +using namespace ai; + + +NextAction** CastAbolishDiseaseAction::getAlternatives() +{ + return NextAction::merge(NextAction::array(0, new NextAction("cure disease"), NULL), CastSpellAction::getAlternatives()); +} + +NextAction** CastAbolishDiseaseOnPartyAction::getAlternatives() +{ + return NextAction::merge(NextAction::array(0, new NextAction("cure disease on party"), NULL), CastSpellAction::getAlternatives()); +} + diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestActions.h b/src/modules/Bots/playerbot/strategy/priest/PriestActions.h new file mode 100644 index 000000000..12afb6ac9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestActions.h @@ -0,0 +1,246 @@ +#pragma once + +#include "../actions/GenericActions.h" + +namespace ai +{ + class CastGreaterHealAction : public CastHealingSpellAction { + public: + CastGreaterHealAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "greater heal") {} + }; + + class CastGreaterHealOnPartyAction : public HealPartyMemberAction + { + public: + CastGreaterHealOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "greater heal") {} + + virtual string getName() { return "greater heal on party"; } + }; + + class CastLesserHealAction : public CastHealingSpellAction { + public: + CastLesserHealAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "lesser heal") {} + }; + + class CastLesserHealOnPartyAction : public HealPartyMemberAction + { + public: + CastLesserHealOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "lesser heal") {} + + virtual string getName() { return "lesser heal on party"; } + }; + + class CastFlashHealAction : public CastHealingSpellAction { + public: + CastFlashHealAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "flash heal") {} + }; + + class CastFlashHealOnPartyAction : public HealPartyMemberAction + { + public: + CastFlashHealOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "flash heal") {} + + virtual string getName() { return "flash heal on party"; } + }; + + class CastHealAction : public CastHealingSpellAction { + public: + CastHealAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "heal") {} + }; + + class CastHealOnPartyAction : public HealPartyMemberAction + { + public: + CastHealOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "heal") {} + + virtual string getName() { return "heal on party"; } + }; + + class CastRenewAction : public CastHealingSpellAction { + public: + CastRenewAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "renew") {} + }; + + class CastRenewOnPartyAction : public HealPartyMemberAction + { + public: + CastRenewOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "renew") {} + + virtual string getName() { return "renew on party"; } + }; + + class CastFadeAction : public CastBuffSpellAction { + public: + CastFadeAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "fade") {} + }; + + class CastShadowformAction : public CastBuffSpellAction { + public: + CastShadowformAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "shadowform") {} + }; + + class CastRemoveShadowformAction : public Action { + public: + CastRemoveShadowformAction(PlayerbotAI* ai) : Action(ai, "remove shadowform") {} + virtual bool isUseful() { return ai->HasAura("shadowform", AI_VALUE(Unit*, "self target")); } + virtual bool isPossible() { return true; } + virtual bool Execute(Event event) { + ai->RemoveAura("shadowform"); + return true; + } + }; + + class CastVampiricEmbraceAction : public CastBuffSpellAction { + public: + CastVampiricEmbraceAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "vampiric embrace") {} + }; + + class CastPowerWordShieldAction : public CastBuffSpellAction { + public: + CastPowerWordShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "power word: shield") {} + }; + + class CastPowerWordShieldOnPartyAction : public HealPartyMemberAction + { + public: + CastPowerWordShieldOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "power word: shield") {} + + virtual string getName() { return "power word: shield on party"; } + }; + + class CastPowerWordFortitudeAction : public CastBuffSpellAction { + public: + CastPowerWordFortitudeAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "power word: fortitude") {} + }; + + class CastDivineSpiritAction : public CastBuffSpellAction { + public: + CastDivineSpiritAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "divine spirit") {} + }; + + class CastInnerFireAction : public CastBuffSpellAction { + public: + CastInnerFireAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "inner fire") {} + }; + + BEGIN_SPELL_ACTION(CastHolyNovaAction, "holy nova") + virtual bool isUseful() { + return !ai->HasAura("shadowform", AI_VALUE(Unit*, "self target")); + } + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastHolyFireAction, "holy fire") + virtual bool isUseful() { + return !ai->HasAura("shadowform", AI_VALUE(Unit*, "self target")); + } + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastSmiteAction, "smite") + virtual bool isUseful() { + return !ai->HasAura("shadowform", AI_VALUE(Unit*, "self target")); + } + END_SPELL_ACTION() + + class CastPowerWordFortitudeOnPartyAction : public BuffOnPartyAction { + public: + CastPowerWordFortitudeOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "power word: fortitude") {} + }; + + class CastDivineSpiritOnPartyAction : public BuffOnPartyAction { + public: + CastDivineSpiritOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "divine spirit") {} + }; + + class CastPowerWordPainAction : public CastDebuffSpellAction + { + public: + CastPowerWordPainAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "shadow word: pain") {} + }; + + class CastPowerWordPainOnAttackerAction : public CastDebuffSpellOnAttackerAction + { + public: + CastPowerWordPainOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "shadow word: pain") {} + }; + + BEGIN_DEBUFF_ACTION(CastDevouringPlagueAction, "devouring plague") + END_SPELL_ACTION() + + BEGIN_DEBUFF_ACTION(CastVampiricTouchAction, "vampiric touch") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastMindBlastAction, "mind blast") + END_SPELL_ACTION() + + BEGIN_RANGED_SPELL_ACTION(CastMindFlayAction, "mind flay") + END_SPELL_ACTION() + + class CastCureDiseaseAction : public CastCureSpellAction { + public: + CastCureDiseaseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cure disease") {} + }; + + class CastCureDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastCureDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cure disease", DISPEL_DISEASE) {} + virtual string getName() { return "cure disease on party"; } + }; + + class CastAbolishDiseaseAction : public CastCureSpellAction { + public: + CastAbolishDiseaseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "abolish disease") {} + virtual NextAction** getAlternatives(); + }; + + class CastAbolishDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastAbolishDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "abolish disease", DISPEL_DISEASE) {} + virtual string getName() { return "abolish disease on party"; } + virtual NextAction** getAlternatives(); + }; + + class CastDispelMagicAction : public CastCureSpellAction { + public: + CastDispelMagicAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "dispel magic") {} + }; + + class CastDispelMagicOnTargetAction : public CastSpellAction { + public: + CastDispelMagicOnTargetAction(PlayerbotAI* ai) : CastSpellAction(ai, "dispel magic") {} + }; + + class CastDispelMagicOnPartyAction : public CurePartyMemberAction + { + public: + CastDispelMagicOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "dispel magic", DISPEL_MAGIC) {} + virtual string getName() { return "dispel magic on party"; } + }; + + class CastResurrectionAction : public ResurrectPartyMemberAction + { + public: + CastResurrectionAction(PlayerbotAI* ai) : ResurrectPartyMemberAction(ai, "resurrection") {} + }; + + class CastCircleOfHealingAction : public CastAoeHealSpellAction + { + public: + CastCircleOfHealingAction(PlayerbotAI* ai) : CastAoeHealSpellAction(ai, "circle of healing") {} + }; + + class CastPsychicScreamAction : public CastSpellAction + { + public: + CastPsychicScreamAction(PlayerbotAI* ai) : CastSpellAction(ai, "psychic scream") {} + }; + + class CastDispersionAction : public CastSpellAction + { + public: + CastDispersionAction(PlayerbotAI* ai) : CastSpellAction(ai, "dispersion") {} + virtual string GetTargetName() { return "self target"; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.cpp new file mode 100644 index 000000000..e890e60ad --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.cpp @@ -0,0 +1,219 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestActions.h" +#include "PriestAiObjectContext.h" +#include "PriestNonCombatStrategy.h" +#include "ShadowPriestStrategy.h" +#include "../generic/PullStrategy.h" +#include "PriestTriggers.h" +#include "../NamedObjectContext.h" +#include "HolyPriestStrategy.h" + +using namespace ai; + + +namespace ai +{ + namespace priest + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &priest::StrategyFactoryInternal::nc; + creators["pull"] = &priest::StrategyFactoryInternal::pull; + creators["aoe"] = &priest::StrategyFactoryInternal::shadow_aoe; + creators["shadow aoe"] = &priest::StrategyFactoryInternal::shadow_aoe; + creators["dps debuff"] = &priest::StrategyFactoryInternal::shadow_debuff; + creators["shadow debuff"] = &priest::StrategyFactoryInternal::shadow_debuff; + creators["cure"] = &priest::StrategyFactoryInternal::cure; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new PriestNonCombatStrategy(ai); } + static Strategy* shadow_aoe(PlayerbotAI* ai) { return new ShadowPriestAoeStrategy(ai); } + static Strategy* pull(PlayerbotAI* ai) { return new PullStrategy(ai, "shoot"); } + static Strategy* shadow_debuff(PlayerbotAI* ai) { return new ShadowPriestDebuffStrategy(ai); } + static Strategy* cure(PlayerbotAI* ai) { return new PriestCureStrategy(ai); } + }; + + class CombatStrategyFactoryInternal : public NamedObjectContext + { + public: + CombatStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["heal"] = &priest::CombatStrategyFactoryInternal::heal; + creators["shadow"] = &priest::CombatStrategyFactoryInternal::dps; + creators["dps"] = &priest::CombatStrategyFactoryInternal::dps; + creators["holy"] = &priest::CombatStrategyFactoryInternal::holy; + } + + private: + static Strategy* heal(PlayerbotAI* ai) { return new HealPriestStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new ShadowPriestStrategy(ai); } + static Strategy* holy(PlayerbotAI* ai) { return new HolyPriestStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace priest + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["devouring plague"] = &TriggerFactoryInternal::devouring_plague; + creators["shadow word: pain"] = &TriggerFactoryInternal::shadow_word_pain; + creators["shadow word: pain on attacker"] = &TriggerFactoryInternal::shadow_word_pain_on_attacker; + creators["dispel magic"] = &TriggerFactoryInternal::dispel_magic; + creators["dispel magic on party"] = &TriggerFactoryInternal::dispel_magic_party_member; + creators["cure disease"] = &TriggerFactoryInternal::cure_disease; + creators["party member cure disease"] = &TriggerFactoryInternal::party_member_cure_disease; + creators["power word: fortitude"] = &TriggerFactoryInternal::power_word_fortitude; + creators["power word: fortitude on party"] = &TriggerFactoryInternal::power_word_fortitude_on_party; + creators["divine spirit"] = &TriggerFactoryInternal::divine_spirit; + creators["divine spirit on party"] = &TriggerFactoryInternal::divine_spirit_on_party; + creators["inner fire"] = &TriggerFactoryInternal::inner_fire; + creators["vampiric touch"] = &TriggerFactoryInternal::vampiric_touch; + creators["shadowform"] = &TriggerFactoryInternal::shadowform; + creators["vampiric embrace"] = &TriggerFactoryInternal::vampiric_embrace; + + } + + private: + static Trigger* vampiric_embrace(PlayerbotAI* ai) { return new VampiricEmbraceTrigger(ai); } + static Trigger* shadowform(PlayerbotAI* ai) { return new ShadowformTrigger(ai); } + static Trigger* vampiric_touch(PlayerbotAI* ai) { return new VampiricTouchTrigger(ai); } + static Trigger* devouring_plague(PlayerbotAI* ai) { return new DevouringPlagueTrigger(ai); } + static Trigger* shadow_word_pain(PlayerbotAI* ai) { return new PowerWordPainTrigger(ai); } + static Trigger* shadow_word_pain_on_attacker(PlayerbotAI* ai) { return new PowerWordPainOnAttackerTrigger(ai); } + static Trigger* dispel_magic(PlayerbotAI* ai) { return new DispelMagicTrigger(ai); } + static Trigger* dispel_magic_party_member(PlayerbotAI* ai) { return new DispelMagicPartyMemberTrigger(ai); } + static Trigger* cure_disease(PlayerbotAI* ai) { return new CureDiseaseTrigger(ai); } + static Trigger* party_member_cure_disease(PlayerbotAI* ai) { return new PartyMemberCureDiseaseTrigger(ai); } + static Trigger* power_word_fortitude(PlayerbotAI* ai) { return new PowerWordFortitudeTrigger(ai); } + static Trigger* power_word_fortitude_on_party(PlayerbotAI* ai) { return new PowerWordFortitudeOnPartyTrigger(ai); } + static Trigger* divine_spirit(PlayerbotAI* ai) { return new DivineSpiritTrigger(ai); } + static Trigger* divine_spirit_on_party(PlayerbotAI* ai) { return new DivineSpiritOnPartyTrigger(ai); } + static Trigger* inner_fire(PlayerbotAI* ai) { return new InnerFireTrigger(ai); } + }; + }; +}; + + + +namespace ai +{ + namespace priest + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["shadow word: pain"] = &AiObjectContextInternal::shadow_word_pain; + creators["shadow word: pain on attacker"] = &AiObjectContextInternal::shadow_word_pain_on_attacker; + creators["devouring plague"] = &AiObjectContextInternal::devouring_plague; + creators["mind flay"] = &AiObjectContextInternal::mind_flay; + creators["holy fire"] = &AiObjectContextInternal::holy_fire; + creators["smite"] = &AiObjectContextInternal::smite; + creators["mind blast"] = &AiObjectContextInternal::mind_blast; + creators["shadowform"] = &AiObjectContextInternal::shadowform; + creators["remove shadowform"] = &AiObjectContextInternal::remove_shadowform; + creators["holy nova"] = &AiObjectContextInternal::holy_nova; + creators["power word: fortitude"] = &AiObjectContextInternal::power_word_fortitude; + creators["power word: fortitude on party"] = &AiObjectContextInternal::power_word_fortitude_on_party; + creators["divine spirit"] = &AiObjectContextInternal::divine_spirit; + creators["divine spirit on party"] = &AiObjectContextInternal::divine_spirit_on_party; + creators["power word: shield"] = &AiObjectContextInternal::power_word_shield; + creators["power word: shield on party"] = &AiObjectContextInternal::power_word_shield_on_party; + creators["renew"] = &AiObjectContextInternal::renew; + creators["renew on party"] = &AiObjectContextInternal::renew_on_party; + creators["greater heal"] = &AiObjectContextInternal::greater_heal; + creators["greater heal on party"] = &AiObjectContextInternal::greater_heal_on_party; + creators["heal"] = &AiObjectContextInternal::heal; + creators["heal on party"] = &AiObjectContextInternal::heal_on_party; + creators["lesser heal"] = &AiObjectContextInternal::lesser_heal; + creators["lesser heal on party"] = &AiObjectContextInternal::lesser_heal_on_party; + creators["flash heal"] = &AiObjectContextInternal::flash_heal; + creators["flash heal on party"] = &AiObjectContextInternal::flash_heal_on_party; + creators["dispel magic"] = &AiObjectContextInternal::dispel_magic; + creators["dispel magic on party"] = &AiObjectContextInternal::dispel_magic_on_party; + creators["dispel magic on target"] = &AiObjectContextInternal::dispel_magic_on_target; + creators["cure disease"] = &AiObjectContextInternal::cure_disease; + creators["cure disease on party"] = &AiObjectContextInternal::cure_disease_on_party; + creators["abolish disease"] = &AiObjectContextInternal::abolish_disease; + creators["abolish disease on party"] = &AiObjectContextInternal::abolish_disease_on_party; + creators["fade"] = &AiObjectContextInternal::fade; + creators["inner fire"] = &AiObjectContextInternal::inner_fire; + creators["resurrection"] = &AiObjectContextInternal::resurrection; + creators["circle of healing"] = &AiObjectContextInternal::circle_of_healing; + creators["psychic scream"] = &AiObjectContextInternal::psychic_scream; + creators["vampiric touch"] = &AiObjectContextInternal::vampiric_touch; + creators["vampiric embrace"] = &AiObjectContextInternal::vampiric_embrace; + creators["dispersion"] = &AiObjectContextInternal::dispersion; + } + + private: + static Action* dispersion(PlayerbotAI* ai) { return new CastDispersionAction(ai); } + static Action* vampiric_embrace(PlayerbotAI* ai) { return new CastVampiricEmbraceAction(ai); } + static Action* vampiric_touch(PlayerbotAI* ai) { return new CastVampiricTouchAction(ai); } + static Action* psychic_scream(PlayerbotAI* ai) { return new CastPsychicScreamAction(ai); } + static Action* circle_of_healing(PlayerbotAI* ai) { return new CastCircleOfHealingAction(ai); } + static Action* resurrection(PlayerbotAI* ai) { return new CastResurrectionAction(ai); } + static Action* shadow_word_pain(PlayerbotAI* ai) { return new CastPowerWordPainAction(ai); } + static Action* shadow_word_pain_on_attacker(PlayerbotAI* ai) { return new CastPowerWordPainOnAttackerAction(ai); } + static Action* devouring_plague(PlayerbotAI* ai) { return new CastDevouringPlagueAction(ai); } + static Action* mind_flay(PlayerbotAI* ai) { return new CastMindFlayAction(ai); } + static Action* holy_fire(PlayerbotAI* ai) { return new CastHolyFireAction(ai); } + static Action* smite(PlayerbotAI* ai) { return new CastSmiteAction(ai); } + static Action* mind_blast(PlayerbotAI* ai) { return new CastMindBlastAction(ai); } + static Action* shadowform(PlayerbotAI* ai) { return new CastShadowformAction(ai); } + static Action* remove_shadowform(PlayerbotAI* ai) { return new CastRemoveShadowformAction(ai); } + static Action* holy_nova(PlayerbotAI* ai) { return new CastHolyNovaAction(ai); } + static Action* power_word_fortitude(PlayerbotAI* ai) { return new CastPowerWordFortitudeAction(ai); } + static Action* power_word_fortitude_on_party(PlayerbotAI* ai) { return new CastPowerWordFortitudeOnPartyAction(ai); } + static Action* divine_spirit(PlayerbotAI* ai) { return new CastDivineSpiritAction(ai); } + static Action* divine_spirit_on_party(PlayerbotAI* ai) { return new CastDivineSpiritOnPartyAction(ai); } + static Action* power_word_shield(PlayerbotAI* ai) { return new CastPowerWordShieldAction(ai); } + static Action* power_word_shield_on_party(PlayerbotAI* ai) { return new CastPowerWordShieldOnPartyAction(ai); } + static Action* renew(PlayerbotAI* ai) { return new CastRenewAction(ai); } + static Action* renew_on_party(PlayerbotAI* ai) { return new CastRenewOnPartyAction(ai); } + static Action* greater_heal(PlayerbotAI* ai) { return new CastGreaterHealAction(ai); } + static Action* greater_heal_on_party(PlayerbotAI* ai) { return new CastGreaterHealOnPartyAction(ai); } + static Action* heal(PlayerbotAI* ai) { return new CastHealAction(ai); } + static Action* heal_on_party(PlayerbotAI* ai) { return new CastHealOnPartyAction(ai); } + static Action* lesser_heal(PlayerbotAI* ai) { return new CastLesserHealAction(ai); } + static Action* lesser_heal_on_party(PlayerbotAI* ai) { return new CastLesserHealOnPartyAction(ai); } + static Action* flash_heal(PlayerbotAI* ai) { return new CastFlashHealAction(ai); } + static Action* flash_heal_on_party(PlayerbotAI* ai) { return new CastFlashHealOnPartyAction(ai); } + static Action* dispel_magic(PlayerbotAI* ai) { return new CastDispelMagicAction(ai); } + static Action* dispel_magic_on_party(PlayerbotAI* ai) { return new CastDispelMagicOnPartyAction(ai); } + static Action* dispel_magic_on_target(PlayerbotAI* ai) { return new CastDispelMagicOnTargetAction(ai); } + static Action* cure_disease(PlayerbotAI* ai) { return new CastCureDiseaseAction(ai); } + static Action* cure_disease_on_party(PlayerbotAI* ai) { return new CastCureDiseaseOnPartyAction(ai); } + static Action* abolish_disease(PlayerbotAI* ai) { return new CastAbolishDiseaseAction(ai); } + static Action* abolish_disease_on_party(PlayerbotAI* ai) { return new CastAbolishDiseaseOnPartyAction(ai); } + static Action* fade(PlayerbotAI* ai) { return new CastFadeAction(ai); } + static Action* inner_fire(PlayerbotAI* ai) { return new CastInnerFireAction(ai); } + }; + }; +}; + +PriestAiObjectContext::PriestAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::priest::StrategyFactoryInternal()); + strategyContexts.Add(new ai::priest::CombatStrategyFactoryInternal()); + actionContexts.Add(new ai::priest::AiObjectContextInternal()); + triggerContexts.Add(new ai::priest::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.h b/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.h new file mode 100644 index 000000000..315c33533 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class PriestAiObjectContext : public AiObjectContext + { + public: + PriestAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.cpp b/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.cpp new file mode 100644 index 000000000..f1b1cea38 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "PriestActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.h b/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.cpp new file mode 100644 index 000000000..8c84972ef --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.cpp @@ -0,0 +1,82 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "PriestNonCombatStrategy.h" +#include "PriestNonCombatStrategyActionNodeFactory.h" + +using namespace ai; + +PriestNonCombatStrategy::PriestNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new PriestNonCombatStrategyActionNodeFactory()); +} + +void PriestNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "power word: fortitude", + NextAction::array(0, new NextAction("power word: fortitude", 12.0f), NULL))); + + triggers.push_back(new TriggerNode( + "power word: fortitude on party", + NextAction::array(0, new NextAction("power word: fortitude on party", 11.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "divine spirit", + NextAction::array(0, new NextAction("divine spirit", 14.0f), NULL))); + + triggers.push_back(new TriggerNode( + "divine spirit on party", + NextAction::array(0, new NextAction("divine spirit on party", 13.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "inner fire", + NextAction::array(0, new NextAction("inner fire", 10.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("power word: shield", 70.0f), new NextAction("greater heal", 70.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member critical health", + NextAction::array(0, new NextAction("power word: shield on party", 60.0f), new NextAction("greater heal on party", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("flash heal", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("flash heal on party", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe heal", + NextAction::array(0, new NextAction("circle of healing", 27.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member dead", + NextAction::array(0, new NextAction("resurrection", 30.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "dispel magic", + NextAction::array(0, new NextAction("dispel magic", 41.0f), NULL))); + + triggers.push_back(new TriggerNode( + "dispel magic on party", + NextAction::array(0, new NextAction("dispel magic on party", 40.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "cure disease", + NextAction::array(0, new NextAction("abolish disease", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure disease", + NextAction::array(0, new NextAction("abolish disease on party", 30.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.h new file mode 100644 index 000000000..da82e1ae2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategy.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class PriestNonCombatStrategy : public NonCombatStrategy + { + public: + PriestNonCombatStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "nc"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategyActionNodeFactory.h b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategyActionNodeFactory.h new file mode 100644 index 000000000..05edd68eb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestNonCombatStrategyActionNodeFactory.h @@ -0,0 +1,126 @@ +#pragma once + +namespace ai +{ + class PriestNonCombatStrategyActionNodeFactory : public NamedObjectFactory + { + public: + PriestNonCombatStrategyActionNodeFactory() + { + creators["holy nova"] = &holy_nova; + creators["power word: shield"] = &power_word_shield; + creators["power word: shield on party"] = &power_word_shield_on_party; + creators["renew"] = &renew; + creators["renew on party"] = &renew_on_party; + creators["greater heal"] = &greater_heal; + creators["greater heal on party"] = &greater_heal_on_party; + creators["heal"] = &heal; + creators["heal on party"] = &heal_on_party; + creators["lesser heal"] = &lesser_heal; + creators["lesser heal on party"] = &lesser_heal_on_party; + creators["flash heal"] = &flash_heal; + creators["flash heal on party"] = &flash_heal_on_party; + creators["circle of healing"] = &circle_of_healing; + } + private: + static ActionNode* holy_nova(PlayerbotAI* ai) + { + return new ActionNode ("holy nova", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* power_word_shield(PlayerbotAI* ai) + { + return new ActionNode ("power word: shield", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("renew", 50.0f), NULL), + /*C*/ NULL); + } + static ActionNode* power_word_shield_on_party(PlayerbotAI* ai) + { + return new ActionNode ("power word: shield on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("renew on party", 50.0f), NULL), + /*C*/ NULL); + } + static ActionNode* renew(PlayerbotAI* ai) + { + return new ActionNode ("renew", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* renew_on_party(PlayerbotAI* ai) + { + return new ActionNode ("renew on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* greater_heal(PlayerbotAI* ai) + { + return new ActionNode ("greater heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("heal"), NULL), + /*C*/ NULL); + } + static ActionNode* greater_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("greater heal on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("heal on party"), NULL), + /*C*/ NULL); + } + static ActionNode* heal(PlayerbotAI* ai) + { + return new ActionNode ("heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("lesser heal"), NULL), + /*C*/ NULL); + } + static ActionNode* heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("lesser heal on party"), NULL), + /*C*/ NULL); + } + static ActionNode* lesser_heal(PlayerbotAI* ai) + { + return new ActionNode ("lesser heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* lesser_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("lesser heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* flash_heal(PlayerbotAI* ai) + { + return new ActionNode ("flash heal", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* flash_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("flash heal on party", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* circle_of_healing(PlayerbotAI* ai) + { + return new ActionNode ("circle of healing", + /*P*/ NextAction::array(0, new NextAction("remove shadowform"), NULL), + /*A*/ NextAction::array(0, new NextAction("flash heal on party"), NULL), + /*C*/ NULL); + } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.cpp b/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.cpp new file mode 100644 index 000000000..93bf6a3a0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.cpp @@ -0,0 +1,7 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestTriggers.h" +#include "PriestActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.h b/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.h new file mode 100644 index 000000000..6739b7bdc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/PriestTriggers.h @@ -0,0 +1,70 @@ +#pragma once + +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + class PowerWordFortitudeOnPartyTrigger : public BuffOnPartyTrigger { + public: + PowerWordFortitudeOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "power word: fortitude", 7) {} + }; + + class PowerWordFortitudeTrigger : public BuffTrigger { + public: + PowerWordFortitudeTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "power word: fortitude", 5) {} + }; + + class DivineSpiritOnPartyTrigger : public BuffOnPartyTrigger { + public: + DivineSpiritOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "divine spirit", 7) {} + }; + + class DivineSpiritTrigger : public BuffTrigger { + public: + DivineSpiritTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "divine spirit", 5) {} + }; + + + BUFF_TRIGGER(InnerFireTrigger, "inner fire", "inner fire") + BUFF_TRIGGER(VampiricEmbraceTrigger, "vampiric embrace", "vampiric embrace") + + class PowerWordPainOnAttackerTrigger : public DebuffOnAttackerTrigger + { + public: + PowerWordPainOnAttackerTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "shadow word: pain") {} + }; + + DEBUFF_TRIGGER(PowerWordPainTrigger, "shadow word: pain", "shadow word: pain") + DEBUFF_TRIGGER(DevouringPlagueTrigger, "devouring plague", "devouring plague") + DEBUFF_TRIGGER(VampiricTouchTrigger, "vampiric touch", "vampiric touch") + + class DispelMagicTrigger : public NeedCureTrigger + { + public: + DispelMagicTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "dispel magic", DISPEL_MAGIC) {} + }; + + class DispelMagicPartyMemberTrigger : public PartyMemberNeedCureTrigger + { + public: + DispelMagicPartyMemberTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "dispel magic", DISPEL_MAGIC) {} + }; + + class CureDiseaseTrigger : public NeedCureTrigger + { + public: + CureDiseaseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cure disease", DISPEL_DISEASE) {} + }; + + class PartyMemberCureDiseaseTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCureDiseaseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cure disease", DISPEL_DISEASE) {} + }; + + class ShadowformTrigger : public BuffTrigger { + public: + ShadowformTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "shadowform") {} + virtual bool IsActive() { return !ai->HasAura("shadowform", bot); } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.cpp b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.cpp new file mode 100644 index 000000000..56ff6deb3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.cpp @@ -0,0 +1,60 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PriestMultipliers.h" +#include "ShadowPriestStrategy.h" +#include "ShadowPriestStrategyActionNodeFactory.h" + +using namespace ai; + +ShadowPriestStrategy::ShadowPriestStrategy(PlayerbotAI* ai) : GenericPriestStrategy(ai) +{ + actionNodeFactories.Add(new ShadowPriestStrategyActionNodeFactory()); +} + +NextAction** ShadowPriestStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("mind blast", 10.0f), NULL); +} + +void ShadowPriestStrategy::InitTriggers(std::list &triggers) +{ + GenericPriestStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "shadowform", + NextAction::array(0, new NextAction("shadowform", 15.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("dispersion", ACTION_EMERGENCY + 5), NULL))); + + triggers.push_back(new TriggerNode( + "vampiric embrace", + NextAction::array(0, new NextAction("vampiric embrace", 16.0f), NULL))); +} + +void ShadowPriestAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "shadow word: pain on attacker", + NextAction::array(0, new NextAction("shadow word: pain on attacker", 11.0f), NULL))); +} + +void ShadowPriestDebuffStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "devouring plague", + NextAction::array(0, new NextAction("devouring plague", 13.0f), NULL))); + + triggers.push_back(new TriggerNode( + "vampiric touch", + NextAction::array(0, new NextAction("vampiric touch", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "shadow word: pain", + NextAction::array(0, new NextAction("shadow word: pain", 12.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.h b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.h new file mode 100644 index 000000000..3097f4ae8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategy.h @@ -0,0 +1,38 @@ +#pragma once + +#include "HealPriestStrategy.h" + +namespace ai +{ + class ShadowPriestStrategy : public GenericPriestStrategy + { + public: + ShadowPriestStrategy(PlayerbotAI* ai); + + public: + virtual NextAction** getDefaultActions(); + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "shadow"; } + virtual int GetType() { return STRATEGY_TYPE_DPS|STRATEGY_TYPE_RANGED; } + }; + + class ShadowPriestAoeStrategy : public CombatStrategy + { + public: + ShadowPriestAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "shadow aoe"; } + }; + + class ShadowPriestDebuffStrategy : public CombatStrategy + { + public: + ShadowPriestDebuffStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "shadow debuff"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategyActionNodeFactory.h b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategyActionNodeFactory.h new file mode 100644 index 000000000..1b9c533b7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/priest/ShadowPriestStrategyActionNodeFactory.h @@ -0,0 +1,37 @@ +#pragma once + +namespace ai +{ + class ShadowPriestStrategyActionNodeFactory : public NamedObjectFactory + { + public: + ShadowPriestStrategyActionNodeFactory() + { + creators["mind flay"] = &mind_flay; + creators["mind blast"] = &mind_blast; + creators["dispersion"] = &dispersion; + } + private: + static ActionNode* mind_flay(PlayerbotAI* ai) + { + return new ActionNode ("mind flay", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } + static ActionNode* mind_blast(PlayerbotAI* ai) + { + return new ActionNode ("mind blast", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mind flay"), NULL), + /*C*/ NULL); + } + static ActionNode* dispersion(PlayerbotAI* ai) + { + return new ActionNode ("dispersion", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mana potion"), NULL), + /*C*/ NULL); + } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.cpp b/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.cpp new file mode 100644 index 000000000..68d619d8b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.cpp @@ -0,0 +1,110 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueMultipliers.h" +#include "DpsRogueStrategy.h" + +using namespace ai; + +class DpsRogueStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + DpsRogueStrategyActionNodeFactory() + { + creators["riposte"] = &riposte; + creators["mutilate"] = &mutilate; + creators["sinister strike"] = &sinister_strike; + creators["kick"] = &kick; + creators["kidney shot"] = &kidney_shot; + creators["rupture"] = &rupture; + creators["backstab"] = &backstab; + } +private: + static ActionNode* riposte(PlayerbotAI* ai) + { + return new ActionNode ("riposte", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mutilate"), NULL), + /*C*/ NULL); + } + static ActionNode* mutilate(PlayerbotAI* ai) + { + return new ActionNode ("mutilate", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("sinister strike"), NULL), + /*C*/ NULL); + } + static ActionNode* sinister_strike(PlayerbotAI* ai) + { + return new ActionNode ("sinister strike", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* kick(PlayerbotAI* ai) + { + return new ActionNode ("kick", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("kidney shot"), NULL), + /*C*/ NULL); + } + static ActionNode* kidney_shot(PlayerbotAI* ai) + { + return new ActionNode ("kidney shot", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* rupture(PlayerbotAI* ai) + { + return new ActionNode ("rupture", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("eviscerate"), NULL), + /*C*/ NULL); + } + static ActionNode* backstab(PlayerbotAI* ai) + { + return new ActionNode ("backstab", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mutilate"), NULL), + /*C*/ NULL); + } +}; + +DpsRogueStrategy::DpsRogueStrategy(PlayerbotAI* ai) : MeleeCombatStrategy(ai) +{ + actionNodeFactories.Add(new DpsRogueStrategyActionNodeFactory()); +} + +NextAction** DpsRogueStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("riposte", ACTION_NORMAL), NULL); +} + +void DpsRogueStrategy::InitTriggers(std::list &triggers) +{ + MeleeCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "combo points available", + NextAction::array(0, new NextAction("rupture", ACTION_HIGH + 2), NULL))); + + triggers.push_back(new TriggerNode( + "medium threat", + NextAction::array(0, new NextAction("vanish", ACTION_HIGH), NULL))); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("evasion", ACTION_EMERGENCY), new NextAction("feint", ACTION_EMERGENCY), NULL))); + + triggers.push_back(new TriggerNode( + "kick", + NextAction::array(0, new NextAction("kick", ACTION_INTERRUPT + 2), NULL))); + + triggers.push_back(new TriggerNode( + "kick on enemy healer", + NextAction::array(0, new NextAction("kick on enemy healer", ACTION_INTERRUPT + 1), NULL))); + + triggers.push_back(new TriggerNode( + "behind target", + NextAction::array(0, new NextAction("backstab", ACTION_NORMAL), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.h b/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.h new file mode 100644 index 000000000..77e22495a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/DpsRogueStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/MeleeCombatStrategy.h" + +namespace ai +{ + class DpsRogueStrategy : public MeleeCombatStrategy + { + public: + DpsRogueStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps"; } + virtual NextAction** getDefaultActions(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.cpp new file mode 100644 index 000000000..f128c4151 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.cpp @@ -0,0 +1,14 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueTriggers.h" +#include "RogueMultipliers.h" +#include "GenericRogueNonCombatStrategy.h" +#include "RogueActions.h" + +using namespace ai; + +void GenericRogueNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.h new file mode 100644 index 000000000..a595f17b1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/GenericRogueNonCombatStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericRogueNonCombatStrategy : public NonCombatStrategy + { + public: + GenericRogueNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueActions.cpp b/src/modules/Bots/playerbot/strategy/rogue/RogueActions.cpp new file mode 100644 index 000000000..600eb8f73 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueActions.cpp @@ -0,0 +1,5 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueActions.h" + +using namespace ai; diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueActions.h b/src/modules/Bots/playerbot/strategy/rogue/RogueActions.h new file mode 100644 index 000000000..9dfe59ae2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueActions.h @@ -0,0 +1,82 @@ +#pragma once + +#include "../actions/GenericActions.h" +#include "RogueComboActions.h" +#include "RogueOpeningActions.h" +#include "RogueFinishingActions.h" + +namespace ai +{ + class CastEvasionAction : public CastBuffSpellAction + { + public: + CastEvasionAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "evasion") {} + }; + + class CastSprintAction : public CastBuffSpellAction + { + public: + CastSprintAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "sprint") {} + }; + + class CastKickAction : public CastSpellAction + { + public: + CastKickAction(PlayerbotAI* ai) : CastSpellAction(ai, "kick") {} + }; + + class CastFeintAction : public CastBuffSpellAction + { + public: + CastFeintAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "feint") {} + }; + + class CastDismantleAction : public CastSpellAction + { + public: + CastDismantleAction(PlayerbotAI* ai) : CastSpellAction(ai, "dismantle") {} + }; + + class CastDistractAction : public CastSpellAction + { + public: + CastDistractAction(PlayerbotAI* ai) : CastSpellAction(ai, "distract") {} + }; + + class CastVanishAction : public CastBuffSpellAction + { + public: + CastVanishAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "vanish") {} + }; + + class CastBlindAction : public CastDebuffSpellAction + { + public: + CastBlindAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "blind") {} + }; + + + class CastBladeFlurryAction : public CastBuffSpellAction + { + public: + CastBladeFlurryAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "blade flurry") {} + }; + + class CastAdrenalineRushAction : public CastBuffSpellAction + { + public: + CastAdrenalineRushAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "adrenaline rush") {} + }; + + class CastKillingSpreeAction : public CastBuffSpellAction + { + public: + CastKillingSpreeAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "killing spree") {} + }; + + class CastKickOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastKickOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "kick") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.cpp new file mode 100644 index 000000000..6fdea8a4f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.cpp @@ -0,0 +1,119 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueActions.h" +#include "RogueTriggers.h" +#include "RogueAiObjectContext.h" +#include "DpsRogueStrategy.h" +#include "GenericRogueNonCombatStrategy.h" +#include "../generic/PullStrategy.h" +#include "../NamedObjectContext.h" + +using namespace ai; + + +namespace ai +{ + namespace rogue + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["dps"] = &rogue::StrategyFactoryInternal::dps; + creators["nc"] = &rogue::StrategyFactoryInternal::nc; + creators["pull"] = &rogue::StrategyFactoryInternal::pull; + } + + private: + static Strategy* dps(PlayerbotAI* ai) { return new DpsRogueStrategy(ai); } + static Strategy* nc(PlayerbotAI* ai) { return new GenericRogueNonCombatStrategy(ai); } + static Strategy* pull(PlayerbotAI* ai) { return new PullStrategy(ai, "shoot"); } + }; + }; +}; + +namespace ai +{ + namespace rogue + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["kick"] = &TriggerFactoryInternal::kick; + creators["rupture"] = &TriggerFactoryInternal::rupture; + creators["slice and dice"] = &TriggerFactoryInternal::slice_and_dice; + creators["expose armor"] = &TriggerFactoryInternal::expose_armor; + creators["kick on enemy healer"] = &TriggerFactoryInternal::kick_on_enemy_healer; + + } + + private: + static Trigger* kick(PlayerbotAI* ai) { return new KickInterruptSpellTrigger(ai); } + static Trigger* rupture(PlayerbotAI* ai) { return new RuptureTrigger(ai); } + static Trigger* slice_and_dice(PlayerbotAI* ai) { return new SliceAndDiceTrigger(ai); } + static Trigger* expose_armor(PlayerbotAI* ai) { return new ExposeArmorTrigger(ai); } + static Trigger* kick_on_enemy_healer(PlayerbotAI* ai) { return new KickInterruptEnemyHealerSpellTrigger(ai); } + }; + }; +}; + + +namespace ai +{ + namespace rogue + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["riposte"] = &AiObjectContextInternal::riposte; + creators["mutilate"] = &AiObjectContextInternal::mutilate; + creators["sinister strike"] = &AiObjectContextInternal::sinister_strike; + creators["kidney shot"] = &AiObjectContextInternal::kidney_shot; + creators["rupture"] = &AiObjectContextInternal::rupture; + creators["slice and dice"] = &AiObjectContextInternal::slice_and_dice; + creators["eviscerate"] = &AiObjectContextInternal::eviscerate; + creators["vanish"] = &AiObjectContextInternal::vanish; + creators["evasion"] = &AiObjectContextInternal::evasion; + creators["kick"] = &AiObjectContextInternal::kick; + creators["feint"] = &AiObjectContextInternal::feint; + creators["backstab"] = &AiObjectContextInternal::backstab; + creators["expose armor"] = &AiObjectContextInternal::expose_armor; + creators["kick on enemy healer"] = &AiObjectContextInternal::kick_on_enemy_healer; + } + + private: + static Action* riposte(PlayerbotAI* ai) { return new CastRiposteAction(ai); } + static Action* mutilate(PlayerbotAI* ai) { return new CastMutilateAction(ai); } + static Action* sinister_strike(PlayerbotAI* ai) { return new CastSinisterStrikeAction(ai); } + static Action* kidney_shot(PlayerbotAI* ai) { return new CastKidneyShotAction(ai); } + static Action* rupture(PlayerbotAI* ai) { return new CastRuptureAction(ai); } + static Action* slice_and_dice(PlayerbotAI* ai) { return new CastSliceAndDiceAction(ai); } + static Action* eviscerate(PlayerbotAI* ai) { return new CastEviscerateAction(ai); } + static Action* vanish(PlayerbotAI* ai) { return new CastVanishAction(ai); } + static Action* evasion(PlayerbotAI* ai) { return new CastEvasionAction(ai); } + static Action* kick(PlayerbotAI* ai) { return new CastKickAction(ai); } + static Action* feint(PlayerbotAI* ai) { return new CastFeintAction(ai); } + static Action* backstab(PlayerbotAI* ai) { return new CastBackstabAction(ai); } + static Action* expose_armor(PlayerbotAI* ai) { return new CastExposeArmorAction(ai); } + static Action* kick_on_enemy_healer(PlayerbotAI* ai) { return new CastKickOnEnemyHealerAction(ai); } + }; + }; +}; + +RogueAiObjectContext::RogueAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::rogue::StrategyFactoryInternal()); + actionContexts.Add(new ai::rogue::AiObjectContextInternal()); + triggerContexts.Add(new ai::rogue::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.h b/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.h new file mode 100644 index 000000000..ba346266c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class RogueAiObjectContext : public AiObjectContext + { + public: + RogueAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueComboActions.h b/src/modules/Bots/playerbot/strategy/rogue/RogueComboActions.h new file mode 100644 index 000000000..0568847e3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueComboActions.h @@ -0,0 +1,45 @@ +#pragma once + +namespace ai +{ + class CastComboAction : public CastMeleeSpellAction + { + public: + CastComboAction(PlayerbotAI* ai, string name) : CastMeleeSpellAction(ai, name) {} + + virtual bool isUseful() + { + return CastMeleeSpellAction::isUseful() && AI_VALUE2(uint8, "combo", "self target") < 5; + } + }; + + class CastSinisterStrikeAction : public CastComboAction + { + public: + CastSinisterStrikeAction(PlayerbotAI* ai) : CastComboAction(ai, "sinister strike") {} + }; + + class CastMutilateAction : public CastComboAction + { + public: + CastMutilateAction(PlayerbotAI* ai) : CastComboAction(ai, "mutilate") {} + }; + + class CastRiposteAction : public CastComboAction + { + public: + CastRiposteAction(PlayerbotAI* ai) : CastComboAction(ai, "riposte") {} + }; + + class CastGougeAction : public CastComboAction + { + public: + CastGougeAction(PlayerbotAI* ai) : CastComboAction(ai, "gouge") {} + }; + + class CastBackstabAction : public CastComboAction + { + public: + CastBackstabAction(PlayerbotAI* ai) : CastComboAction(ai, "backstab") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueFinishingActions.h b/src/modules/Bots/playerbot/strategy/rogue/RogueFinishingActions.h new file mode 100644 index 000000000..d25b14f07 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueFinishingActions.h @@ -0,0 +1,35 @@ +#pragma once + +namespace ai +{ + class CastEviscerateAction : public CastMeleeSpellAction + { + public: + CastEviscerateAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "eviscerate") {} + }; + + class CastSliceAndDiceAction : public CastMeleeSpellAction + { + public: + CastSliceAndDiceAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "slice and dice") {} + }; + + class CastExposeArmorAction : public CastMeleeSpellAction + { + public: + CastExposeArmorAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "expose armor") {} + }; + + class CastRuptureAction : public CastMeleeSpellAction + { + public: + CastRuptureAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "rupture") {} + }; + + class CastKidneyShotAction : public CastMeleeSpellAction + { + public: + CastKidneyShotAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "kidney shot") {} + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.cpp b/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.cpp new file mode 100644 index 000000000..98f6f687f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueMultipliers.h" +#include "RogueActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.h b/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueOpeningActions.h b/src/modules/Bots/playerbot/strategy/rogue/RogueOpeningActions.h new file mode 100644 index 000000000..684d6b84a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueOpeningActions.h @@ -0,0 +1,24 @@ +#pragma once + +namespace ai +{ + class CastSapAction : public CastMeleeSpellAction + { + public: + CastSapAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "sap") {} + }; + + class CastGarroteAction : public CastMeleeSpellAction + { + public: + CastGarroteAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "garrote") {} + }; + + + class CastCheapShotAction : public CastMeleeSpellAction + { + public: + CastCheapShotAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "cheap shot") {} + }; + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.cpp b/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.cpp new file mode 100644 index 000000000..4ac71cc74 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.cpp @@ -0,0 +1,7 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RogueTriggers.h" +#include "RogueActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.h b/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.h new file mode 100644 index 000000000..8d20e12b8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/rogue/RogueTriggers.h @@ -0,0 +1,36 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + + class KickInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + KickInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "kick") {} + }; + + class SliceAndDiceTrigger : public BuffTrigger + { + public: + SliceAndDiceTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "slice and dice") {} + }; + + class RuptureTrigger : public DebuffTrigger + { + public: + RuptureTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "rupture") {} + }; + + class ExposeArmorTrigger : public DebuffTrigger + { + public: + ExposeArmorTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "expose armor") {} + }; + + class KickInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger + { + public: + KickInterruptEnemyHealerSpellTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "kick") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.cpp new file mode 100644 index 000000000..fd6cf9c70 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.cpp @@ -0,0 +1,69 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "CasterShamanStrategy.h" + +using namespace ai; + +class CasterShamanStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CasterShamanStrategyActionNodeFactory() + { + creators["magma totem"] = &magma_totem; + } +private: + static ActionNode* magma_totem(PlayerbotAI* ai) + { + return new ActionNode ("magma totem", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NextAction::array(0, new NextAction("fire nova"), NULL)); + } +}; + +CasterShamanStrategy::CasterShamanStrategy(PlayerbotAI* ai) : GenericShamanStrategy(ai) +{ + actionNodeFactories.Add(new CasterShamanStrategyActionNodeFactory()); +} + +NextAction** CasterShamanStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("lightning bolt", 10.0f), NULL); +} + +void CasterShamanStrategy::InitTriggers(std::list &triggers) +{ + GenericShamanStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "shaman weapon", + NextAction::array(0, new NextAction("flametongue weapon", 23.0f), NULL))); + + triggers.push_back(new TriggerNode( + "searing totem", + NextAction::array(0, new NextAction("searing totem", 19.0f), NULL))); + + triggers.push_back(new TriggerNode( + "shock", + NextAction::array(0, new NextAction("earth shock", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "frost shock snare", + NextAction::array(0, new NextAction("frost shock", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("flametongue totem", ACTION_LIGHT_HEAL), NULL))); +} + +void CasterAoeShamanStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("chain lightning", 25.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.h new file mode 100644 index 000000000..0c3dd5678 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/CasterShamanStrategy.h @@ -0,0 +1,29 @@ +#pragma once + +#include "GenericShamanStrategy.h" +#include "MeleeShamanStrategy.h" + +namespace ai +{ + class CasterShamanStrategy : public GenericShamanStrategy + { + public: + CasterShamanStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + virtual string getName() { return "caster"; } + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } + }; + + class CasterAoeShamanStrategy : public CombatStrategy + { + public: + CasterAoeShamanStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "caster aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.cpp new file mode 100644 index 000000000..b0d78c674 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.cpp @@ -0,0 +1,205 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "HealShamanStrategy.h" + +using namespace ai; + +class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericShamanStrategyActionNodeFactory() + { + creators["flametongue weapon"] = &flametongue_weapon; + creators["frostbrand weapon"] = &frostbrand_weapon; + creators["windfury weapon"] = &windfury_weapon; + creators["lesser healing wave"] = &lesser_healing_wave; + creators["lesser healing wave on party"] = &lesser_healing_wave_on_party; + creators["chain heal"] = &chain_heal; + creators["riptide"] = &riptide; + creators["chain heal on party"] = &chain_heal_on_party; + creators["riptide on party"] = &riptide_on_party; + creators["earth shock"] = &earth_shock; + } +private: + static ActionNode* earth_shock(PlayerbotAI* ai) + { + return new ActionNode ("earth shock", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("flame shock"), NULL), + /*C*/ NULL); + } + static ActionNode* flametongue_weapon(PlayerbotAI* ai) + { + return new ActionNode ("flametongue weapon", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("frostbrand weapon"), NULL), + /*C*/ NULL); + } + static ActionNode* frostbrand_weapon(PlayerbotAI* ai) + { + return new ActionNode ("frostbrand weapon", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("rockbiter weapon"), NULL), + /*C*/ NULL); + } + static ActionNode* windfury_weapon(PlayerbotAI* ai) + { + return new ActionNode ("windfury weapon", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("rockbiter weapon"), NULL), + /*C*/ NULL); + } + static ActionNode* lesser_healing_wave(PlayerbotAI* ai) + { + return new ActionNode ("lesser healing wave", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("healing wave"), NULL), + /*C*/ NULL); + } + static ActionNode* lesser_healing_wave_on_party(PlayerbotAI* ai) + { + return new ActionNode ("lesser healing wave on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("healing wave on party"), NULL), + /*C*/ NULL); + } + static ActionNode* chain_heal(PlayerbotAI* ai) + { + return new ActionNode ("chain heal", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("lesser healing wave"), NULL), + /*C*/ NULL); + } + static ActionNode* riptide(PlayerbotAI* ai) + { + return new ActionNode ("riptide", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("healing wave"), NULL), + /*C*/ NULL); + } + static ActionNode* chain_heal_on_party(PlayerbotAI* ai) + { + return new ActionNode ("chain heal on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("lesser healing wave on party"), NULL), + /*C*/ NULL); + } + static ActionNode* riptide_on_party(PlayerbotAI* ai) + { + return new ActionNode ("riptide on party", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("healing wave on party"), NULL), + /*C*/ NULL); + } +}; + +GenericShamanStrategy::GenericShamanStrategy(PlayerbotAI* ai) : CombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericShamanStrategyActionNodeFactory()); +} + +void GenericShamanStrategy::InitTriggers(std::list &triggers) +{ + CombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "wind shear", + NextAction::array(0, new NextAction("wind shear", 23.0f), NULL))); + + triggers.push_back(new TriggerNode( + "wind shear on enemy healer", + NextAction::array(0, new NextAction("wind shear on enemy healer", 23.0f), NULL))); + + triggers.push_back(new TriggerNode( + "purge", + NextAction::array(0, new NextAction("purge", 10.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member medium health", + NextAction::array(0, new NextAction("lesser healing wave on party", 25.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member low health", + NextAction::array(0, new NextAction("riptide on party", 25.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe heal", + NextAction::array(0, new NextAction("chain heal", 27.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("lesser healing wave", 26.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low health", + NextAction::array(0, new NextAction("riptide", 26.0f), NULL))); + + triggers.push_back(new TriggerNode( + "heroism", + NextAction::array(0, new NextAction("heroism", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "bloodlust", + NextAction::array(0, new NextAction("bloodlust", 30.0f), NULL))); +} + +void ShamanBuffDpsStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "lightning shield", + NextAction::array(0, new NextAction("lightning shield", 22.0f), NULL))); +} + +void ShamanBuffManaStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "water shield", + NextAction::array(0, new NextAction("water shield", 22.0f), NULL))); +} + +void ShamanCureStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "cure poison", + NextAction::array(0, new NextAction("cure poison", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure poison", + NextAction::array(0, new NextAction("cure poison on party", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse spirit poison", + NextAction::array(0, new NextAction("cleanse spirit", 24.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cleanse spirit poison", + NextAction::array(0, new NextAction("cleanse spirit poison on party", 23.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "cure disease", + NextAction::array(0, new NextAction("cure disease", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure disease", + NextAction::array(0, new NextAction("cure disease on party", 30.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cleanse spirit disease", + NextAction::array(0, new NextAction("cleanse spirit", 24.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cleanse spirit disease", + NextAction::array(0, new NextAction("cleanse spirit disease on party", 23.0f), NULL))); + + + triggers.push_back(new TriggerNode( + "cleanse spirit curse", + NextAction::array(0, new NextAction("cleanse spirit", 24.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cleanse spirit curse", + NextAction::array(0, new NextAction("cleanse spirit curse on party", 23.0f), NULL))); + +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.h new file mode 100644 index 000000000..e88a9b291 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/GenericShamanStrategy.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class GenericShamanStrategy : public CombatStrategy + { + public: + GenericShamanStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + + }; + + class ShamanBuffDpsStrategy : public Strategy + { + public: + ShamanBuffDpsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bdps"; } + + }; + + class ShamanBuffManaStrategy : public Strategy + { + public: + ShamanBuffManaStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "bmana"; } + + }; + + class ShamanCureStrategy : public Strategy + { + public: + ShamanCureStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "cure"; } + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.cpp new file mode 100644 index 000000000..ec9c0174b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.cpp @@ -0,0 +1,58 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "HealShamanStrategy.h" + +using namespace ai; + +class HealShamanStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + HealShamanStrategyActionNodeFactory() + { + creators["earthliving weapon"] = &earthliving_weapon; + creators["mana tide totem"] = &mana_tide_totem; + } +private: + static ActionNode* earthliving_weapon(PlayerbotAI* ai) + { + return new ActionNode ("earthliving weapon", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("flametongue weapon"), NULL), + /*C*/ NULL); + } + static ActionNode* mana_tide_totem(PlayerbotAI* ai) + { + return new ActionNode ("mana tide totem", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mana potion"), NULL), + /*C*/ NULL); + } + +}; + +HealShamanStrategy::HealShamanStrategy(PlayerbotAI* ai) : GenericShamanStrategy(ai) +{ + actionNodeFactories.Add(new HealShamanStrategyActionNodeFactory()); +} + +void HealShamanStrategy::InitTriggers(std::list &triggers) +{ + GenericShamanStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of spell", + NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "shaman weapon", + NextAction::array(0, new NextAction("earthliving weapon", 22.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("mana tide totem", ACTION_EMERGENCY + 5), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("healing stream totem", ACTION_LIGHT_HEAL), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.h new file mode 100644 index 000000000..56b18b9fe --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/HealShamanStrategy.h @@ -0,0 +1,17 @@ +#pragma once + +#include "GenericShamanStrategy.h" + +namespace ai +{ + class HealShamanStrategy : public GenericShamanStrategy + { + public: + HealShamanStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "heal"; } + virtual int GetType() { return STRATEGY_TYPE_HEAL; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.cpp new file mode 100644 index 000000000..07a1bbc2e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.cpp @@ -0,0 +1,93 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "MeleeShamanStrategy.h" + +using namespace ai; + +class MeleeShamanStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + MeleeShamanStrategyActionNodeFactory() + { + creators["stormstrike"] = &stormstrike; + creators["lava lash"] = &lava_lash; + creators["magma totem"] = &magma_totem; + } +private: + static ActionNode* stormstrike(PlayerbotAI* ai) + { + return new ActionNode ("stormstrike", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("lava lash"), NULL), + /*C*/ NULL); + } + static ActionNode* lava_lash(PlayerbotAI* ai) + { + return new ActionNode ("lava lash", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* magma_totem(PlayerbotAI* ai) + { + return new ActionNode ("magma totem", + /*P*/ NULL, + /*A*/ NULL, + /*C*/ NextAction::array(0, new NextAction("fire nova"), NULL)); + } +}; + +MeleeShamanStrategy::MeleeShamanStrategy(PlayerbotAI* ai) : GenericShamanStrategy(ai) +{ + actionNodeFactories.Add(new MeleeShamanStrategyActionNodeFactory()); +} + +NextAction** MeleeShamanStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("stormstrike", 10.0f), NULL); +} + +void MeleeShamanStrategy::InitTriggers(std::list &triggers) +{ + GenericShamanStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "shaman weapon", + NextAction::array(0, new NextAction("windfury weapon", 22.0f), NULL))); + + triggers.push_back(new TriggerNode( + "searing totem", + NextAction::array(0, new NextAction("reach melee", 22.0f), new NextAction("searing totem", 22.0f), NULL))); + + triggers.push_back(new TriggerNode( + "shock", + NextAction::array(0, new NextAction("earth shock", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "not facing target", + NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL))); + + triggers.push_back(new TriggerNode( + "enemy too close for melee", + NextAction::array(0, new NextAction("move out of enemy contact", ACTION_NORMAL + 8), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("strength of earth totem", ACTION_LIGHT_HEAL), NULL))); +} + +void MeleeAoeShamanStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "enemy out of melee", + NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 8), NULL))); + + triggers.push_back(new TriggerNode( + "magma totem", + NextAction::array(0, new NextAction("magma totem", 26.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("fire nova", 25.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.h new file mode 100644 index 000000000..6c18fb601 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/MeleeShamanStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GenericShamanStrategy.h" + +namespace ai +{ + class MeleeShamanStrategy : public GenericShamanStrategy + { + public: + MeleeShamanStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + virtual string getName() { return "melee"; } + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; } + }; + + class MeleeAoeShamanStrategy : public CombatStrategy + { + public: + MeleeAoeShamanStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "melee aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.cpp b/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.cpp new file mode 100644 index 000000000..46573f085 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.h b/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.h new file mode 100644 index 000000000..be2543b1d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanActions.h @@ -0,0 +1,337 @@ +#pragma once + +#include "../actions/GenericActions.h" + +namespace ai +{ + class CastLesserHealingWaveAction : public CastHealingSpellAction { + public: + CastLesserHealingWaveAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "lesser healing wave") {} + }; + + class CastLesserHealingWaveOnPartyAction : public HealPartyMemberAction + { + public: + CastLesserHealingWaveOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "lesser healing wave") {} + }; + + + class CastHealingWaveAction : public CastHealingSpellAction { + public: + CastHealingWaveAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "healing wave") {} + }; + + class CastHealingWaveOnPartyAction : public HealPartyMemberAction + { + public: + CastHealingWaveOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "healing wave") {} + }; + + class CastChainHealAction : public CastAoeHealSpellAction { + public: + CastChainHealAction(PlayerbotAI* ai) : CastAoeHealSpellAction(ai, "chain heal") {} + }; + + class CastRiptideAction : public CastHealingSpellAction { + public: + CastRiptideAction(PlayerbotAI* ai) : CastHealingSpellAction(ai, "riptide") {} + }; + + class CastRiptideOnPartyAction : public HealPartyMemberAction + { + public: + CastRiptideOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "riptide") {} + }; + + + class CastEarthShieldAction : public CastBuffSpellAction { + public: + CastEarthShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "earth shield") {} + }; + + class CastEarthShieldOnPartyAction : public BuffOnPartyAction + { + public: + CastEarthShieldOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "earth shield") {} + }; + + class CastWaterShieldAction : public CastBuffSpellAction { + public: + CastWaterShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "water shield") {} + }; + + class CastLightningShieldAction : public CastBuffSpellAction { + public: + CastLightningShieldAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "lightning shield") {} + }; + + class CastEarthlivingWeaponAction : public CastEnchantItemAction { + public: + CastEarthlivingWeaponAction(PlayerbotAI* ai) : CastEnchantItemAction(ai, "earthliving weapon") {} + }; + + class CastRockbiterWeaponAction : public CastEnchantItemAction { + public: + CastRockbiterWeaponAction(PlayerbotAI* ai) : CastEnchantItemAction(ai, "rockbiter weapon") {} + }; + + class CastFlametongueWeaponAction : public CastEnchantItemAction { + public: + CastFlametongueWeaponAction(PlayerbotAI* ai) : CastEnchantItemAction(ai, "flametongue weapon") {} + }; + + class CastFrostbrandWeaponAction : public CastEnchantItemAction { + public: + CastFrostbrandWeaponAction(PlayerbotAI* ai) : CastEnchantItemAction(ai, "frostbrand weapon") {} + }; + + class CastWindfuryWeaponAction : public CastEnchantItemAction { + public: + CastWindfuryWeaponAction(PlayerbotAI* ai) : CastEnchantItemAction(ai, "windfury weapon") {} + }; + + class CastTotemAction : public CastBuffSpellAction + { + public: + CastTotemAction(PlayerbotAI* ai, string spell) : CastBuffSpellAction(ai, spell) {} + virtual bool isUseful() { return CastBuffSpellAction::isUseful() && !AI_VALUE2(bool, "has totem", name); } + }; + + class CastStoneskinTotemAction : public CastTotemAction + { + public: + CastStoneskinTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "stoneskin totem") {} + }; + + class CastEarthbindTotemAction : public CastTotemAction + { + public: + CastEarthbindTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "earthbind totem") {} + }; + + class CastStrengthOfEarthTotemAction : public CastTotemAction + { + public: + CastStrengthOfEarthTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "strength of earth totem") {} + }; + + class CastManaSpringTotemAction : public CastTotemAction + { + public: + CastManaSpringTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "mana spring totem") {} + virtual bool isUseful() { return CastTotemAction::isUseful() && !AI_VALUE2(bool, "has totem", "healing stream totem"); } + }; + + class CastManaTideTotemAction : public CastTotemAction + { + public: + CastManaTideTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "mana tide totem") {} + virtual string GetTargetName() { return "self target"; } + }; + + class CastHealingStreamTotemAction : public CastTotemAction + { + public: + CastHealingStreamTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "healing stream totem") {} + }; + + class CastCleansingTotemAction : public CastTotemAction + { + public: + CastCleansingTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "cleansing totem") {} + }; + + class CastFlametongueTotemAction : public CastTotemAction + { + public: + CastFlametongueTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "flametongue totem") {} + virtual bool isUseful() { return CastTotemAction::isUseful() && !AI_VALUE2(bool, "has totem", "magma totem"); } + }; + + class CastWindfuryTotemAction : public CastTotemAction + { + public: + CastWindfuryTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "windfury totem") {} + }; + + class CastGraceOfAirTotemAction : public CastTotemAction + { + public: + CastGraceOfAirTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "grace of air totem") {} + }; + + class CastSearingTotemAction : public CastTotemAction + { + public: + CastSearingTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "searing totem") {} + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return CastTotemAction::isUseful() && !AI_VALUE2(bool, "has totem", "flametongue totem"); } + }; + + class CastMagmaTotemAction : public CastMeleeSpellAction + { + public: + CastMagmaTotemAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "magma totem") {} + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return CastMeleeSpellAction::isUseful() && !AI_VALUE2(bool, "has totem", name); } + }; + + class CastFireNovaAction : public CastSpellAction { + public: + CastFireNovaAction(PlayerbotAI* ai) : CastSpellAction(ai, "fire nova") {} + }; + + class CastWindShearAction : public CastSpellAction { + public: + CastWindShearAction(PlayerbotAI* ai) : CastSpellAction(ai, "wind shear") {} + }; + + class CastAncestralSpiritAction : public ResurrectPartyMemberAction + { + public: + CastAncestralSpiritAction(PlayerbotAI* ai) : ResurrectPartyMemberAction(ai, "ancestral spirit") {} + }; + + + class CastPurgeAction : public CastSpellAction + { + public: + CastPurgeAction(PlayerbotAI* ai) : CastSpellAction(ai, "purge") {} + }; + + class CastStormstrikeAction : public CastMeleeSpellAction { + public: + CastStormstrikeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "stormstrike") {} + }; + + class CastLavaLashAction : public CastMeleeSpellAction { + public: + CastLavaLashAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "lava lash") {} + }; + + class CastWaterBreathingAction : public CastBuffSpellAction { + public: + CastWaterBreathingAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "water breathing") {} + }; + + class CastWaterWalkingAction : public CastBuffSpellAction { + public: + CastWaterWalkingAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "water walking") {} + }; + + class CastWaterBreathingOnPartyAction : public BuffOnPartyAction { + public: + CastWaterBreathingOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "water breathing") {} + }; + + class CastWaterWalkingOnPartyAction : public BuffOnPartyAction { + public: + CastWaterWalkingOnPartyAction(PlayerbotAI* ai) : BuffOnPartyAction(ai, "water walking") {} + }; + + + class CastCleanseSpiritAction : public CastCureSpellAction { + public: + CastCleanseSpiritAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cleanse spirit") {} + }; + + class CastCleanseSpiritPoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastCleanseSpiritPoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse spirit", DISPEL_POISON) {} + + virtual string getName() { return "cleanse spirit poison on party"; } + }; + class CastCleanseSpiritCurseOnPartyAction : public CurePartyMemberAction + { + public: + CastCleanseSpiritCurseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse spirit", DISPEL_CURSE) {} + + virtual string getName() { return "cleanse spirit curse on party"; } + }; + class CastCleanseSpiritDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastCleanseSpiritDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cleanse spirit", DISPEL_DISEASE) {} + + virtual string getName() { return "cleanse spirit disease on party"; } + }; + + class CastFlameShockAction : public CastDebuffSpellAction + { + public: + CastFlameShockAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "flame shock") {} + }; + + class CastEarthShockAction : public CastDebuffSpellAction + { + public: + CastEarthShockAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "earth shock") {} + }; + + class CastFrostShockAction : public CastSnareSpellAction + { + public: + CastFrostShockAction(PlayerbotAI* ai) : CastSnareSpellAction(ai, "frost shock") {} + }; + + class CastChainLightningAction : public CastSpellAction + { + public: + CastChainLightningAction(PlayerbotAI* ai) : CastSpellAction(ai, "chain lightning") {} + }; + + class CastLightningBoltAction : public CastSpellAction + { + public: + CastLightningBoltAction(PlayerbotAI* ai) : CastSpellAction(ai, "lightning bolt") {} + }; + + class CastThunderstormAction : public CastMeleeSpellAction + { + public: + CastThunderstormAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "thunderstorm") {} + }; + + class CastHeroismAction : public CastBuffSpellAction + { + public: + CastHeroismAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "heroism") {} + }; + + class CastBloodlustAction : public CastBuffSpellAction + { + public: + CastBloodlustAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "bloodlust") {} + }; + + class CastWindShearOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastWindShearOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "wind shear") {} + }; + + class CastCurePoisonAction : public CastCureSpellAction + { + public: + CastCurePoisonAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cure poison") {} + }; + + class CastCurePoisonOnPartyAction : public CurePartyMemberAction + { + public: + CastCurePoisonOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cure poison", DISPEL_POISON) {} + }; + + class CastCureDiseaseAction : public CastCureSpellAction { + public: + CastCureDiseaseAction(PlayerbotAI* ai) : CastCureSpellAction(ai, "cure disease") {} + }; + + class CastCureDiseaseOnPartyAction : public CurePartyMemberAction + { + public: + CastCureDiseaseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "cure disease", DISPEL_DISEASE) {} + virtual string getName() { return "cure disease on party"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.cpp new file mode 100644 index 000000000..11cd2a320 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.cpp @@ -0,0 +1,290 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanActions.h" +#include "ShamanAiObjectContext.h" +#include "ShamanNonCombatStrategy.h" +#include "HealShamanStrategy.h" +#include "MeleeShamanStrategy.h" +#include "ShamanTriggers.h" +#include "../NamedObjectContext.h" +#include "TotemsShamanStrategy.h" +#include "CasterShamanStrategy.h" + +using namespace ai; + + + +namespace ai +{ + namespace shaman + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &shaman::StrategyFactoryInternal::nc; + creators["totems"] = &shaman::StrategyFactoryInternal::totems; + creators["melee aoe"] = &shaman::StrategyFactoryInternal::melee_aoe; + creators["caster aoe"] = &shaman::StrategyFactoryInternal::caster_aoe; + creators["cure"] = &shaman::StrategyFactoryInternal::cure; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new ShamanNonCombatStrategy(ai); } + static Strategy* totems(PlayerbotAI* ai) { return new TotemsShamanStrategy(ai); } + static Strategy* melee_aoe(PlayerbotAI* ai) { return new MeleeAoeShamanStrategy(ai); } + static Strategy* caster_aoe(PlayerbotAI* ai) { return new CasterAoeShamanStrategy(ai); } + static Strategy* cure(PlayerbotAI* ai) { return new ShamanCureStrategy(ai); } + }; + + class BuffStrategyFactoryInternal : public NamedObjectContext + { + public: + BuffStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["bmana"] = &shaman::BuffStrategyFactoryInternal::bmana; + creators["bdps"] = &shaman::BuffStrategyFactoryInternal::bdps; + } + + private: + static Strategy* bmana(PlayerbotAI* ai) { return new ShamanBuffManaStrategy(ai); } + static Strategy* bdps(PlayerbotAI* ai) { return new ShamanBuffDpsStrategy(ai); } + }; + + class CombatStrategyFactoryInternal : public NamedObjectContext + { + public: + CombatStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["heal"] = &shaman::CombatStrategyFactoryInternal::heal; + creators["melee"] = &shaman::CombatStrategyFactoryInternal::dps; + creators["dps"] = &shaman::CombatStrategyFactoryInternal::dps; + creators["caster"] = &shaman::CombatStrategyFactoryInternal::caster; + } + + private: + static Strategy* heal(PlayerbotAI* ai) { return new HealShamanStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new MeleeShamanStrategy(ai); } + static Strategy* caster(PlayerbotAI* ai) { return new CasterShamanStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace shaman + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["grace of air totem"] = &TriggerFactoryInternal::grace_of_air_totem; + creators["windfury totem"] = &TriggerFactoryInternal::windfury_totem; + creators["mana spring totem"] = &TriggerFactoryInternal::mana_spring_totem; + creators["flametongue totem"] = &TriggerFactoryInternal::flametongue_totem; + creators["strength of earth totem"] = &TriggerFactoryInternal::strength_of_earth_totem; + creators["magma totem"] = &TriggerFactoryInternal::magma_totem; + creators["searing totem"] = &TriggerFactoryInternal::searing_totem; + creators["wind shear"] = &TriggerFactoryInternal::wind_shear; + creators["purge"] = &TriggerFactoryInternal::purge; + creators["shaman weapon"] = &TriggerFactoryInternal::shaman_weapon; + creators["water shield"] = &TriggerFactoryInternal::water_shield; + creators["lightning shield"] = &TriggerFactoryInternal::lightning_shield; + creators["water breathing"] = &TriggerFactoryInternal::water_breathing; + creators["water walking"] = &TriggerFactoryInternal::water_walking; + creators["water breathing on party"] = &TriggerFactoryInternal::water_breathing_on_party; + creators["water walking on party"] = &TriggerFactoryInternal::water_walking_on_party; + creators["cleanse spirit poison"] = &TriggerFactoryInternal::cleanse_poison; + creators["cleanse spirit curse"] = &TriggerFactoryInternal::cleanse_curse; + creators["cleanse spirit disease"] = &TriggerFactoryInternal::cleanse_disease; + creators["party member cleanse spirit poison"] = &TriggerFactoryInternal::party_member_cleanse_poison; + creators["party member cleanse spirit curse"] = &TriggerFactoryInternal::party_member_cleanse_curse; + creators["party member cleanse spirit disease"] = &TriggerFactoryInternal::party_member_cleanse_disease; + creators["shock"] = &TriggerFactoryInternal::shock; + creators["frost shock snare"] = &TriggerFactoryInternal::frost_shock_snare; + creators["heroism"] = &TriggerFactoryInternal::heroism; + creators["bloodlust"] = &TriggerFactoryInternal::bloodlust; + creators["maelstrom weapon"] = &TriggerFactoryInternal::maelstrom_weapon; + creators["wind shear on enemy healer"] = &TriggerFactoryInternal::wind_shear_on_enemy_healer; + creators["cure poison"] = &TriggerFactoryInternal::cure_poison; + creators["party member cure poison"] = &TriggerFactoryInternal::party_member_cure_poison; + creators["cure disease"] = &TriggerFactoryInternal::cure_disease; + creators["party member cure disease"] = &TriggerFactoryInternal::party_member_cure_disease; + } + + private: + static Trigger* maelstrom_weapon(PlayerbotAI* ai) { return new MaelstromWeaponTrigger(ai); } + static Trigger* heroism(PlayerbotAI* ai) { return new HeroismTrigger(ai); } + static Trigger* bloodlust(PlayerbotAI* ai) { return new BloodlustTrigger(ai); } + static Trigger* party_member_cleanse_disease(PlayerbotAI* ai) { return new PartyMemberCleanseSpiritDiseaseTrigger(ai); } + static Trigger* party_member_cleanse_curse(PlayerbotAI* ai) { return new PartyMemberCleanseSpiritCurseTrigger(ai); } + static Trigger* party_member_cleanse_poison(PlayerbotAI* ai) { return new PartyMemberCleanseSpiritPoisonTrigger(ai); } + static Trigger* cleanse_disease(PlayerbotAI* ai) { return new CleanseSpiritDiseaseTrigger(ai); } + static Trigger* cleanse_curse(PlayerbotAI* ai) { return new CleanseSpiritCurseTrigger(ai); } + static Trigger* cleanse_poison(PlayerbotAI* ai) { return new CleanseSpiritPoisonTrigger(ai); } + static Trigger* water_breathing(PlayerbotAI* ai) { return new WaterBreathingTrigger(ai); } + static Trigger* water_walking(PlayerbotAI* ai) { return new WaterWalkingTrigger(ai); } + static Trigger* water_breathing_on_party(PlayerbotAI* ai) { return new WaterBreathingOnPartyTrigger(ai); } + static Trigger* water_walking_on_party(PlayerbotAI* ai) { return new WaterWalkingOnPartyTrigger(ai); } + static Trigger* windfury_totem(PlayerbotAI* ai) { return new WindfuryTotemTrigger(ai); } + static Trigger* grace_of_air_totem(PlayerbotAI* ai) { return new GraceOfAirTotemTrigger(ai); } + static Trigger* mana_spring_totem(PlayerbotAI* ai) { return new ManaSpringTotemTrigger(ai); } + static Trigger* flametongue_totem(PlayerbotAI* ai) { return new FlametongueTotemTrigger(ai); } + static Trigger* strength_of_earth_totem(PlayerbotAI* ai) { return new StrengthOfEarthTotemTrigger(ai); } + static Trigger* magma_totem(PlayerbotAI* ai) { return new MagmaTotemTrigger(ai); } + static Trigger* searing_totem(PlayerbotAI* ai) { return new SearingTotemTrigger(ai); } + static Trigger* wind_shear(PlayerbotAI* ai) { return new WindShearInterruptSpellTrigger(ai); } + static Trigger* purge(PlayerbotAI* ai) { return new PurgeTrigger(ai); } + static Trigger* shaman_weapon(PlayerbotAI* ai) { return new ShamanWeaponTrigger(ai); } + static Trigger* water_shield(PlayerbotAI* ai) { return new WaterShieldTrigger(ai); } + static Trigger* lightning_shield(PlayerbotAI* ai) { return new LightningShieldTrigger(ai); } + static Trigger* shock(PlayerbotAI* ai) { return new ShockTrigger(ai); } + static Trigger* frost_shock_snare(PlayerbotAI* ai) { return new FrostShockSnareTrigger(ai); } + static Trigger* wind_shear_on_enemy_healer(PlayerbotAI* ai) { return new WindShearInterruptEnemyHealerSpellTrigger(ai); } + static Trigger* cure_poison(PlayerbotAI* ai) { return new CurePoisonTrigger(ai); } + static Trigger* party_member_cure_poison(PlayerbotAI* ai) { return new PartyMemberCurePoisonTrigger(ai); } + static Trigger* cure_disease(PlayerbotAI* ai) { return new CureDiseaseTrigger(ai); } + static Trigger* party_member_cure_disease(PlayerbotAI* ai) { return new PartyMemberCureDiseaseTrigger(ai); } + }; + }; +}; + + +namespace ai +{ + namespace shaman + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["water shield"] = &AiObjectContextInternal::water_shield; + creators["lightning shield"] = &AiObjectContextInternal::lightning_shield; + creators["strength of earth totem"] = &AiObjectContextInternal::strength_of_earth_totem; + creators["flametongue totem"] = &AiObjectContextInternal::flametongue_totem; + creators["searing totem"] = &AiObjectContextInternal::searing_totem; + creators["magma totem"] = &AiObjectContextInternal::magma_totem; + creators["windfury totem"] = &AiObjectContextInternal::windfury_totem; + creators["grace of air totem"] = &AiObjectContextInternal::grace_of_air_totem; + creators["mana spring totem"] = &AiObjectContextInternal::mana_spring_totem; + creators["mana tide totem"] = &AiObjectContextInternal::mana_tide_totem; + creators["healing stream totem"] = &AiObjectContextInternal::healing_stream_totem; + creators["wind shear"] = &AiObjectContextInternal::wind_shear; + creators["wind shear on enemy healer"] = &AiObjectContextInternal::wind_shear_on_enemy_healer; + creators["rockbiter weapon"] = &AiObjectContextInternal::rockbiter_weapon; + creators["flametongue weapon"] = &AiObjectContextInternal::flametongue_weapon; + creators["frostbrand weapon"] = &AiObjectContextInternal::frostbrand_weapon; + creators["windfury weapon"] = &AiObjectContextInternal::windfury_weapon; + creators["earthliving weapon"] = &AiObjectContextInternal::earthliving_weapon; + creators["purge"] = &AiObjectContextInternal::purge; + creators["healing wave"] = &AiObjectContextInternal::healing_wave; + creators["lesser healing wave"] = &AiObjectContextInternal::lesser_healing_wave; + creators["healing wave on party"] = &AiObjectContextInternal::healing_wave_on_party; + creators["lesser healing wave on party"] = &AiObjectContextInternal::lesser_healing_wave_on_party; + creators["earth shield"] = &AiObjectContextInternal::earth_shield; + creators["earth shield on party"] = &AiObjectContextInternal::earth_shield_on_party; + creators["chain heal"] = &AiObjectContextInternal::chain_heal; + creators["riptide"] = &AiObjectContextInternal::riptide; + creators["riptide on party"] = &AiObjectContextInternal::riptide_on_party; + creators["stormstrike"] = &AiObjectContextInternal::stormstrike; + creators["lava lash"] = &AiObjectContextInternal::lava_lash; + creators["fire nova"] = &AiObjectContextInternal::fire_nova; + creators["ancestral spirit"] = &AiObjectContextInternal::ancestral_spirit; + creators["water walking"] = &AiObjectContextInternal::water_walking; + creators["water breathing"] = &AiObjectContextInternal::water_breathing; + creators["water walking on party"] = &AiObjectContextInternal::water_walking_on_party; + creators["water breathing on party"] = &AiObjectContextInternal::water_breathing_on_party; + creators["cleanse spirit"] = &AiObjectContextInternal::cleanse_spirit; + creators["cleanse spirit poison on party"] = &AiObjectContextInternal::cleanse_spirit_poison_on_party; + creators["cleanse spirit disease on party"] = &AiObjectContextInternal::cleanse_spirit_disease_on_party; + creators["cleanse spirit curse on party"] = &AiObjectContextInternal::cleanse_spirit_curse_on_party; + creators["flame shock"] = &AiObjectContextInternal::flame_shock; + creators["earth shock"] = &AiObjectContextInternal::earth_shock; + creators["frost shock"] = &AiObjectContextInternal::frost_shock; + creators["chain lightning"] = &AiObjectContextInternal::chain_lightning; + creators["lightning bolt"] = &AiObjectContextInternal::lightning_bolt; + creators["thunderstorm"] = &AiObjectContextInternal::thunderstorm; + creators["heroism"] = &AiObjectContextInternal::heroism; + creators["bloodlust"] = &AiObjectContextInternal::bloodlust; + creators["cure disease"] = &AiObjectContextInternal::cure_disease; + creators["cure disease on party"] = &AiObjectContextInternal::cure_disease_on_party; + creators["cure poison"] = &AiObjectContextInternal::cure_poison; + creators["cure poison on party"] = &AiObjectContextInternal::cure_poison_on_party; + } + + private: + static Action* heroism(PlayerbotAI* ai) { return new CastHeroismAction(ai); } + static Action* bloodlust(PlayerbotAI* ai) { return new CastBloodlustAction(ai); } + static Action* thunderstorm(PlayerbotAI* ai) { return new CastThunderstormAction(ai); } + static Action* lightning_bolt(PlayerbotAI* ai) { return new CastLightningBoltAction(ai); } + static Action* chain_lightning(PlayerbotAI* ai) { return new CastChainLightningAction(ai); } + static Action* frost_shock(PlayerbotAI* ai) { return new CastFrostShockAction(ai); } + static Action* earth_shock(PlayerbotAI* ai) { return new CastEarthShockAction(ai); } + static Action* flame_shock(PlayerbotAI* ai) { return new CastFlameShockAction(ai); } + static Action* cleanse_spirit_poison_on_party(PlayerbotAI* ai) { return new CastCleanseSpiritPoisonOnPartyAction(ai); } + static Action* cleanse_spirit_disease_on_party(PlayerbotAI* ai) { return new CastCleanseSpiritDiseaseOnPartyAction(ai); } + static Action* cleanse_spirit_curse_on_party(PlayerbotAI* ai) { return new CastCleanseSpiritCurseOnPartyAction(ai); } + static Action* cleanse_spirit(PlayerbotAI* ai) { return new CastCleanseSpiritAction(ai); } + static Action* water_walking(PlayerbotAI* ai) { return new CastWaterWalkingAction(ai); } + static Action* water_breathing(PlayerbotAI* ai) { return new CastWaterBreathingAction(ai); } + static Action* water_walking_on_party(PlayerbotAI* ai) { return new CastWaterWalkingOnPartyAction(ai); } + static Action* water_breathing_on_party(PlayerbotAI* ai) { return new CastWaterBreathingOnPartyAction(ai); } + static Action* water_shield(PlayerbotAI* ai) { return new CastWaterShieldAction(ai); } + static Action* lightning_shield(PlayerbotAI* ai) { return new CastLightningShieldAction(ai); } + static Action* strength_of_earth_totem(PlayerbotAI* ai) { return new CastStrengthOfEarthTotemAction(ai); } + static Action* flametongue_totem(PlayerbotAI* ai) { return new CastFlametongueTotemAction(ai); } + static Action* magma_totem(PlayerbotAI* ai) { return new CastMagmaTotemAction(ai); } + static Action* searing_totem(PlayerbotAI* ai) { return new CastSearingTotemAction(ai); } + static Action* fire_nova(PlayerbotAI* ai) { return new CastFireNovaAction(ai); } + static Action* windfury_totem(PlayerbotAI* ai) { return new CastWindfuryTotemAction(ai); } + static Action* grace_of_air_totem(PlayerbotAI* ai) { return new CastGraceOfAirTotemAction(ai); } + static Action* mana_spring_totem(PlayerbotAI* ai) { return new CastManaSpringTotemAction(ai); } + static Action* mana_tide_totem(PlayerbotAI* ai) { return new CastManaTideTotemAction(ai); } + static Action* healing_stream_totem(PlayerbotAI* ai) { return new CastHealingStreamTotemAction(ai); } + static Action* wind_shear(PlayerbotAI* ai) { return new CastWindShearAction(ai); } + static Action* rockbiter_weapon(PlayerbotAI* ai) { return new CastRockbiterWeaponAction(ai); } + static Action* flametongue_weapon(PlayerbotAI* ai) { return new CastFlametongueWeaponAction(ai); } + static Action* frostbrand_weapon(PlayerbotAI* ai) { return new CastFrostbrandWeaponAction(ai); } + static Action* windfury_weapon(PlayerbotAI* ai) { return new CastWindfuryWeaponAction(ai); } + static Action* earthliving_weapon(PlayerbotAI* ai) { return new CastEarthlivingWeaponAction(ai); } + static Action* purge(PlayerbotAI* ai) { return new CastPurgeAction(ai); } + static Action* healing_wave(PlayerbotAI* ai) { return new CastHealingWaveAction(ai); } + static Action* lesser_healing_wave(PlayerbotAI* ai) { return new CastLesserHealingWaveAction(ai); } + static Action* healing_wave_on_party(PlayerbotAI* ai) { return new CastHealingWaveOnPartyAction(ai); } + static Action* lesser_healing_wave_on_party(PlayerbotAI* ai) { return new CastLesserHealingWaveOnPartyAction(ai); } + static Action* earth_shield(PlayerbotAI* ai) { return new CastEarthShieldAction(ai); } + static Action* earth_shield_on_party(PlayerbotAI* ai) { return new CastEarthShieldOnPartyAction(ai); } + static Action* chain_heal(PlayerbotAI* ai) { return new CastChainHealAction(ai); } + static Action* riptide(PlayerbotAI* ai) { return new CastRiptideAction(ai); } + static Action* riptide_on_party(PlayerbotAI* ai) { return new CastRiptideOnPartyAction(ai); } + static Action* stormstrike(PlayerbotAI* ai) { return new CastStormstrikeAction(ai); } + static Action* lava_lash(PlayerbotAI* ai) { return new CastLavaLashAction(ai); } + static Action* ancestral_spirit(PlayerbotAI* ai) { return new CastAncestralSpiritAction(ai); } + static Action* wind_shear_on_enemy_healer(PlayerbotAI* ai) { return new CastWindShearOnEnemyHealerAction(ai); } + static Action* cure_poison(PlayerbotAI* ai) { return new CastCurePoisonAction(ai); } + static Action* cure_poison_on_party(PlayerbotAI* ai) { return new CastCurePoisonOnPartyAction(ai); } + static Action* cure_disease(PlayerbotAI* ai) { return new CastCureDiseaseAction(ai); } + static Action* cure_disease_on_party(PlayerbotAI* ai) { return new CastCureDiseaseOnPartyAction(ai); } + }; + }; +}; + + + +ShamanAiObjectContext::ShamanAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::shaman::StrategyFactoryInternal()); + strategyContexts.Add(new ai::shaman::CombatStrategyFactoryInternal()); + strategyContexts.Add(new ai::shaman::BuffStrategyFactoryInternal()); + actionContexts.Add(new ai::shaman::AiObjectContextInternal()); + triggerContexts.Add(new ai::shaman::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.h b/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.h new file mode 100644 index 000000000..718084a6a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class ShamanAiObjectContext : public AiObjectContext + { + public: + ShamanAiObjectContext(PlayerbotAI* ai); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.cpp b/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.cpp new file mode 100644 index 000000000..9f42a73b6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "ShamanActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.h b/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.cpp new file mode 100644 index 000000000..e204d8ecf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.cpp @@ -0,0 +1,65 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "ShamanNonCombatStrategy.h" + +using namespace ai; + +void ShamanNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "party member dead", + NextAction::array(0, new NextAction("ancestral spirit", 33.0f), NULL))); + + triggers.push_back(new TriggerNode( + "water breathing", + NextAction::array(0, new NextAction("water breathing", 12.0f), NULL))); + + triggers.push_back(new TriggerNode( + "water walking", + NextAction::array(0, new NextAction("water walking", 12.0f), NULL))); + + triggers.push_back(new TriggerNode( + "water breathing on party", + NextAction::array(0, new NextAction("water breathing on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "water walking on party", + NextAction::array(0, new NextAction("water walking on party", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("healing wave", 70.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member critical health", + NextAction::array(0, new NextAction("healing wave on party", 60.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe heal", + NextAction::array(0, new NextAction("chain heal", 27.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cure poison", + NextAction::array(0, new NextAction("cure poison", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure poison", + NextAction::array(0, new NextAction("cure poison on party", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "cure disease", + NextAction::array(0, new NextAction("cure disease", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member cure disease", + NextAction::array(0, new NextAction("cure disease on party", 30.0f), NULL))); +} + +void ShamanNonCombatStrategy::InitMultipliers(std::list &multipliers) +{ + NonCombatStrategy::InitMultipliers(multipliers); +} + diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.h new file mode 100644 index 000000000..a6859ee71 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanNonCombatStrategy.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class ShamanNonCombatStrategy : public NonCombatStrategy + { + public: + ShamanNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual void InitMultipliers(std::list &multipliers); + virtual string getName() { return "nc"; } + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.cpp b/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.cpp new file mode 100644 index 000000000..4d719d395 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.cpp @@ -0,0 +1,42 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanTriggers.h" +#include "ShamanActions.h" + +using namespace ai; + +list ShamanWeaponTrigger::spells; + +bool ShamanWeaponTrigger::IsActive() +{ + if (spells.empty()) + { + spells.push_back("frostbrand weapon"); + spells.push_back("rockbiter weapon"); + spells.push_back("flametongue weapon"); + spells.push_back("earthliving weapon"); + spells.push_back("windfury weapon"); + } + + for (list::iterator i = spells.begin(); i != spells.end(); ++i) + { + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + if (!spellId) + { + continue; + } + + if (AI_VALUE2(Item*, "item for spell", spellId)) + { + return true; + } + } + + return false; +} + +bool ShockTrigger::IsActive() +{ + return SpellTrigger::IsActive() + && !ai->HasAnyAuraOf(GetTarget(), "frost shock", "earth shock", "flame shock", NULL); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.h b/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.h new file mode 100644 index 000000000..712dc6540 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/ShamanTriggers.h @@ -0,0 +1,227 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + class ShamanWeaponTrigger : public BuffTrigger { + public: + ShamanWeaponTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "rockbiter weapon") {} + virtual bool IsActive(); + private: + static list spells; + }; + + class TotemTrigger : public Trigger { + public: + TotemTrigger(PlayerbotAI* ai, string spell, int attackerCount = 0) : Trigger(ai, spell), attackerCount(attackerCount) {} + + virtual bool IsActive() + { + return AI_VALUE(uint8, "attacker count") >= attackerCount && !AI_VALUE2(bool, "has totem", name); + } + + protected: + int attackerCount; + }; + + class WindfuryTotemTrigger : public TotemTrigger { + public: + WindfuryTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "windfury totem") {} + }; + + class GraceOfAirTotemTrigger : public TotemTrigger + { + public: + GraceOfAirTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "grace of air totem") {} + }; + + class ManaSpringTotemTrigger : public TotemTrigger { + public: + ManaSpringTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "mana spring totem") {} + virtual bool IsActive() + { + return AI_VALUE(uint8, "attacker count") >= attackerCount && + !AI_VALUE2(bool, "has totem", "mana tide totem") && + !AI_VALUE2(bool, "has totem", name); + } + }; + + class FlametongueTotemTrigger : public TotemTrigger { + public: + FlametongueTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "flametongue totem") {} + }; + + class StrengthOfEarthTotemTrigger : public TotemTrigger { + public: + StrengthOfEarthTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "strength of earth totem") {} + }; + + class MagmaTotemTrigger : public TotemTrigger { + public: + MagmaTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "magma totem", 3) {} + }; + + class SearingTotemTrigger : public TotemTrigger { + public: + SearingTotemTrigger(PlayerbotAI* ai) : TotemTrigger(ai, "searing totem", 1) {} + }; + + class WindShearInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + WindShearInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "wind shear") {} + }; + + class WaterShieldTrigger : public BuffTrigger + { + public: + WaterShieldTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "water shield") {} + }; + + class LightningShieldTrigger : public BuffTrigger + { + public: + LightningShieldTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "lightning shield") {} + }; + + class PurgeTrigger : public TargetAuraDispelTrigger + { + public: + PurgeTrigger(PlayerbotAI* ai) : TargetAuraDispelTrigger(ai, "purge", DISPEL_MAGIC) {} + }; + + class WaterWalkingTrigger : public BuffTrigger { + public: + WaterWalkingTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "water walking", 7) {} + + virtual bool IsActive() + { + return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); + } + }; + + class WaterBreathingTrigger : public BuffTrigger { + public: + WaterBreathingTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "water breathing", 5) {} + + virtual bool IsActive() + { + return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); + } + }; + + class WaterWalkingOnPartyTrigger : public BuffOnPartyTrigger { + public: + WaterWalkingOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "water walking on party", 7) {} + + virtual bool IsActive() + { + return BuffOnPartyTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); + } + }; + + class WaterBreathingOnPartyTrigger : public BuffOnPartyTrigger { + public: + WaterBreathingOnPartyTrigger(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, "water breathing on party", 5) {} + + virtual bool IsActive() + { + return BuffOnPartyTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); + } + }; + + class CleanseSpiritPoisonTrigger : public NeedCureTrigger + { + public: + CleanseSpiritPoisonTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse spirit", DISPEL_POISON) {} + }; + + class PartyMemberCleanseSpiritPoisonTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCleanseSpiritPoisonTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse spirit", DISPEL_POISON) {} + }; + + class CleanseSpiritCurseTrigger : public NeedCureTrigger + { + public: + CleanseSpiritCurseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse spirit", DISPEL_CURSE) {} + }; + + class PartyMemberCleanseSpiritCurseTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCleanseSpiritCurseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse spirit", DISPEL_CURSE) {} + }; + + class CleanseSpiritDiseaseTrigger : public NeedCureTrigger + { + public: + CleanseSpiritDiseaseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cleanse spirit", DISPEL_DISEASE) {} + }; + + class PartyMemberCleanseSpiritDiseaseTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCleanseSpiritDiseaseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cleanse spirit", DISPEL_DISEASE) {} + }; + + class ShockTrigger : public DebuffTrigger { + public: + ShockTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "earth shock") {} + virtual bool IsActive(); + }; + + class FrostShockSnareTrigger : public SnareTargetTrigger { + public: + FrostShockSnareTrigger(PlayerbotAI* ai) : SnareTargetTrigger(ai, "frost shock") {} + }; + + class HeroismTrigger : public BoostTrigger + { + public: + HeroismTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "heroism") {} + }; + + class BloodlustTrigger : public BoostTrigger + { + public: + BloodlustTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "bloodlust") {} + }; + + class MaelstromWeaponTrigger : public HasAuraTrigger + { + public: + MaelstromWeaponTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "maelstrom weapon") {} + }; + + class WindShearInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger + { + public: + WindShearInterruptEnemyHealerSpellTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "wind shear") {} + }; + + class CurePoisonTrigger : public NeedCureTrigger + { + public: + CurePoisonTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cure poison", DISPEL_POISON) {} + }; + + class PartyMemberCurePoisonTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCurePoisonTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cure poison", DISPEL_POISON) {} + }; + + class CureDiseaseTrigger : public NeedCureTrigger + { + public: + CureDiseaseTrigger(PlayerbotAI* ai) : NeedCureTrigger(ai, "cure disease", DISPEL_DISEASE) {} + }; + + class PartyMemberCureDiseaseTrigger : public PartyMemberNeedCureTrigger + { + public: + PartyMemberCureDiseaseTrigger(PlayerbotAI* ai) : PartyMemberNeedCureTrigger(ai, "cure disease", DISPEL_DISEASE) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.cpp b/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.cpp new file mode 100644 index 000000000..cba749d55 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.cpp @@ -0,0 +1,31 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ShamanMultipliers.h" +#include "TotemsShamanStrategy.h" + +using namespace ai; + +TotemsShamanStrategy::TotemsShamanStrategy(PlayerbotAI* ai) : GenericShamanStrategy(ai) +{ +} + +void TotemsShamanStrategy::InitTriggers(std::list &triggers) +{ + GenericShamanStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "grace of air totem", + NextAction::array(0, new NextAction("grace of air totem", 16.0f), NULL))); + + triggers.push_back(new TriggerNode( + "mana spring totem", + NextAction::array(0, new NextAction("mana spring totem", 19.0f), NULL))); + + triggers.push_back(new TriggerNode( + "strength of earth totem", + NextAction::array(0, new NextAction("strength of earth totem", 18.0f), NULL))); + + triggers.push_back(new TriggerNode( + "flametongue totem", + NextAction::array(0, new NextAction("flametongue totem", 17.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.h b/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.h new file mode 100644 index 000000000..02fdeff75 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/shaman/TotemsShamanStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "GenericShamanStrategy.h" + +namespace ai +{ + class TotemsShamanStrategy : public GenericShamanStrategy + { + public: + TotemsShamanStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "totems"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/ChatCommandTrigger.h b/src/modules/Bots/playerbot/strategy/triggers/ChatCommandTrigger.h new file mode 100644 index 000000000..019a2f57d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/ChatCommandTrigger.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Trigger.h" + +namespace ai +{ + class ChatCommandTrigger : public Trigger { + public: + ChatCommandTrigger(PlayerbotAI* ai, string command) : Trigger(ai, command), triggered(false) {} + + virtual void ExternalEvent(string param, Player* owner = NULL) + { + this->param = param; + this->owner = owner; + triggered = true; + } + + virtual Event Check() + { + if (!triggered) + { + return Event(); + } + + return Event(getName(), param, owner); + } + + virtual void Reset() + { + triggered = false; + } + + private: + string param; + bool triggered; + Player* owner; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h b/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h new file mode 100644 index 000000000..6d55c0a23 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/ChatTriggerContext.h @@ -0,0 +1,158 @@ +#pragma once + +#include "ChatCommandTrigger.h" + +namespace ai +{ + class ChatTriggerContext : public NamedObjectContext + { + public: + ChatTriggerContext() + { + creators["quests"] = &ChatTriggerContext::quests; + creators["stats"] = &ChatTriggerContext::stats; + creators["leave"] = &ChatTriggerContext::leave; + creators["rep"] = &ChatTriggerContext::reputation; + creators["reputation"] = &ChatTriggerContext::reputation; + creators["log"] = &ChatTriggerContext::log; + creators["los"] = &ChatTriggerContext::los; + creators["drop"] = &ChatTriggerContext::drop; + creators["share"] = &ChatTriggerContext::share; + creators["q"] = &ChatTriggerContext::q; + creators["ll"] = &ChatTriggerContext::ll; + creators["ss"] = &ChatTriggerContext::ss; + creators["loot all"] = &ChatTriggerContext::loot_all; + creators["add all loot"] = &ChatTriggerContext::loot_all; + creators["release"] = &ChatTriggerContext::release; + creators["teleport"] = &ChatTriggerContext::teleport; + creators["taxi"] = &ChatTriggerContext::taxi; + creators["repair"] = &ChatTriggerContext::repair; + creators["u"] = &ChatTriggerContext::use; + creators["use"] = &ChatTriggerContext::use; + creators["c"] = &ChatTriggerContext::item_count; + creators["e"] = &ChatTriggerContext::equip; + creators["ue"] = &ChatTriggerContext::uneqip; + creators["s"] = &ChatTriggerContext::sell; + creators["b"] = &ChatTriggerContext::buy; + creators["r"] = &ChatTriggerContext::reward; + creators["t"] = &ChatTriggerContext::trade; + creators["nt"] = &ChatTriggerContext::nontrade; + creators["talents"] = &ChatTriggerContext::talents; + creators["spells"] = &ChatTriggerContext::spells; + creators["co"] = &ChatTriggerContext::co; + creators["nc"] = &ChatTriggerContext::nc; + creators["dead"] = &ChatTriggerContext::dead; + creators["trainer"] = &ChatTriggerContext::trainer; + creators["attack"] = &ChatTriggerContext::attack; + creators["chat"] = &ChatTriggerContext::chat; + creators["accept"] = &ChatTriggerContext::accept; + creators["home"] = &ChatTriggerContext::home; + creators["reset ai"] = &ChatTriggerContext::reset_ai; + creators["destroy"] = &ChatTriggerContext::destroy; + creators["emote"] = &ChatTriggerContext::emote; + creators["buff"] = &ChatTriggerContext::buff; + creators["help"] = &ChatTriggerContext::help; + creators["gb"] = &ChatTriggerContext::gb; + creators["bank"] = &ChatTriggerContext::bank; + creators["follow"] = &ChatTriggerContext::follow; + creators["stay"] = &ChatTriggerContext::stay; + creators["flee"] = &ChatTriggerContext::flee; + creators["grind"] = &ChatTriggerContext::grind; + creators["tank attack"] = &ChatTriggerContext::tank_attack; + creators["talk"] = &ChatTriggerContext::talk; + creators["cast"] = &ChatTriggerContext::cast; + creators["invite"] = &ChatTriggerContext::invite; + creators["spell"] = &ChatTriggerContext::spell; + creators["rti"] = &ChatTriggerContext::rti; + creators["revive"] = &ChatTriggerContext::revive; + creators["runaway"] = &ChatTriggerContext::runaway; + creators["warning"] = &ChatTriggerContext::warning; + creators["position"] = &ChatTriggerContext::position; + creators["summon"] = &ChatTriggerContext::summon; + creators["who"] = &ChatTriggerContext::who; + creators["save mana"] = &ChatTriggerContext::save_mana; + creators["max dps"] = &ChatTriggerContext::max_dps; + creators["attackers"] = &ChatTriggerContext::attackers; + creators["formation"] = &ChatTriggerContext::formation; + creators["sendmail"] = &ChatTriggerContext::sendmail; + creators["mail"] = &ChatTriggerContext::mail; + creators["outfit"] = &ChatTriggerContext::outfit; + creators["go"] = &ChatTriggerContext::go; + creators["ready"] = &ChatTriggerContext::ready_check; + creators["debug"] = &ChatTriggerContext::debug; + creators["cs"] = &ChatTriggerContext::cs; + } + + private: + static Trigger* cs(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "cs"); } + static Trigger* debug(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "debug"); } + static Trigger* go(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "go"); } + static Trigger* outfit(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "outfit"); } + static Trigger* mail(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "mail"); } + static Trigger* sendmail(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "sendmail"); } + static Trigger* formation(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "formation"); } + static Trigger* attackers(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "attackers"); } + static Trigger* max_dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "max dps"); } + static Trigger* save_mana(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "save mana"); } + static Trigger* who(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "who"); } + static Trigger* summon(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "summon"); } + static Trigger* position(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "position"); } + static Trigger* runaway(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "runaway"); } + static Trigger* warning(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "warning"); } + static Trigger* revive(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "revive"); } + static Trigger* rti(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "rti"); } + static Trigger* invite(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "invite"); } + static Trigger* cast(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "cast"); } + static Trigger* talk(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "talk"); } + static Trigger* flee(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "flee"); } + static Trigger* grind(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "grind"); } + static Trigger* tank_attack(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "tank attack"); } + static Trigger* stay(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "stay"); } + static Trigger* follow(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "follow"); } + static Trigger* gb(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "gb"); } + static Trigger* bank(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bank"); } + static Trigger* help(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "help"); } + static Trigger* buff(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "buff"); } + static Trigger* emote(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "emote"); } + static Trigger* destroy(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "destroy"); } + static Trigger* home(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "home"); } + static Trigger* accept(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "accept"); } + static Trigger* chat(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "chat"); } + static Trigger* attack(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "attack"); } + static Trigger* trainer(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "trainer"); } + static Trigger* co(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "co"); } + static Trigger* nc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "nc"); } + static Trigger* dead(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dead"); } + static Trigger* spells(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "spells"); } + static Trigger* talents(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "talents"); } + static Trigger* equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "e"); } + static Trigger* uneqip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "ue"); } + static Trigger* sell(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "s"); } + static Trigger* buy(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "b"); } + static Trigger* reward(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "r"); } + static Trigger* trade(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "t"); } + static Trigger* nontrade(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "nt"); } + + static Trigger* item_count(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "c"); } + static Trigger* use(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "use"); } + static Trigger* repair(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "repair"); } + static Trigger* taxi(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "taxi"); } + static Trigger* teleport(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "teleport"); } + static Trigger* q(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "q"); } + static Trigger* ll(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "ll"); } + static Trigger* ss(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "ss"); } + static Trigger* drop(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "drop"); } + static Trigger* share(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "share"); } + static Trigger* quests(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "quests"); } + static Trigger* stats(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "stats"); } + static Trigger* leave(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "leave"); } + static Trigger* reputation(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "reputation"); } + static Trigger* log(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "log"); } + static Trigger* los(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "los"); } + static Trigger* loot_all(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "add all loot"); } + static Trigger* release(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "release"); } + static Trigger* reset_ai(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "reset ai"); } + static Trigger* spell(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "spell"); } + static Trigger* ready_check(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "ready check"); } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.cpp b/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.cpp new file mode 100644 index 000000000..6560658f0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.cpp @@ -0,0 +1,17 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericTriggers.h" +#include "CureTriggers.h" + +using namespace ai; + +bool NeedCureTrigger::IsActive() +{ + Unit* target = GetTarget(); + return target && ai->HasAuraToDispel(target, dispelType); +} + +Value* PartyMemberNeedCureTrigger::GetTargetValue() +{ + return context->GetValue("party member to dispel", dispelType); +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.h new file mode 100644 index 000000000..f35a2b828 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/CureTriggers.h @@ -0,0 +1,35 @@ +#pragma once +#include "../Trigger.h" + +namespace ai +{ + class SpellTrigger; + + class NeedCureTrigger : public SpellTrigger { + public: + NeedCureTrigger(PlayerbotAI* ai, string spell, uint32 dispelType) : SpellTrigger(ai, spell, 5) + { + this->dispelType = dispelType; + } + virtual string GetTargetName() { return "self target"; } + virtual bool IsActive(); + + protected: + uint32 dispelType; + }; + + class TargetAuraDispelTrigger : public NeedCureTrigger { + public: + TargetAuraDispelTrigger(PlayerbotAI* ai, string spell, uint32 dispelType) : + NeedCureTrigger(ai, spell, dispelType) {} + virtual string GetTargetName() { return "current target"; } + }; + + class PartyMemberNeedCureTrigger : public NeedCureTrigger { + public: + PartyMemberNeedCureTrigger(PlayerbotAI* ai, string spell, uint32 dispelType) : + NeedCureTrigger(ai, spell, dispelType) {} + + virtual Value* GetTargetValue(); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp new file mode 100644 index 000000000..9276113b6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp @@ -0,0 +1,256 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericTriggers.h" +#include "../../LootObjectStack.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool LowManaTrigger::IsActive() +{ + return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana; +} + +bool MediumManaTrigger::IsActive() +{ + return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana; +} + + +bool RageAvailable::IsActive() +{ + return AI_VALUE2(uint8, "rage", "self target") >= amount; +} + +bool EnergyAvailable::IsActive() +{ + return AI_VALUE2(uint8, "energy", "self target") >= amount; +} + +bool ComboPointsAvailableTrigger::IsActive() +{ + return AI_VALUE2(uint8, "combo", "current target") >= amount; +} + +bool LoseAggroTrigger::IsActive() +{ + return !AI_VALUE2(bool, "has aggro", "current target"); +} + +bool HasAggroTrigger::IsActive() +{ + return AI_VALUE2(bool, "has aggro", "current target"); +} + +bool PanicTrigger::IsActive() +{ + return AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig.criticalHealth && + (!AI_VALUE2(bool, "has mana", "self target") || AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana); +} + +bool BuffTrigger::IsActive() +{ + Unit* target = GetTarget(); + return SpellTrigger::IsActive() && + !ai->HasAura(spell, target) && + (!AI_VALUE2(bool, "has mana", "self target") || AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.lowMana); +} + +Value* BuffOnPartyTrigger::GetTargetValue() +{ + return context->GetValue("party member without aura", spell); +} + +Value* DebuffOnAttackerTrigger::GetTargetValue() +{ + return context->GetValue("attacker without aura", spell); +} + +bool NoAttackersTrigger::IsActive() +{ + return !AI_VALUE(Unit*, "current target") && AI_VALUE(uint8, "attacker count") > 0; +} + +bool InvalidTargetTrigger::IsActive() +{ + return AI_VALUE2(bool, "invalid target", "current target"); +} + +bool NoTargetTrigger::IsActive() +{ + return !AI_VALUE(Unit*, "current target"); +} + +bool MyAttackerCountTrigger::IsActive() +{ + return AI_VALUE(uint8, "my attacker count") >= amount; +} + +bool AoeTrigger::IsActive() +{ + return AI_VALUE(uint8, "aoe count") >= amount && AI_VALUE(uint8, "attacker count") >= amount; +} + +bool DebuffTrigger::IsActive() +{ + return BuffTrigger::IsActive() && AI_VALUE2(uint8, "health", "current target") > 25; +} + +bool SpellTrigger::IsActive() +{ + return GetTarget(); +} + +bool SpellCanBeCastTrigger::IsActive() +{ + Unit* target = GetTarget(); + return target && ai->CanCastSpell(spell, target); +} + +bool RandomTrigger::IsActive() +{ + if (time(0) - lastCheck < sPlayerbotAIConfig.repeatDelay / 1000) + { + return false; + } + + lastCheck = time(0); + int k = (int)(probability / sPlayerbotAIConfig.randomChangeMultiplier); + if (k < 1) k = 1; + { + return (rand() % k) == 0; + } +} + +bool AndTrigger::IsActive() +{ + return ls->IsActive() && rs->IsActive(); +} + +string AndTrigger::getName() +{ + std::string name(ls->getName()); + name = name + " and "; + name = name + rs->getName(); + return name; +} + +bool BoostTrigger::IsActive() +{ + return BuffTrigger::IsActive() && AI_VALUE(uint8, "balance") <= balance; +} + +bool ItemCountTrigger::IsActive() +{ + return AI_VALUE2(uint8, "item count", item) < count; +} + +bool InterruptSpellTrigger::IsActive() +{ + return SpellTrigger::IsActive() && ai->IsInterruptableSpellCasting(GetTarget(), getName()); +} + +bool HasAuraTrigger::IsActive() +{ + return ai->HasAura(getName(), GetTarget()); +} + +bool TankAoeTrigger::IsActive() +{ + if (!AI_VALUE(uint8, "attacker count")) + { + return false; + } + + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (!currentTarget) + { + return true; + } + + Unit* tankTarget = AI_VALUE(Unit*, "tank target"); + if (!tankTarget || currentTarget == tankTarget) + { + return false; + } + + return currentTarget->getVictim() == AI_VALUE(Unit*, "self target"); +} + +bool IsBehindTargetTrigger::IsActive() +{ + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(bool, "behind", "current target"); +} + +bool IsNotFacingTargetTrigger::IsActive() +{ + return !AI_VALUE2(bool, "facing", "current target"); +} + +bool HasCcTargetTrigger::IsActive() +{ + return AI_VALUE(uint8, "attacker count") > 1 && AI_VALUE2(Unit*, "cc target", getName()) && + !AI_VALUE2(Unit*, "current cc target", getName()); +} + +bool NoMovementTrigger::IsActive() +{ + return !AI_VALUE2(bool, "moving", "self target"); +} + +bool NoPossibleTargetsTrigger::IsActive() +{ + list targets = AI_VALUE(list, "possible targets"); + return !targets.size(); +} + +bool NotDpsTargetActiveTrigger::IsActive() +{ + Unit* dps = AI_VALUE(Unit*, "dps target"); + Unit* target = AI_VALUE(Unit*, "current target"); + return dps && target != dps; +} + +bool EnemyPlayerIsAttacking::IsActive() +{ + Unit* enemyPlayer = AI_VALUE(Unit*, "enemy player target"); + Unit* target = AI_VALUE(Unit*, "current target"); + return enemyPlayer && target != enemyPlayer; +} + +bool IsSwimmingTrigger::IsActive() +{ + return AI_VALUE2(bool, "swimming", "self target"); +} + +bool HasNearestAddsTrigger::IsActive() +{ + list targets = AI_VALUE(list, "nearest adds"); + return targets.size(); +} + +bool HasItemForSpellTrigger::IsActive() +{ + string spell = getName(); + uint32 spellId = AI_VALUE2(uint32, "spell id", spell); + return spellId && AI_VALUE2(Item*, "item for spell", spellId); +} + + +bool TargetChangedTrigger::IsActive() +{ + Unit* oldTarget = context->GetValue("old target")->Get(); + Unit* target = context->GetValue("current target")->Get(); + return target && oldTarget != target; +} + +Value* InterruptEnemyHealerTrigger::GetTargetValue() +{ + return context->GetValue("enemy healer target", spell); +} + +Value* SnareTargetTrigger::GetTargetValue() +{ + return context->GetValue("snare target", spell); +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h new file mode 100644 index 000000000..ebf980946 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h @@ -0,0 +1,577 @@ +#pragma once +#include "../Trigger.h" +#include "../../PlayerbotAIConfig.h" + +#define BUFF_TRIGGER(clazz, spell, action) \ + class clazz : public BuffTrigger \ + { \ + public: \ + clazz(PlayerbotAI* ai) : BuffTrigger(ai, spell) {} \ + }; + +#define BUFF_ON_PARTY_TRIGGER(clazz, spell, action) \ + class clazz : public BuffOnPartyTrigger \ + { \ + public: \ + clazz(PlayerbotAI* ai) : BuffOnPartyTrigger(ai, spell) {} \ + }; + +#define DEBUFF_TRIGGER(clazz, spell, action) \ + class clazz : public DebuffTrigger \ + { \ + public: \ + clazz(PlayerbotAI* ai) : DebuffTrigger(ai, spell) {} \ + }; + +namespace ai +{ + class StatAvailable : public Trigger + { + public: + StatAvailable(PlayerbotAI* ai, int amount, string name = "stat available") : Trigger(ai, name) + { + this->amount = amount; + } + + protected: + int amount; + }; + + class RageAvailable : public StatAvailable + { + public: + RageAvailable(PlayerbotAI* ai, int amount) : StatAvailable(ai, amount, "rage available") {} + virtual bool IsActive(); + }; + + class LightRageAvailableTrigger : public RageAvailable + { + public: + LightRageAvailableTrigger(PlayerbotAI* ai) : RageAvailable(ai, 20) {} + }; + + class MediumRageAvailableTrigger : public RageAvailable + { + public: + MediumRageAvailableTrigger(PlayerbotAI* ai) : RageAvailable(ai, 40) {} + }; + + class HighRageAvailableTrigger : public RageAvailable + { + public: + HighRageAvailableTrigger(PlayerbotAI* ai) : RageAvailable(ai, 60) {} + }; + + class EnergyAvailable : public StatAvailable + { + public: + EnergyAvailable(PlayerbotAI* ai, int amount) : StatAvailable(ai, amount, "energy available") {} + virtual bool IsActive(); + }; + + class LightEnergyAvailableTrigger : public EnergyAvailable + { + public: + LightEnergyAvailableTrigger(PlayerbotAI* ai) : EnergyAvailable(ai, 20) {} + }; + + class MediumEnergyAvailableTrigger : public EnergyAvailable + { + public: + MediumEnergyAvailableTrigger(PlayerbotAI* ai) : EnergyAvailable(ai, 40) {} + }; + + class HighEnergyAvailableTrigger : public EnergyAvailable + { + public: + HighEnergyAvailableTrigger(PlayerbotAI* ai) : EnergyAvailable(ai, 60) {} + }; + + class ComboPointsAvailableTrigger : public StatAvailable + { + public: + ComboPointsAvailableTrigger(PlayerbotAI* ai, int amount = 5) : StatAvailable(ai, amount, "combo points available") {} + virtual bool IsActive(); + }; + + class LoseAggroTrigger : public Trigger { + public: + LoseAggroTrigger(PlayerbotAI* ai) : Trigger(ai, "lose aggro") {} + virtual bool IsActive(); + }; + + class HasAggroTrigger : public Trigger { + public: + HasAggroTrigger(PlayerbotAI* ai) : Trigger(ai, "have aggro") {} + virtual bool IsActive(); + }; + + class SpellTrigger : public Trigger + { + public: + SpellTrigger(PlayerbotAI* ai, string spell, int checkInterval = 1) : Trigger(ai, spell, checkInterval) + { + this->spell = spell; + } + + virtual string GetTargetName() { return "current target"; } + virtual string getName() { return spell; } + virtual bool IsActive(); + + protected: + string spell; + }; + + class SpellCanBeCastTrigger : public SpellTrigger + { + public: + SpellCanBeCastTrigger(PlayerbotAI* ai, string spell) : SpellTrigger(ai, spell) {} + virtual bool IsActive(); + }; + + // TODO: check other targets + class InterruptSpellTrigger : public SpellTrigger + { + public: + InterruptSpellTrigger(PlayerbotAI* ai, string spell) : SpellTrigger(ai, spell) {} + virtual bool IsActive(); + }; + + + class AttackerCountTrigger : public Trigger + { + public: + AttackerCountTrigger(PlayerbotAI* ai, int amount, float distance = sPlayerbotAIConfig.sightDistance) : Trigger(ai) + { + this->amount = amount; + this->distance = distance; + } + public: + virtual bool IsActive() + { + return AI_VALUE(uint8, "attacker count") >= amount; + } + virtual string getName() { return "attacker count"; } + + protected: + int amount; + float distance; + }; + + class HasAttackersTrigger : public AttackerCountTrigger + { + public: + HasAttackersTrigger(PlayerbotAI* ai) : AttackerCountTrigger(ai, 1) {} + }; + + class MyAttackerCountTrigger : public AttackerCountTrigger + { + public: + MyAttackerCountTrigger(PlayerbotAI* ai, int amount) : AttackerCountTrigger(ai, amount) {} + public: + virtual bool IsActive(); + virtual string getName() { return "my attacker count"; } + }; + + class MediumThreatTrigger : public MyAttackerCountTrigger + { + public: + MediumThreatTrigger(PlayerbotAI* ai) : MyAttackerCountTrigger(ai, 2) {} + }; + + class AoeTrigger : public AttackerCountTrigger + { + public: + AoeTrigger(PlayerbotAI* ai, int amount = 3, float range = 15.0f) : AttackerCountTrigger(ai, amount) + { + this->range = range; + } + public: + virtual bool IsActive(); + virtual string getName() { return "aoe"; } + + private: + float range; + }; + + class NoFoodTrigger : public Trigger { + public: + NoFoodTrigger(PlayerbotAI* ai) : Trigger(ai, "no food trigger") {} + virtual bool IsActive() { return AI_VALUE2(list, "inventory items", "food").empty(); } + }; + + class NoDrinkTrigger : public Trigger { + public: + NoDrinkTrigger(PlayerbotAI* ai) : Trigger(ai, "no drink trigger") {} + virtual bool IsActive() { return AI_VALUE2(list, "inventory items", "drink").empty(); } + }; + + class LightAoeTrigger : public AoeTrigger + { + public: + LightAoeTrigger(PlayerbotAI* ai) : AoeTrigger(ai, 2, 15.0f) {} + }; + + class MediumAoeTrigger : public AoeTrigger + { + public: + MediumAoeTrigger(PlayerbotAI* ai) : AoeTrigger(ai, 3, 17.0f) {} + }; + + class HighAoeTrigger : public AoeTrigger + { + public: + HighAoeTrigger(PlayerbotAI* ai) : AoeTrigger(ai, 4, 20.0f) {} + }; + + class BuffTrigger : public SpellTrigger + { + public: + BuffTrigger(PlayerbotAI* ai, string spell, int checkInterval = 1) : SpellTrigger(ai, spell, checkInterval) {} + public: + virtual string GetTargetName() { return "self target"; } + virtual bool IsActive(); + }; + + class BuffOnPartyTrigger : public BuffTrigger + { + public: + BuffOnPartyTrigger(PlayerbotAI* ai, string spell, int checkInterval = 1) : BuffTrigger(ai, spell, checkInterval) {} + public: + virtual Value* GetTargetValue(); + virtual string getName() { return spell + " on party"; } + }; + + BEGIN_TRIGGER(NoAttackersTrigger, Trigger) + END_TRIGGER() + + BEGIN_TRIGGER(NoTargetTrigger, Trigger) + END_TRIGGER() + + BEGIN_TRIGGER(InvalidTargetTrigger, Trigger) + END_TRIGGER() + + class TargetInSightTrigger : public Trigger { + public: + TargetInSightTrigger(PlayerbotAI* ai) : Trigger(ai, "target in sight") {} + virtual bool IsActive() { return AI_VALUE(Unit*, "grind target"); } + }; + + class DebuffTrigger : public BuffTrigger + { + public: + DebuffTrigger(PlayerbotAI* ai, string spell, int checkInterval = 1) : BuffTrigger(ai, spell, checkInterval) { + checkInterval = 1; + } + public: + virtual string GetTargetName() { return "current target"; } + virtual bool IsActive(); + }; + + class DebuffOnAttackerTrigger : public DebuffTrigger + { + public: + DebuffOnAttackerTrigger(PlayerbotAI* ai, string spell) : DebuffTrigger(ai, spell) {} + public: + virtual Value* GetTargetValue(); + virtual string getName() { return spell + " on attacker"; } + }; + + class BoostTrigger : public BuffTrigger + { + public: + BoostTrigger(PlayerbotAI* ai, string spell, float balance = 50) : BuffTrigger(ai, spell, 1) + { + this->balance = balance; + } + public: + virtual bool IsActive(); + + protected: + float balance; + }; + + class RandomTrigger : public Trigger + { + public: + RandomTrigger(PlayerbotAI* ai, string name, int probability = 7) : Trigger(ai, name) + { + this->probability = probability; + lastCheck = time(0); + } + public: + virtual bool IsActive(); + + protected: + int probability; + time_t lastCheck; + }; + + class AndTrigger : public Trigger + { + public: + AndTrigger(PlayerbotAI* ai, Trigger* ls, Trigger* rs) : Trigger(ai) + { + this->ls = ls; + this->rs = rs; + } + virtual ~AndTrigger() + { + delete ls; + delete rs; + } + public: + virtual bool IsActive(); + virtual string getName(); + + protected: + Trigger* ls; + Trigger* rs; + }; + + class SnareTargetTrigger : public DebuffTrigger + { + public: + SnareTargetTrigger(PlayerbotAI* ai, string spell) : DebuffTrigger(ai, spell) {} + public: + virtual Value* GetTargetValue(); + virtual string getName() { return spell + " on snare target"; } + }; + + class LowManaTrigger : public Trigger + { + public: + LowManaTrigger(PlayerbotAI* ai) : Trigger(ai, "low mana") {} + + virtual bool IsActive(); + }; + + class MediumManaTrigger : public Trigger + { + public: + MediumManaTrigger(PlayerbotAI* ai) : Trigger(ai, "medium mana") {} + + virtual bool IsActive(); + }; + + BEGIN_TRIGGER(PanicTrigger, Trigger) + virtual string getName() { return "panic"; } + END_TRIGGER() + + + class NoPetTrigger : public Trigger + { + public: + NoPetTrigger(PlayerbotAI* ai) : Trigger(ai, "no pet", 30) {} + + virtual bool IsActive() { + return !AI_VALUE(Unit*, "pet target") && !AI_VALUE2(bool, "mounted", "self target"); + } + }; + + class ItemCountTrigger : public Trigger { + public: + ItemCountTrigger(PlayerbotAI* ai, string item, int count) : Trigger(ai, item, 30) { + this->item = item; + this->count = count; + } + public: + virtual bool IsActive(); + virtual string getName() { return "item count"; } + + protected: + string item; + int count; + }; + + class HasAuraTrigger : public Trigger { + public: + HasAuraTrigger(PlayerbotAI* ai, string spell) : Trigger(ai, spell) {} + + virtual string GetTargetName() { return "self target"; } + virtual bool IsActive(); + + }; + + class TimerTrigger : public Trigger + { + public: + TimerTrigger(PlayerbotAI* ai) : Trigger(ai, "timer") + { + lastCheck = 0; + } + + public: + virtual bool IsActive() + { + if (time(0) != lastCheck) + { + lastCheck = time(0); + return true; + } + return false; + } + + private: + time_t lastCheck; + }; + + class TankAoeTrigger : public NoAttackersTrigger + { + public: + TankAoeTrigger(PlayerbotAI* ai) : NoAttackersTrigger(ai) {} + + public: + virtual bool IsActive(); + + }; + + class IsBehindTargetTrigger : public Trigger + { + public: + IsBehindTargetTrigger(PlayerbotAI* ai) : Trigger(ai) {} + + public: + virtual bool IsActive(); + }; + + class IsNotFacingTargetTrigger : public Trigger + { + public: + IsNotFacingTargetTrigger(PlayerbotAI* ai) : Trigger(ai) {} + + public: + virtual bool IsActive(); + }; + + class HasCcTargetTrigger : public Trigger + { + public: + HasCcTargetTrigger(PlayerbotAI* ai, string name) : Trigger(ai, name) {} + + public: + virtual bool IsActive(); + }; + + class NoMovementTrigger : public Trigger + { + public: + NoMovementTrigger(PlayerbotAI* ai, string name) : Trigger(ai, name) {} + + public: + virtual bool IsActive(); + }; + + + class NoPossibleTargetsTrigger : public Trigger + { + public: + NoPossibleTargetsTrigger(PlayerbotAI* ai) : Trigger(ai, "no possible targets") {} + + public: + virtual bool IsActive(); + }; + + class NotDpsTargetActiveTrigger : public Trigger + { + public: + NotDpsTargetActiveTrigger(PlayerbotAI* ai) : Trigger(ai, "not dps target active") {} + + public: + virtual bool IsActive(); + }; + + class EnemyPlayerIsAttacking : public Trigger + { + public: + EnemyPlayerIsAttacking(PlayerbotAI* ai) : Trigger(ai, "enemy player is attacking") {} + + public: + virtual bool IsActive(); + }; + + class IsSwimmingTrigger : public Trigger + { + public: + IsSwimmingTrigger(PlayerbotAI* ai) : Trigger(ai, "swimming") {} + + public: + virtual bool IsActive(); + }; + + class HasNearestAddsTrigger : public Trigger + { + public: + HasNearestAddsTrigger(PlayerbotAI* ai) : Trigger(ai, "has nearest adds") {} + + public: + virtual bool IsActive(); + }; + + class HasItemForSpellTrigger : public Trigger + { + public: + HasItemForSpellTrigger(PlayerbotAI* ai, string spell) : Trigger(ai, spell) {} + + public: + virtual bool IsActive(); + }; + + class TargetChangedTrigger : public Trigger + { + public: + TargetChangedTrigger(PlayerbotAI* ai) : Trigger(ai, "target changed") {} + + public: + virtual bool IsActive(); + }; + + class InterruptEnemyHealerTrigger : public SpellTrigger + { + public: + InterruptEnemyHealerTrigger(PlayerbotAI* ai, string spell) : SpellTrigger(ai, spell) {} + public: + virtual Value* GetTargetValue(); + virtual string getName() { return spell + " on enemy healer"; } + }; + + class RandomBotUpdateTrigger : public RandomTrigger + { + public: + RandomBotUpdateTrigger(PlayerbotAI* ai) : RandomTrigger(ai, "random bot update", 30) {} + + public: + virtual bool IsActive() + { + return RandomTrigger::IsActive() && AI_VALUE(bool, "random bot update"); + } + }; + + class NoNonBotPlayersAroundTrigger : public Trigger + { + public: + NoNonBotPlayersAroundTrigger(PlayerbotAI* ai) : Trigger(ai, "no non bot players around", 5) {} + + public: + virtual bool IsActive() + { + return AI_VALUE(list, "nearest non bot players").empty(); + } + }; + + class NewPlayerNearbyTrigger : public Trigger + { + public: + NewPlayerNearbyTrigger(PlayerbotAI* ai) : Trigger(ai, "new player nearby", 2) {} + + public: + virtual bool IsActive() + { + return AI_VALUE(ObjectGuid, "new player nearby"); + } + }; +} + +#include "RangeTriggers.h" +#include "HealthTriggers.h" +#include "CureTriggers.h" diff --git a/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.cpp b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.cpp new file mode 100644 index 000000000..407202386 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.cpp @@ -0,0 +1,26 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "HealthTriggers.h" + +using namespace ai; + +float HealthInRangeTrigger::GetValue() +{ + return AI_VALUE2(uint8, "health", GetTargetName()); +} + +bool PartyMemberDeadTrigger::IsActive() +{ + return GetTarget(); +} + +bool DeadTrigger::IsActive() +{ + return AI_VALUE2(bool, "dead", GetTargetName()); +} + +bool AoeHealTrigger::IsActive() +{ + return AI_VALUE2(uint8, "aoe heal", type) >= count; +} + diff --git a/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h new file mode 100644 index 000000000..b07ade21c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h @@ -0,0 +1,139 @@ +#pragma once +#include "../Trigger.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class ValueInRangeTrigger : public Trigger + { + public: + ValueInRangeTrigger(PlayerbotAI* ai, string name, float maxValue, float minValue) : Trigger(ai, name) { + this->maxValue = maxValue; + this->minValue = minValue; + } + public: + virtual float GetValue() = 0; + virtual bool IsActive() { + float value = GetValue(); + return value < maxValue && value >= minValue; + } + + protected: + float maxValue, minValue; + }; + + class HealthInRangeTrigger : public ValueInRangeTrigger + { + public: + HealthInRangeTrigger(PlayerbotAI* ai, string name, float maxValue, float minValue = 0) : + ValueInRangeTrigger(ai, name, maxValue, minValue) {} + + virtual bool IsActive() + { + return ValueInRangeTrigger::IsActive() && !AI_VALUE2(bool, "dead", GetTargetName()); + } + + virtual float GetValue(); + }; + + class LowHealthTrigger : public HealthInRangeTrigger + { + public: + LowHealthTrigger(PlayerbotAI* ai, string name = "low health", + float value = sPlayerbotAIConfig.lowHealth, float minValue = sPlayerbotAIConfig.criticalHealth) : + HealthInRangeTrigger(ai, name, value, minValue) {} + + virtual string GetTargetName() { return "self target"; } + }; + + class CriticalHealthTrigger : public LowHealthTrigger + { + public: + CriticalHealthTrigger(PlayerbotAI* ai) : + LowHealthTrigger(ai, "critical health", sPlayerbotAIConfig.criticalHealth, 0) {} + }; + + class MediumHealthTrigger : public LowHealthTrigger + { + public: + MediumHealthTrigger(PlayerbotAI* ai) : + LowHealthTrigger(ai, "medium health", sPlayerbotAIConfig.mediumHealth, sPlayerbotAIConfig.lowHealth) {} + }; + + class AlmostFullHealthTrigger : public LowHealthTrigger + { + public: + AlmostFullHealthTrigger(PlayerbotAI* ai) : + LowHealthTrigger(ai, "almost full health", sPlayerbotAIConfig.almostFullHealth, sPlayerbotAIConfig.mediumHealth) {} + }; + + class PartyMemberLowHealthTrigger : public HealthInRangeTrigger + { + public: + PartyMemberLowHealthTrigger(PlayerbotAI* ai, string name = "party member low health", float value = sPlayerbotAIConfig.lowHealth, float minValue = sPlayerbotAIConfig.criticalHealth) : + HealthInRangeTrigger(ai, name, value, minValue) {} + + virtual string GetTargetName() { return "party member to heal"; } + }; + + class PartyMemberCriticalHealthTrigger : public PartyMemberLowHealthTrigger + { + public: + PartyMemberCriticalHealthTrigger(PlayerbotAI* ai) : + PartyMemberLowHealthTrigger(ai, "party member critical health", sPlayerbotAIConfig.criticalHealth, 0) {} + }; + + class PartyMemberMediumHealthTrigger : public PartyMemberLowHealthTrigger + { + public: + PartyMemberMediumHealthTrigger(PlayerbotAI* ai) : + PartyMemberLowHealthTrigger(ai, "party member medium health", sPlayerbotAIConfig.mediumHealth,sPlayerbotAIConfig.lowHealth) {} + }; + + class PartyMemberAlmostFullHealthTrigger : public PartyMemberLowHealthTrigger + { + public: + PartyMemberAlmostFullHealthTrigger(PlayerbotAI* ai) : + PartyMemberLowHealthTrigger(ai, "party member almost full health", sPlayerbotAIConfig.almostFullHealth,sPlayerbotAIConfig.mediumHealth) {} + }; + + class TargetLowHealthTrigger : public HealthInRangeTrigger { + public: + TargetLowHealthTrigger(PlayerbotAI* ai, float value, float minValue = 0) : + HealthInRangeTrigger(ai, "target low health", value, minValue) {} + virtual string GetTargetName() { return "current target"; } + }; + + class TargetCriticalHealthTrigger : public TargetLowHealthTrigger + { + public: + TargetCriticalHealthTrigger(PlayerbotAI* ai) : TargetLowHealthTrigger(ai, 20) {} + }; + + class PartyMemberDeadTrigger : public Trigger { + public: + PartyMemberDeadTrigger(PlayerbotAI* ai) : Trigger(ai, "resurrect", 40) {} + virtual string GetTargetName() { return "party member to resurrect"; } + virtual bool IsActive(); + }; + + class DeadTrigger : public Trigger { + public: + DeadTrigger(PlayerbotAI* ai) : Trigger(ai, "dead", 45) {} + virtual string GetTargetName() { return "self target"; } + virtual bool IsActive(); + }; + + class AoeHealTrigger : public Trigger { + public: + AoeHealTrigger(PlayerbotAI* ai, string name, string type, int count) : + Trigger(ai, name), type(type), count(count) {} + public: + virtual bool IsActive(); + + protected: + int count; + string type; + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/LfgTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/LfgTriggers.h new file mode 100644 index 000000000..8bf888eeb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/LfgTriggers.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Trigger.h" + +namespace ai +{ +class LfgProposalActiveTrigger : public Trigger +{ +public: + LfgProposalActiveTrigger(PlayerbotAI* ai) : Trigger(ai, "lfg proposal active", 35) {} + + virtual bool IsActive() + { + return AI_VALUE(uint32, "lfg proposal"); + } +}; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.cpp b/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.cpp new file mode 100644 index 000000000..e5f9f6e75 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.cpp @@ -0,0 +1,21 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootTriggers.h" + +using namespace ai; + +bool LootAvailableTrigger::IsActive() +{ + return AI_VALUE(bool, "has available loot") && AI_VALUE(uint8, "bag space") < 80 && + (AI_VALUE2(float, "distance", "loot target") <= INTERACTION_DISTANCE || AI_VALUE(list, "possible targets").empty()); +} + +bool FarFromCurrentLootTrigger::IsActive() +{ + return AI_VALUE2(float, "distance", "loot target") > INTERACTION_DISTANCE; +} + +bool CanLootTrigger::IsActive() +{ + return AI_VALUE(bool, "can loot"); +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.h new file mode 100644 index 000000000..063456b88 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/LootTriggers.h @@ -0,0 +1,30 @@ +#pragma once +#include "../Trigger.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class LootAvailableTrigger : public Trigger + { + public: + LootAvailableTrigger(PlayerbotAI* ai) : Trigger(ai, "loot available") {} + + virtual bool IsActive(); + }; + + class FarFromCurrentLootTrigger : public Trigger + { + public: + FarFromCurrentLootTrigger(PlayerbotAI* ai) : Trigger(ai, "far from current loot") {} + + virtual bool IsActive(); + }; + + class CanLootTrigger : public Trigger + { + public: + CanLootTrigger(PlayerbotAI* ai) : Trigger(ai, "can loot") {} + + virtual bool IsActive(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/RangeTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/RangeTriggers.h new file mode 100644 index 000000000..9945a0ab7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/RangeTriggers.h @@ -0,0 +1,101 @@ +#pragma once +#include "../Trigger.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class EnemyTooCloseForSpellTrigger : public Trigger { + public: + EnemyTooCloseForSpellTrigger(PlayerbotAI* ai) : Trigger(ai, "enemy too close for spell", 2) {} + virtual bool IsActive() + { + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(float, "distance", "current target") <= sPlayerbotAIConfig.spellDistance / 2; + } + }; + + class EnemyTooCloseForShootTrigger : public Trigger { + public: + EnemyTooCloseForShootTrigger(PlayerbotAI* ai) : Trigger(ai, "enemy too close for shoot", 2) {} + virtual bool IsActive() + { + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(float, "distance", "current target") <= sPlayerbotAIConfig.shootDistance; + } + }; + + class EnemyTooCloseForMeleeTrigger : public Trigger { + public: + EnemyTooCloseForMeleeTrigger(PlayerbotAI* ai) : Trigger(ai, "enemy too close for melee", 2) {} + virtual bool IsActive() + { + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(float, "distance", "current target") <= sPlayerbotAIConfig.contactDistance / 2; + } + }; + + class EnemyIsCloseTrigger : public Trigger { + public: + EnemyIsCloseTrigger(PlayerbotAI* ai) : Trigger(ai, "enemy is close") {} + virtual bool IsActive() + { + Unit* target = AI_VALUE(Unit*, "current target"); + return target && AI_VALUE2(float, "distance", "current target") <= sPlayerbotAIConfig.tooCloseDistance; + } + }; + + class OutOfRangeTrigger : public Trigger { + public: + OutOfRangeTrigger(PlayerbotAI* ai, string name, float distance) : Trigger(ai, name) + { + this->distance = distance; + } + virtual bool IsActive() + { + Unit* target = AI_VALUE(Unit*, GetTargetName()); + return target && AI_VALUE2(float, "distance", GetTargetName()) > distance; + } + virtual string GetTargetName() { return "current target"; } + + protected: + float distance; + }; + + class EnemyOutOfMeleeTrigger : public OutOfRangeTrigger + { + public: + EnemyOutOfMeleeTrigger(PlayerbotAI* ai) : OutOfRangeTrigger(ai, "enemy out of melee range", sPlayerbotAIConfig.meleeDistance) {} + }; + + class EnemyOutOfSpellRangeTrigger : public OutOfRangeTrigger + { + public: + EnemyOutOfSpellRangeTrigger(PlayerbotAI* ai) : OutOfRangeTrigger(ai, "enemy out of spell range", sPlayerbotAIConfig.spellDistance) {} + }; + + class PartyMemberToHealOutOfSpellRangeTrigger : public OutOfRangeTrigger + { + public: + PartyMemberToHealOutOfSpellRangeTrigger(PlayerbotAI* ai) : OutOfRangeTrigger(ai, "party member to heal out of spell range", sPlayerbotAIConfig.spellDistance) {} + virtual string GetTargetName() { return "party member to heal"; } + }; + + class FarFromMasterTrigger : public Trigger { + public: + FarFromMasterTrigger(PlayerbotAI* ai, string name = "far from master", float distance = 12.0f, int checkInterval = 50) : Trigger(ai, name, checkInterval), distance(distance) {} + + virtual bool IsActive() + { + return AI_VALUE2(float, "distance", "master target") > distance; + } + + private: + float distance; + }; + + class OutOfReactRangeTrigger : public FarFromMasterTrigger + { + public: + OutOfReactRangeTrigger(PlayerbotAI* ai) : FarFromMasterTrigger(ai, "out of react range", sPlayerbotAIConfig.reactDistance / 2, 10) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h b/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h new file mode 100644 index 000000000..7609e804f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h @@ -0,0 +1,169 @@ +#pragma once + +#include "HealthTriggers.h" +#include "GenericTriggers.h" +#include "LootTriggers.h" +#include "../triggers/GenericTriggers.h" +#include "LfgTriggers.h" + +namespace ai +{ + + class TriggerContext : public NamedObjectContext + { + public: + TriggerContext() + { + creators["timer"] = &TriggerContext::Timer; + creators["random"] = &TriggerContext::Random; + creators["seldom"] = &TriggerContext::seldom; + creators["often"] = &TriggerContext::often; + + creators["target critical health"] = &TriggerContext::TargetCriticalHealth; + + creators["critical health"] = &TriggerContext::CriticalHealth; + creators["low health"] = &TriggerContext::LowHealth; + creators["medium health"] = &TriggerContext::MediumHealth; + creators["almost full health"] = &TriggerContext::AlmostFullHealth; + + creators["low mana"] = &TriggerContext::LowMana; + creators["medium mana"] = &TriggerContext::MediumMana; + + creators["party member critical health"] = &TriggerContext::PartyMemberCriticalHealth; + creators["party member low health"] = &TriggerContext::PartyMemberLowHealth; + creators["party member medium health"] = &TriggerContext::PartyMemberMediumHealth; + creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth; + + creators["light rage available"] = &TriggerContext::LightRageAvailable; + creators["medium rage available"] = &TriggerContext::MediumRageAvailable; + creators["high rage available"] = &TriggerContext::HighRageAvailable; + + creators["light energy available"] = &TriggerContext::LightEnergyAvailable; + creators["medium energy available"] = &TriggerContext::MediumEnergyAvailable; + creators["high energy available"] = &TriggerContext::HighEnergyAvailable; + + creators["loot available"] = &TriggerContext::LootAvailable; + creators["no attackers"] = &TriggerContext::NoAttackers; + creators["no target"] = &TriggerContext::NoTarget; + creators["target in sight"] = &TriggerContext::TargetInSight; + creators["not dps target active"] = &TriggerContext::not_dps_target_active; + creators["has nearest adds"] = &TriggerContext::has_nearest_adds; + creators["enemy player is attacking"] = &TriggerContext::enemy_player_is_attacking; + + creators["tank aoe"] = &TriggerContext::TankAoe; + creators["lose aggro"] = &TriggerContext::LoseAggro; + creators["has aggro"] = &TriggerContext::HasAggro; + + creators["light aoe"] = &TriggerContext::LightAoe; + creators["medium aoe"] = &TriggerContext::MediumAoe; + creators["high aoe"] = &TriggerContext::HighAoe; + + creators["enemy out of melee"] = &TriggerContext::EnemyOutOfMelee; + creators["enemy out of spell"] = &TriggerContext::EnemyOutOfSpell; + creators["enemy too close for spell"] = &TriggerContext::enemy_too_close_for_spell; + creators["enemy too close for shoot"] = &TriggerContext::enemy_too_close_for_shoot; + creators["enemy too close for melee"] = &TriggerContext::enemy_too_close_for_melee; + creators["enemy is close"] = &TriggerContext::enemy_is_close; + + creators["combo points available"] = &TriggerContext::ComboPointsAvailable; + + creators["medium threat"] = &TriggerContext::MediumThreat; + + creators["dead"] = &TriggerContext::Dead; + creators["party member dead"] = &TriggerContext::PartyMemberDead; + creators["no pet"] = &TriggerContext::no_pet; + creators["has attackers"] = &TriggerContext::has_attackers; + creators["no possible targets"] = &TriggerContext::no_possible_targets; + + creators["no drink"] = &TriggerContext::no_drink; + creators["no food"] = &TriggerContext::no_food; + + creators["panic"] = &TriggerContext::panic; + creators["behind target"] = &TriggerContext::behind_target; + creators["not facing target"] = &TriggerContext::not_facing_target; + creators["far from master"] = &TriggerContext::far_from_master; + creators["far from loot target"] = &TriggerContext::far_from_loot_target; + creators["can loot"] = &TriggerContext::can_loot; + creators["swimming"] = &TriggerContext::swimming; + creators["target changed"] = &TriggerContext::target_changed; + + creators["critical aoe heal"] = &TriggerContext::critical_aoe_heal; + creators["low aoe heal"] = &TriggerContext::low_aoe_heal; + creators["medium aoe heal"] = &TriggerContext::medium_aoe_heal; + creators["invalid target"] = &TriggerContext::invalid_target; + creators["lfg proposal active"] = &TriggerContext::lfg_proposal_active; + + creators["random bot update"] = &TriggerContext::random_bot_update_trigger; + creators["no non bot players around"] = &TriggerContext::no_non_bot_players_around; + creators["new player nearby"] = &TriggerContext::new_player_nearby; + } + + private: + static Trigger* lfg_proposal_active(PlayerbotAI* ai) { return new LfgProposalActiveTrigger(ai); } + static Trigger* invalid_target(PlayerbotAI* ai) { return new InvalidTargetTrigger(ai); } + static Trigger* critical_aoe_heal(PlayerbotAI* ai) { return new AoeHealTrigger(ai, "critical aoe heal", "critical", 2); } + static Trigger* low_aoe_heal(PlayerbotAI* ai) { return new AoeHealTrigger(ai, "low aoe heal", "low", 2); } + static Trigger* medium_aoe_heal(PlayerbotAI* ai) { return new AoeHealTrigger(ai, "medium aoe heal", "medium", 2); } + static Trigger* target_changed(PlayerbotAI* ai) { return new TargetChangedTrigger(ai); } + static Trigger* swimming(PlayerbotAI* ai) { return new IsSwimmingTrigger(ai); } + static Trigger* no_possible_targets(PlayerbotAI* ai) { return new NoPossibleTargetsTrigger(ai); } + static Trigger* can_loot(PlayerbotAI* ai) { return new CanLootTrigger(ai); } + static Trigger* far_from_loot_target(PlayerbotAI* ai) { return new FarFromCurrentLootTrigger(ai); } + static Trigger* far_from_master(PlayerbotAI* ai) { return new FarFromMasterTrigger(ai); } + static Trigger* behind_target(PlayerbotAI* ai) { return new IsBehindTargetTrigger(ai); } + static Trigger* not_facing_target(PlayerbotAI* ai) { return new IsNotFacingTargetTrigger(ai); } + static Trigger* panic(PlayerbotAI* ai) { return new PanicTrigger(ai); } + static Trigger* no_drink(PlayerbotAI* ai) { return new NoDrinkTrigger(ai); } + static Trigger* no_food(PlayerbotAI* ai) { return new NoFoodTrigger(ai); } + static Trigger* LightAoe(PlayerbotAI* ai) { return new LightAoeTrigger(ai); } + static Trigger* MediumAoe(PlayerbotAI* ai) { return new MediumAoeTrigger(ai); } + static Trigger* HighAoe(PlayerbotAI* ai) { return new HighAoeTrigger(ai); } + static Trigger* LoseAggro(PlayerbotAI* ai) { return new LoseAggroTrigger(ai); } + static Trigger* HasAggro(PlayerbotAI* ai) { return new HasAggroTrigger(ai); } + static Trigger* LowHealth(PlayerbotAI* ai) { return new LowHealthTrigger(ai); } + static Trigger* MediumHealth(PlayerbotAI* ai) { return new MediumHealthTrigger(ai); } + static Trigger* AlmostFullHealth(PlayerbotAI* ai) { return new AlmostFullHealthTrigger(ai); } + static Trigger* CriticalHealth(PlayerbotAI* ai) { return new CriticalHealthTrigger(ai); } + static Trigger* TargetCriticalHealth(PlayerbotAI* ai) { return new TargetCriticalHealthTrigger(ai); } + static Trigger* LowMana(PlayerbotAI* ai) { return new LowManaTrigger(ai); } + static Trigger* MediumMana(PlayerbotAI* ai) { return new MediumManaTrigger(ai); } + static Trigger* LightRageAvailable(PlayerbotAI* ai) { return new LightRageAvailableTrigger(ai); } + static Trigger* MediumRageAvailable(PlayerbotAI* ai) { return new MediumRageAvailableTrigger(ai); } + static Trigger* HighRageAvailable(PlayerbotAI* ai) { return new HighRageAvailableTrigger(ai); } + static Trigger* LightEnergyAvailable(PlayerbotAI* ai) { return new LightEnergyAvailableTrigger(ai); } + static Trigger* MediumEnergyAvailable(PlayerbotAI* ai) { return new MediumEnergyAvailableTrigger(ai); } + static Trigger* HighEnergyAvailable(PlayerbotAI* ai) { return new HighEnergyAvailableTrigger(ai); } + static Trigger* LootAvailable(PlayerbotAI* ai) { return new LootAvailableTrigger(ai); } + static Trigger* NoAttackers(PlayerbotAI* ai) { return new NoAttackersTrigger(ai); } + static Trigger* TankAoe(PlayerbotAI* ai) { return new TankAoeTrigger(ai); } + static Trigger* Timer(PlayerbotAI* ai) { return new TimerTrigger(ai); } + static Trigger* NoTarget(PlayerbotAI* ai) { return new NoTargetTrigger(ai); } + static Trigger* TargetInSight(PlayerbotAI* ai) { return new TargetInSightTrigger(ai); } + static Trigger* not_dps_target_active(PlayerbotAI* ai) { return new NotDpsTargetActiveTrigger(ai); } + static Trigger* has_nearest_adds(PlayerbotAI* ai) { return new HasNearestAddsTrigger(ai); } + static Trigger* enemy_player_is_attacking(PlayerbotAI* ai) { return new EnemyPlayerIsAttacking(ai); } + static Trigger* Random(PlayerbotAI* ai) { return new RandomTrigger(ai, "random", 15); } + static Trigger* seldom(PlayerbotAI* ai) { return new RandomTrigger(ai, "seldom", 100); } + static Trigger* often(PlayerbotAI* ai) { return new RandomTrigger(ai, "often", 5); } + static Trigger* EnemyOutOfMelee(PlayerbotAI* ai) { return new EnemyOutOfMeleeTrigger(ai); } + static Trigger* EnemyOutOfSpell(PlayerbotAI* ai) { return new EnemyOutOfSpellRangeTrigger(ai); } + static Trigger* enemy_too_close_for_spell(PlayerbotAI* ai) { return new EnemyTooCloseForSpellTrigger(ai); } + static Trigger* enemy_too_close_for_shoot(PlayerbotAI* ai) { return new EnemyTooCloseForShootTrigger(ai); } + static Trigger* enemy_too_close_for_melee(PlayerbotAI* ai) { return new EnemyTooCloseForMeleeTrigger(ai); } + static Trigger* enemy_is_close(PlayerbotAI* ai) { return new EnemyIsCloseTrigger(ai); } + static Trigger* ComboPointsAvailable(PlayerbotAI* ai) { return new ComboPointsAvailableTrigger(ai); } + static Trigger* MediumThreat(PlayerbotAI* ai) { return new MediumThreatTrigger(ai); } + static Trigger* Dead(PlayerbotAI* ai) { return new DeadTrigger(ai); } + static Trigger* PartyMemberDead(PlayerbotAI* ai) { return new PartyMemberDeadTrigger(ai); } + static Trigger* PartyMemberLowHealth(PlayerbotAI* ai) { return new PartyMemberLowHealthTrigger(ai); } + static Trigger* PartyMemberMediumHealth(PlayerbotAI* ai) { return new PartyMemberMediumHealthTrigger(ai); } + static Trigger* PartyMemberAlmostFullHealth(PlayerbotAI* ai) { return new PartyMemberAlmostFullHealthTrigger(ai); } + static Trigger* PartyMemberCriticalHealth(PlayerbotAI* ai) { return new PartyMemberCriticalHealthTrigger(ai); } + static Trigger* no_pet(PlayerbotAI* ai) { return new NoPetTrigger(ai); } + static Trigger* has_attackers(PlayerbotAI* ai) { return new HasAttackersTrigger(ai); } + static Trigger* random_bot_update_trigger(PlayerbotAI* ai) { return new RandomBotUpdateTrigger(ai); } + static Trigger* no_non_bot_players_around(PlayerbotAI* ai) { return new NoNonBotPlayersAroundTrigger(ai); } + static Trigger* new_player_nearby(PlayerbotAI* ai) { return new NewPlayerNearbyTrigger(ai); } + + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/triggers/WithinAreaTrigger.h b/src/modules/Bots/playerbot/strategy/triggers/WithinAreaTrigger.h new file mode 100644 index 000000000..f91ea492e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/WithinAreaTrigger.h @@ -0,0 +1,86 @@ +#pragma once +#include "../Trigger.h" +#include "../values/LastMovementValue.h" + +namespace ai +{ + class WithinAreaTrigger : public Trigger { + public: + WithinAreaTrigger(PlayerbotAI* ai) : Trigger(ai, "within area trigger") {} + + virtual bool IsActive() + { + + + LastMovement& movement = context->GetValue("last area trigger")->Get(); + if (!movement.lastAreaTrigger) + { + return false; + } + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(movement.lastAreaTrigger); + if(!atEntry) + { + return false; + } + + AreaTrigger const* at = sObjectMgr.GetAreaTrigger(movement.lastAreaTrigger); + if (!at) + { + return false; + } + + return IsPointInAreaTriggerZone(atEntry, bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0.5f); + } + + private: + bool IsPointInAreaTriggerZone(AreaTriggerEntry const* atEntry, uint32 mapid, float x, float y, float z, float delta) + { + if (mapid != atEntry->mapid) + { + return false; + } + + if (atEntry->radius > 0) + { + // if we have radius check it + float dist2 = (x - atEntry->x) * (x - atEntry->x) + (y - atEntry->y) * (y - atEntry->y) + (z - atEntry->z) * (z - atEntry->z); + if (dist2 > (atEntry->radius + delta) * (atEntry->radius + delta)) + { + return false; + } + } + else + { + // we have only extent + + // rotate the players position instead of rotating the whole cube, that way we can make a simplified + // is-in-cube check and we have to calculate only one point instead of 4 + + // 2PI = 360, keep in mind that ingame orientation is counter-clockwise + double rotation = 2 * M_PI - atEntry->box_orientation; + double sinVal = sin(rotation); + double cosVal = cos(rotation); + + float playerBoxDistX = x - atEntry->x; + float playerBoxDistY = y - atEntry->y; + + float rotPlayerX = float(atEntry->x + playerBoxDistX * cosVal - playerBoxDistY * sinVal); + float rotPlayerY = float(atEntry->y + playerBoxDistY * cosVal + playerBoxDistX * sinVal); + + // box edges are parallel to coordiante axis, so we can treat every dimension independently :D + float dz = z - atEntry->z; + float dx = rotPlayerX - atEntry->x; + float dy = rotPlayerY - atEntry->y; + if ((fabs(dx) > atEntry->box_x / 2 + delta) || + (fabs(dy) > atEntry->box_y / 2 + delta) || + (fabs(dz) > atEntry->box_z / 2 + delta)) + { + return false; + } + } + + return true; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTrigger.h b/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTrigger.h new file mode 100644 index 000000000..69a910448 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTrigger.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Trigger.h" + +namespace ai +{ + class WorldPacketTrigger : public Trigger { + public: + WorldPacketTrigger(PlayerbotAI* ai, string command) : Trigger(ai, command), triggered(false) {} + + virtual void ExternalEvent(WorldPacket &packet, Player* owner = NULL) + { + this->packet = packet; + this->owner = owner; + triggered = true; + } + + virtual Event Check() + { + if (!triggered) + { + return Event(); + } + + return Event(getName(), packet, owner); + } + + virtual void Reset() + { + triggered = false; + } + + private: + WorldPacket packet; + bool triggered; + Player* owner; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTriggerContext.h b/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTriggerContext.h new file mode 100644 index 000000000..5fe064d67 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/triggers/WorldPacketTriggerContext.h @@ -0,0 +1,87 @@ +#pragma once + +#include "WorldPacketTrigger.h" +#include "WithinAreaTrigger.h" + +namespace ai +{ + class WorldPacketTriggerContext : public NamedObjectContext + { + public: + WorldPacketTriggerContext() + { + creators["gossip hello"] = &WorldPacketTriggerContext::gossip_hello; + creators["group invite"] = &WorldPacketTriggerContext::group_invite; + creators["group set leader"] = &WorldPacketTriggerContext::group_set_leader; + creators["not enough money"] = &WorldPacketTriggerContext::no_money; + creators["not enough reputation"] = &WorldPacketTriggerContext::no_reputation; + creators["cannot equip"] = &WorldPacketTriggerContext::cannot_equip; + creators["use game object"] = &WorldPacketTriggerContext::use_game_object; + creators["complete quest"] = &WorldPacketTriggerContext::complete_quest; + creators["accept quest"] = &WorldPacketTriggerContext::accept_quest; + creators["quest share"] = &WorldPacketTriggerContext::quest_share; + creators["loot roll"] = &WorldPacketTriggerContext::loot_roll; + creators["resurrect request"] = &WorldPacketTriggerContext::resurrect_request; + creators["area trigger"] = &WorldPacketTriggerContext::area_trigger; + creators["within area trigger"] = &WorldPacketTriggerContext::within_area_trigger; + creators["check mount state"] = &WorldPacketTriggerContext::check_mount_state; + creators["activate taxi"] = &WorldPacketTriggerContext::taxi; + creators["trade status"] = &WorldPacketTriggerContext::trade_status; + creators["loot response"] = &WorldPacketTriggerContext::loot_response; + creators["out of react range"] = &WorldPacketTriggerContext::out_of_react_range; + creators["quest objective completed"] = &WorldPacketTriggerContext::quest_objective_completed; + creators["item push result"] = &WorldPacketTriggerContext::item_push_result; + creators["party command"] = &WorldPacketTriggerContext::party_command; + creators["taxi done"] = &WorldPacketTriggerContext::taxi_done; + creators["cast failed"] = &WorldPacketTriggerContext::cast_failed; + creators["duel requested"] = &WorldPacketTriggerContext::duel_requested; + creators["ready check"] = &WorldPacketTriggerContext::ready_check; + creators["ready check finished"] = &WorldPacketTriggerContext::ready_check_finished; + creators["uninvite"] = &WorldPacketTriggerContext::uninvite; + creators["lfg join"] = &WorldPacketTriggerContext::lfg_update; + creators["lfg proposal"] = &WorldPacketTriggerContext::lfg_proposal; + creators["lfg role check"] = &WorldPacketTriggerContext::lfg_role_check; + creators["lfg leave"] = &WorldPacketTriggerContext::lfg_leave; + creators["guild invite"] = &WorldPacketTriggerContext::guild_invite; + creators["lfg teleport"] = &WorldPacketTriggerContext::lfg_teleport; + creators["inventory change failure"] = &WorldPacketTriggerContext::inventory_change_failure; + } + + private: + static Trigger* inventory_change_failure(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "inventory change failure"); } + static Trigger* guild_invite(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "guild invite"); } + static Trigger* lfg_teleport(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "lfg teleport"); } + static Trigger* lfg_leave(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "lfg leave"); } + static Trigger* lfg_proposal(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "lfg proposal"); } + static Trigger* lfg_role_check(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "lfg role check"); } + static Trigger* lfg_update(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "lfg join"); } + static Trigger* uninvite(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "uninvite"); } + static Trigger* ready_check_finished(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "ready check finished"); } + static Trigger* ready_check(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "ready check"); } + static Trigger* duel_requested(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "duel requested"); } + static Trigger* cast_failed(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "cast failed"); } + static Trigger* taxi_done(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "taxi done"); } + static Trigger* party_command(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "party command"); } + static Trigger* item_push_result(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "item push result"); } + static Trigger* quest_objective_completed(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "quest objective completed"); } + static Trigger* out_of_react_range(PlayerbotAI* ai) { return new OutOfReactRangeTrigger(ai); } + static Trigger* loot_response(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "loot response"); } + static Trigger* trade_status(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "trade status"); } + static Trigger* cannot_equip(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "cannot equip"); } + static Trigger* check_mount_state(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "check mount state"); } + static Trigger* area_trigger(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "area trigger"); } + static Trigger* within_area_trigger(PlayerbotAI* ai) { return new WithinAreaTrigger(ai); } + static Trigger* resurrect_request(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "resurrect request"); } + static Trigger* gossip_hello(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "gossip hello"); } + static Trigger* group_invite(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "group invite"); } + static Trigger* group_set_leader(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "group set leader"); } + static Trigger* no_money(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "not enough money"); } + static Trigger* no_reputation(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "not enough reputation"); } + static Trigger* use_game_object(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "use game object"); } + static Trigger* complete_quest(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "complete quest"); } + static Trigger* accept_quest(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "accept quest"); } + static Trigger* quest_share(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "quest share"); } + static Trigger* loot_roll(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "loot roll"); } + static Trigger* taxi(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "activate taxi"); } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/values/AlwaysLootListValue.h b/src/modules/Bots/playerbot/strategy/values/AlwaysLootListValue.h new file mode 100644 index 000000000..72b6f92ba --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AlwaysLootListValue.h @@ -0,0 +1,14 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class AlwaysLootListValue : public ManualSetValue&> + { + public: + AlwaysLootListValue(PlayerbotAI* ai) : ManualSetValue&>(ai, list) {} + + private: + set list; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/AoeHealValues.cpp b/src/modules/Bots/playerbot/strategy/values/AoeHealValues.cpp new file mode 100644 index 000000000..8ec09dcd6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AoeHealValues.cpp @@ -0,0 +1,49 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AoeHealValues.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +uint8 AoeHealValue::Calculate() +{ + Group* group = bot->GetGroup(); + if (!group) + { + return 0; + } + + float range = 0; + if (qualifier == "low") + { + range = sPlayerbotAIConfig.lowHealth; + } + else if (qualifier == "medium") + { + range = sPlayerbotAIConfig.mediumHealth; + } + else if (qualifier == "critical") + { + range = sPlayerbotAIConfig.criticalHealth; + } + + uint8 count = 0; + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *player = sObjectMgr.GetPlayer(itr->guid); + if( !player || !player->IsAlive()) + { + continue; + } + + float percent = (static_cast (player->GetHealth()) / player->GetMaxHealth()) * 100; + if (percent <= range) + { + count++; + } + } + + return count; +} + diff --git a/src/modules/Bots/playerbot/strategy/values/AoeHealValues.h b/src/modules/Bots/playerbot/strategy/values/AoeHealValues.h new file mode 100644 index 000000000..1568a931f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AoeHealValues.h @@ -0,0 +1,14 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class AoeHealValue : public Uint8CalculatedValue, public Qualified + { + public: + AoeHealValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + public: + virtual uint8 Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/AoeValues.cpp b/src/modules/Bots/playerbot/strategy/values/AoeValues.cpp new file mode 100644 index 000000000..b2efa58e6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AoeValues.cpp @@ -0,0 +1,96 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AoeValues.h" + +#include "../../PlayerbotAIConfig.h" +using namespace ai; + +list FindMaxDensity(Player* bot) +{ + list units = *bot->GetPlayerbotAI()->GetAiObjectContext()->GetValue >("possible targets"); + map > groups; + int maxCount = 0; + ObjectGuid maxGroup; + for (list::iterator i = units.begin(); i != units.end(); ++i) + { + Unit* unit = bot->GetPlayerbotAI()->GetUnit(*i); + if (!unit) + { + continue; + } + + for (list::iterator j = units.begin(); j != units.end(); ++j) + { + Unit* other = bot->GetPlayerbotAI()->GetUnit(*j); + if (!other) + { + continue; + } + + float d = unit->GetDistance2d(other); + if (d <= sPlayerbotAIConfig.aoeRadius * 2) + { + groups[*i].push_back(*j); + } + } + + if (maxCount < groups[*i].size()) + { + maxCount = groups[*i].size(); + maxGroup = *i; + } + } + + if (!maxCount) + { + return list(); + } + + return groups[maxGroup]; +} + +WorldLocation AoePositionValue::Calculate() +{ + list group = FindMaxDensity(bot); + if (group.empty()) + { + return WorldLocation(); + } + + float x1, y1, x2, y2; + for (list::iterator i = group.begin(); i != group.end(); ++i) + { + Unit* unit = bot->GetPlayerbotAI()->GetUnit(*i); + if (!unit) + { + continue; + } + + if (i == group.begin() || x1 > unit->GetPositionX()) + { + x1 = unit->GetPositionX(); + } + if (i == group.begin() || x2 < unit->GetPositionX()) + { + x2 = unit->GetPositionX(); + } + if (i == group.begin() || y1 > unit->GetPositionY()) + { + y1 = unit->GetPositionY(); + } + if (i == group.begin() || y2 < unit->GetPositionY()) + { + y2 = unit->GetPositionY(); + } + } + float x = (x1 + x2) / 2; + float y = (y1 + y2) / 2; + float z = bot->GetPositionZ(); + bot->UpdateGroundPositionZ(x, y, z); + return WorldLocation(bot->GetMapId(), x, y, z, 0); +} + +uint8 AoeCountValue::Calculate() +{ + return FindMaxDensity(bot).size(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/AoeValues.h b/src/modules/Bots/playerbot/strategy/values/AoeValues.h new file mode 100644 index 000000000..d1d36f0c2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AoeValues.h @@ -0,0 +1,23 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class AoePositionValue : public CalculatedValue + { + public: + AoePositionValue(PlayerbotAI* ai) : CalculatedValue(ai, "aoe position") {} + + public: + virtual WorldLocation Calculate(); + }; + + class AoeCountValue : public CalculatedValue + { + public: + AoeCountValue(PlayerbotAI* ai) : CalculatedValue(ai, "aoe count") {} + + public: + virtual uint8 Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/Arrow.cpp b/src/modules/Bots/playerbot/strategy/values/Arrow.cpp new file mode 100644 index 000000000..149bdc9f1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/Arrow.cpp @@ -0,0 +1,180 @@ +#include "../../../botpch.h" +#include "../../playerbot.h" +#include "Formations.h" +#include "Arrow.h" + +using namespace ai; + +WorldLocation ArrowFormation::GetLocationInternal() +{ + if (!bot->GetGroup()) return Formation::NullLocation; + + Build(); + + int tankLines = 1 + tanks.Size() / 6; + int meleeLines = 1 + melee.Size() / 6; + int rangedLines = 1 + ranged.Size() / 6; + float offset = 0; + + Player* master = ai->GetMaster(); + float orientation = master->GetOrientation(); + MultiLineUnitPlacer placer(orientation); + + tanks.PlaceUnits(&placer); + tanks.Move(-cos(orientation) * offset, -sin(orientation) * offset); + + offset += tankLines * sPlayerbotAIConfig.followDistance; + melee.PlaceUnits(&placer); + melee.Move(-cos(orientation) * offset, -sin(orientation) * offset); + + offset += meleeLines * sPlayerbotAIConfig.followDistance; + ranged.PlaceUnits(&placer); + ranged.Move(-cos(orientation) * offset, -sin(orientation) * offset); + + offset += rangedLines * sPlayerbotAIConfig.followDistance; + healers.PlaceUnits(&placer); + healers.Move(-cos(orientation) * offset, -sin(orientation) * offset); + + float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); + float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); + float z = master->GetPositionZ(); + + float ground = master->GetMap()->GetHeight(master->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + return WorldLocation(master->GetMapId(), x, y, 0.05f + ground); + + +} + +void ArrowFormation::Build() +{ + if (built) + { + return; + } + + FillSlotsExceptMaster(); + AddMasterToSlot(); + + built = true; +} + +FormationSlot* ArrowFormation::FindSlot(Player* member) +{ + if (ai->IsTank(member)) + { + return &tanks; + } + else if (ai->IsHeal(member)) + { + return &healers; + } + else if (ai->IsRanged(member)) + { + return &ranged; + } + else + { + return &melee; + } +} + +void ArrowFormation::FillSlotsExceptMaster() +{ + Group* group = bot->GetGroup(); + GroupReference *gref = group->GetFirstMember(); + uint32 index = 0; + while (gref) + { + Player* member = gref->getSource(); + + if (member == bot) + { + FindSlot(member)->AddLast(botUnit = new FormationUnit(index, false)); + } + else if (member != ai->GetMaster()) + { + FindSlot(member)->AddLast(new FormationUnit(index, false)); + } + + gref = gref->next(); + index++; + } +} + +void ArrowFormation::AddMasterToSlot() +{ + Group* group = bot->GetGroup(); + GroupReference *gref = group->GetFirstMember(); + uint32 index = 0; + while (gref) + { + Player* member = gref->getSource(); + + if (member == ai->GetMaster()) + { + FindSlot(member)->InsertAtCenter(masterUnit = new FormationUnit(index, true)); + break; + } + + gref = gref->next(); + index++; + } +} + +void FormationSlot::PlaceUnits(UnitPlacer* placer) +{ + uint32 index = 0; + uint32 count = units.size(); + for (vector::iterator i = units.begin(); i != units.end(); ++i) + { + FormationUnit* unit = *i; + unit->SetLocation(placer->Place(unit, index, count)); + index++; + } +} + +UnitPosition MultiLineUnitPlacer::Place(FormationUnit *unit, uint32 index, uint32 count) +{ + SingleLineUnitPlacer placer(orientation); + if (count <= 6) + { + return placer.Place(unit, index, count); + } + + int lineNo = index / 6; + int indexInLine = index % 6; + int lineSize = max(count - lineNo * 6, uint32(6)); + return placer.Place(unit, indexInLine, lineSize); +} + +UnitPosition SingleLineUnitPlacer::Place(FormationUnit *unit, uint32 index, uint32 count) +{ + float angle = orientation - M_PI / 2.0f; + float x = cos(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2); + float y = sin(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2); + return UnitPosition(x, y); +} + +void FormationSlot::Move(float dx, float dy) +{ + for (vector::iterator i = units.begin(); i != units.end(); ++i) + { + FormationUnit* unit = *i; + unit->SetLocation(unit->GetX() + dx, unit->GetY() + dy); + } +} + +FormationSlot::~FormationSlot() +{ + for (vector::iterator i = units.begin(); i != units.end(); ++i) + { + FormationUnit* unit = *i; + delete unit; + } + units.clear(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/Arrow.h b/src/modules/Bots/playerbot/strategy/values/Arrow.h new file mode 100644 index 000000000..52857ff99 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/Arrow.h @@ -0,0 +1,109 @@ +#pragma once + +namespace ai +{ + class UnitPosition + { + public: + UnitPosition(float x, float y) : x(x), y(y) {} + UnitPosition(const UnitPosition& other) { x = other.x; y = other.y; } + float x, y; + }; + + class FormationUnit + { + public: + FormationUnit(uint32 groupIndex, bool master) : groupIndex(groupIndex), master(master), position(0, 0) {} + FormationUnit(const FormationUnit& other) : position(other.position.x, other.position.y) + { + groupIndex = other.groupIndex; + master = other.master; + } + + public: + uint32 GetGroupIdex() { return groupIndex; } + void SetLocation(UnitPosition pos) { position = pos; } + void SetLocation(float x, float y) { position.x = x; position.y = y; } + float GetX() { return position.x; } + float GetY() { return position.y; } + + private: + uint32 groupIndex; + bool master; + UnitPosition position; + }; + + class UnitPlacer + { + public: + UnitPlacer() {} + + public: + virtual UnitPosition Place(FormationUnit *unit, uint32 index, uint32 count) = 0; + }; + + class FormationSlot + { + public: + FormationSlot() {} + virtual ~FormationSlot(); + + public: + void AddLast(FormationUnit* unit) { units.push_back(unit); } + void InsertAtCenter(FormationUnit* unit) { units.insert(units.begin() + (units.size() + 1) / 2, unit); } + void PlaceUnits(UnitPlacer* placer); + void Move(float dx, float dy); + int Size() { return units.size(); } + + private: + WorldLocation center; + vector units; + }; + + + class MultiLineUnitPlacer : public UnitPlacer + { + public: + MultiLineUnitPlacer(float orientation) : UnitPlacer(), orientation(orientation) {} + + public: + virtual UnitPosition Place(FormationUnit *unit, uint32 index, uint32 count); + + private: + float orientation; + }; + + class SingleLineUnitPlacer + { + public: + SingleLineUnitPlacer(float orientation) : orientation(orientation) {} + + public: + virtual UnitPosition Place(FormationUnit *unit, uint32 index, uint32 count); + + private: + float orientation; + }; + + class ArrowFormation : public MoveAheadFormation + { + public: + ArrowFormation(PlayerbotAI* ai) : MoveAheadFormation(ai, "arrow"), built(false), masterUnit(NULL), botUnit(NULL) {} + + public: + virtual WorldLocation GetLocationInternal(); + + private: + void Build(); + void FillSlotsExceptMaster(); + void AddMasterToSlot(); + FormationSlot* FindSlot(Player* member); + + private: + FormationSlot tanks, melee, ranged, healers; + FormationUnit *masterUnit, *botUnit; + bool built; + }; + +} + diff --git a/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.cpp b/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.cpp new file mode 100644 index 000000000..4f4d96b41 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.cpp @@ -0,0 +1,123 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AttackerCountValues.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +uint8 MyAttackerCountValue::Calculate() +{ + return bot->getAttackers().size(); +} + +bool HasAggroValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return true; + } + + HostileReference *ref = bot->GetHostileRefManager().getFirst(); + if (!ref) + { + return true; // simulate as target is not atacking anybody yet + } + + while( ref ) + { + ThreatManager *threatManager = ref->getSource(); + Unit *attacker = threatManager->getOwner(); + Unit *victim = attacker->getVictim(); + if (victim == bot && target == attacker) + { + return true; + } + ref = ref->next(); + } + return false; +} + +uint8 AttackerCountValue::Calculate() +{ + int count = 0; + float range = sPlayerbotAIConfig.sightDistance; + + list attackers = context->GetValue >("attackers")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || !unit->IsAlive()) + { + continue; + } + + float distance = bot->GetDistance(unit); + if (distance <= range) + { + count++; + } + } + + return count; +} + +uint8 BalancePercentValue::Calculate() +{ + float playerLevel = 0, + attackerLevel = 0; + + Group* group = bot->GetGroup(); + if (group) + { + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *player = sObjectMgr.GetPlayer(itr->guid); + if( !player || !player->IsAlive()) + { + continue; + } + + playerLevel += player->getLevel(); + } + } + + list v = context->GetValue >("attackers")->Get(); + + for (list::iterator i = v.begin(); i!=v.end(); i++) + { + Creature* creature = ai->GetCreature((*i)); + if (!creature || !creature->IsAlive()) + { + continue; + } + + uint32 level = creature->getLevel(); + + switch (creature->GetCreatureInfo()->Rank) { + case CREATURE_ELITE_RARE: + level *= 2; + break; + case CREATURE_ELITE_ELITE: + level *= 3; + break; + case CREATURE_ELITE_RAREELITE: + level *= 3; + break; + case CREATURE_ELITE_WORLDBOSS: + level *= 5; + break; + } + attackerLevel += level; + } + + if (!attackerLevel) + { + return 100; + } + + float percent = playerLevel * 100 / attackerLevel; + return percent <= 200 ? (uint8)percent : 200; +} + diff --git a/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.h b/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.h new file mode 100644 index 000000000..9c77a3f48 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackerCountValues.h @@ -0,0 +1,59 @@ +#pragma once +#include "StatsValues.h" + +namespace ai +{ + + class AttackerCountValue : public Uint8CalculatedValue, public Qualified + { + public: + AttackerCountValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class MyAttackerCountValue : public Uint8CalculatedValue, public Qualified + { + public: + MyAttackerCountValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class HasAggroValue : public BoolCalculatedValue, public Qualified + { + public: + HasAggroValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual bool Calculate(); + }; + + class BalancePercentValue : public Uint8CalculatedValue, public Qualified + { + public: + BalancePercentValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.cpp new file mode 100644 index 000000000..453f04874 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.cpp @@ -0,0 +1,32 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AttackerWithoutAuraTargetValue.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +Unit* AttackerWithoutAuraTargetValue::Calculate() +{ + list attackers = ai->GetAiObjectContext()->GetValue >("attackers")->Get(); + Unit* target = ai->GetAiObjectContext()->GetValue("current target")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); ++i) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || unit == target) + { + continue; + } + + if (bot->GetDistance(unit) > sPlayerbotAIConfig.spellDistance) + { + continue; + } + + if (!ai->HasAura(qualifier, unit)) + { + return unit; + } + } + + return NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.h b/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.h new file mode 100644 index 000000000..a1ed091e5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackerWithoutAuraTargetValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class AttackerWithoutAuraTargetValue : public UnitCalculatedValue, public Qualified + { + public: + AttackerWithoutAuraTargetValue(PlayerbotAI* ai) : + UnitCalculatedValue(ai, "attacker without aura") {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/AttackersValue.cpp b/src/modules/Bots/playerbot/strategy/values/AttackersValue.cpp new file mode 100644 index 000000000..ab95c00b5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackersValue.cpp @@ -0,0 +1,99 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "AttackersValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +list AttackersValue::Calculate() +{ + set targets; + + AddAttackersOf(bot, targets); + + Group* group = bot->GetGroup(); + if (group) + { + AddAttackersOf(group, targets); + } + + RemoveNonThreating(targets); + + list result; + for (set::iterator i = targets.begin(); i != targets.end(); i++) + { + result.push_back((*i)->GetObjectGuid()); + } + + if (bot->duel && bot->duel->opponent) + { + result.push_back(bot->duel->opponent->GetObjectGuid()); + } + + return result; +} + +void AttackersValue::AddAttackersOf(Group* group, set& targets) +{ + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = sObjectMgr.GetPlayer(itr->guid); + if (!member || !member->IsAlive() || member == bot) + { + continue; + } + + AddAttackersOf(member, targets); + } +} + +void AttackersValue::AddAttackersOf(Player* player, set& targets) +{ + if (player->IsBeingTeleported()) + { + return; + } + + list units; + MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(player, sPlayerbotAIConfig.sightDistance); + MaNGOS::UnitListSearcher searcher(units, u_check); + Cell::VisitAllObjects(player, searcher, sPlayerbotAIConfig.sightDistance); + for (list::iterator i = units.begin(); i != units.end(); i++) + { + targets.insert(*i); + } +} + +void AttackersValue::RemoveNonThreating(set& targets) +{ + for(set::iterator tIter = targets.begin(); tIter != targets.end();) + { + Unit* unit = *tIter; + if(!bot->IsWithinLOSInMap(unit) || bot->GetMapId() != unit->GetMapId() || !hasRealThreat(unit)) + { + set::iterator tIter2 = tIter; + ++tIter; + targets.erase(tIter2); + } + else + { + ++tIter; + } + } +} + +bool AttackersValue::hasRealThreat(Unit *attacker) +{ + return attacker && + attacker->IsInWorld() && + attacker->IsAlive() && + !attacker->IsPolymorphed() && + !attacker->IsInRoots() && + !attacker->IsFriendlyTo(bot) && + (attacker->GetThreatManager().getCurrentVictim() || attacker->GetObjectGuid().IsPlayer()); +} diff --git a/src/modules/Bots/playerbot/strategy/values/AttackersValue.h b/src/modules/Bots/playerbot/strategy/values/AttackersValue.h new file mode 100644 index 000000000..93392f1ea --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AttackersValue.h @@ -0,0 +1,20 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" +#include "NearestUnitsValue.h" + +namespace ai +{ + class AttackersValue : public ObjectGuidListCalculatedValue + { + public: + AttackersValue(PlayerbotAI* ai) : ObjectGuidListCalculatedValue(ai, "attackers", 1) {} + list Calculate(); + + private: + void AddAttackersOf(Group* group, set& targets); + void AddAttackersOf(Player* player, set& targets); + void RemoveNonThreating(set& targets); + bool hasRealThreat(Unit* attacker); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/AvailableLootValue.h b/src/modules/Bots/playerbot/strategy/values/AvailableLootValue.h new file mode 100644 index 000000000..0089cb45d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/AvailableLootValue.h @@ -0,0 +1,42 @@ +#pragma once +#include "../Value.h" +#include "../../LootObjectStack.h" + +namespace ai +{ + + class AvailableLootValue : public ManualSetValue + { + public: + AvailableLootValue(PlayerbotAI* ai) : ManualSetValue(ai, NULL) + { + value = new LootObjectStack(ai->GetBot()); + } + + virtual ~AvailableLootValue() + { + if (value) + { + delete value; + } + } + }; + + class LootTargetValue : public ManualSetValue + { + public: + LootTargetValue(PlayerbotAI* ai) : ManualSetValue(ai, LootObject()) {} + }; + + class CanLootValue : public BoolCalculatedValue + { + public: + CanLootValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + virtual bool Calculate() + { + LootObject loot = AI_VALUE(LootObject, "loot target"); + return !loot.IsEmpty() && loot.GetWorldObject(bot) && AI_VALUE2(float, "distance", "loot target") <= INTERACTION_DISTANCE; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/CcTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/CcTargetValue.cpp new file mode 100644 index 000000000..f27760afa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CcTargetValue.cpp @@ -0,0 +1,111 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CcTargetValue.h" +#include "../../PlayerbotAIConfig.h" +#include "../Action.h" + +using namespace ai; + +class FindTargetForCcStrategy : public FindTargetStrategy +{ +public: + FindTargetForCcStrategy(PlayerbotAI* ai, string& spell) : FindTargetStrategy(ai) + { + this->spell = spell; + maxDistance = 0; + } + +public: + virtual void CheckAttacker(Unit* creature, ThreatManager* threatManager) + { + Player* bot = ai->GetBot(); + if (*ai->GetAiObjectContext()->GetValue("current target") == creature) + { + return; + } + + uint8 health = creature->GetHealthPercent(); + if (health < sPlayerbotAIConfig.mediumHealth) + { + return; + } + + if (!ai->CanCastSpell(spell, creature)) + { + return; + } + + if (*ai->GetAiObjectContext()->GetValue("rti target") == creature) + { + result = creature; + return; + } + + float minDistance = sPlayerbotAIConfig.spellDistance; + Group* group = bot->GetGroup(); + if (!group) + { + return; + } + + if (group->GetTargetIcon(4) == creature->GetObjectGuid()) + { + result = creature; + return; + } + + if (*ai->GetAiObjectContext()->GetValue("aoe count") > 2) + { + WorldLocation aoe = *ai->GetAiObjectContext()->GetValue("aoe position"); + if (creature->GetDistance2d(aoe.coord_x, aoe.coord_y) <= sPlayerbotAIConfig.aoeRadius) + { + return; + } + } + + int tankCount, dpsCount; + GetPlayerCount(creature, &tankCount, &dpsCount); + if (!tankCount || !dpsCount) + { + result = creature; + return; + } + + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = sObjectMgr.GetPlayer(itr->guid); + if( !member || !member->IsAlive() || member == bot) + { + continue; + } + + if (!ai->IsTank(member)) + { + continue; + } + + float distance = member->GetDistance(creature); + if (distance < minDistance) + { + minDistance = distance; + } + } + + if (!result || minDistance > maxDistance) + { + result = creature; + maxDistance = minDistance; + } + } + +private: + string spell; + float maxDistance; +}; + +Unit* CcTargetValue::Calculate() +{ + FindTargetForCcStrategy strategy(ai, qualifier); + return FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/CcTargetValue.h b/src/modules/Bots/playerbot/strategy/values/CcTargetValue.h new file mode 100644 index 000000000..dcf697a20 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CcTargetValue.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class CcTargetValue : public TargetValue, public Qualified + { + public: + CcTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ChatValue.h b/src/modules/Bots/playerbot/strategy/values/ChatValue.h new file mode 100644 index 000000000..5571b36c2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ChatValue.h @@ -0,0 +1,11 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class ChatValue : public ManualSetValue + { + public: + ChatValue(PlayerbotAI* ai) : ManualSetValue(ai, CHAT_MSG_WHISPER) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.cpp new file mode 100644 index 000000000..f5504a02c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.cpp @@ -0,0 +1,33 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CurrentCcTargetValue.h" + +using namespace ai; + +class FindCurrentCcTargetStrategy : public FindTargetStrategy +{ +public: + FindCurrentCcTargetStrategy(PlayerbotAI* ai, string& spell) : FindTargetStrategy(ai) + { + this->spell = spell; + } + +public: + virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager) + { + if (ai->HasAura(spell, attacker)) + { + result = attacker; + } + } + +private: + string spell; +}; + + +Unit* CurrentCcTargetValue::Calculate() +{ + FindCurrentCcTargetStrategy strategy(ai, qualifier); + return FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.h b/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.h new file mode 100644 index 000000000..52c64881b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CurrentCcTargetValue.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class CurrentCcTargetValue : public TargetValue, public Qualified + { + public: + CurrentCcTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.cpp new file mode 100644 index 000000000..2ddf95968 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.cpp @@ -0,0 +1,28 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "CurrentTargetValue.h" + +using namespace ai; + +Unit* CurrentTargetValue::Get() +{ + + + if (selection.IsEmpty()) + { + return NULL; + } + + Unit* unit = sObjectAccessor.GetUnit(*bot, selection); + if (unit && !bot->IsWithinLOSInMap(unit)) + { + return NULL; + } + + return unit; +} + +void CurrentTargetValue::Set(Unit* target) +{ + selection = target ? target->GetObjectGuid() : ObjectGuid(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.h b/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.h new file mode 100644 index 000000000..18a3cbf49 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/CurrentTargetValue.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class CurrentTargetValue : public UnitManualSetValue + { + public: + CurrentTargetValue(PlayerbotAI* ai) : UnitManualSetValue(ai, NULL) {} + + virtual Unit* Get(); + virtual void Set(Unit* unit); + + private: + ObjectGuid selection; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/DistanceValue.h b/src/modules/Bots/playerbot/strategy/values/DistanceValue.h new file mode 100644 index 000000000..3233a7928 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/DistanceValue.h @@ -0,0 +1,41 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" +#include "../../LootObjectStack.h" + +namespace ai +{ + class DistanceValue : public FloatCalculatedValue, public Qualified + { + public: + DistanceValue(PlayerbotAI* ai) : FloatCalculatedValue(ai) {} + + public: + float Calculate() + { + if (qualifier == "loot target") + { + LootObject loot = AI_VALUE(LootObject, qualifier); + if (loot.IsEmpty()) + { + return 0.0f; + } + + WorldObject* obj = loot.GetWorldObject(bot); + if (!obj) + { + return 0.0f; + } + + return ai->GetBot()->GetDistance2d(obj); + } + Unit* target = AI_VALUE(Unit*, qualifier); + if (!target || !target->IsInWorld()) + { + return 0.0f; + } + + return ai->GetBot()->GetDistance2d(target); + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.cpp new file mode 100644 index 000000000..94f55def0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.cpp @@ -0,0 +1,44 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DpsTargetValue.h" + +using namespace ai; + +class FindLeastHpTargetStrategy : public FindTargetStrategy +{ +public: + explicit FindLeastHpTargetStrategy(PlayerbotAI* ai) : FindTargetStrategy(ai) + { + minHealth = 0; + } + +public: + virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager) + { + Group* group = ai->GetBot()->GetGroup(); + if (group) + { + uint64 guid = group->GetTargetIcon(4); + if (guid && attacker->GetObjectGuid() == ObjectGuid(guid)) + { + return; + } + } + if (!result || result->GetHealth() > attacker->GetHealth()) + { + result = attacker; + } + } + +protected: + float minHealth; +}; + +Unit* DpsTargetValue::Calculate() +{ + Unit* rti = RtiTargetValue::Calculate(); + if (rti) return rti; + + FindLeastHpTargetStrategy strategy(ai); + return TargetValue::FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.h b/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.h new file mode 100644 index 000000000..35955a0ea --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/DpsTargetValue.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "RtiTargetValue.h" +#include "TargetValue.h" + +namespace ai +{ + class DpsTargetValue : public RtiTargetValue + { + public: + DpsTargetValue(PlayerbotAI* ai) : RtiTargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.cpp new file mode 100644 index 000000000..ea0135484 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "DuelTargetValue.h" + +using namespace ai; + +Unit* DuelTargetValue::Calculate() +{ + return bot->duel ? bot->duel->opponent : NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.h b/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.h new file mode 100644 index 000000000..606fe0b01 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/DuelTargetValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + class DuelTargetValue : public TargetValue + { + public: + DuelTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.cpp new file mode 100644 index 000000000..2ed4db0e2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.cpp @@ -0,0 +1,46 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "EnemyHealerTargetValue.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +Unit* EnemyHealerTargetValue::Calculate() +{ + string spell = qualifier; + + list attackers = ai->GetAiObjectContext()->GetValue >("attackers")->Get(); + Unit* target = ai->GetAiObjectContext()->GetValue("current target")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); ++i) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || unit == target) + { + continue; + } + + if (bot->GetDistance(unit) > sPlayerbotAIConfig.spellDistance) + { + continue; + } + + if (!ai->IsInterruptableSpellCasting(unit, spell)) + { + continue; + } + + Spell* spell = unit->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (spell && IsPositiveSpell(spell->m_spellInfo)) + { + return unit; + } + + spell = unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL); + if (spell && IsPositiveSpell(spell->m_spellInfo)) + { + return unit; + } + } + + return NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.h b/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.h new file mode 100644 index 000000000..48f92defa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/EnemyHealerTargetValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class EnemyHealerTargetValue : public UnitCalculatedValue, public Qualified + { + public: + EnemyHealerTargetValue(PlayerbotAI* ai) : + UnitCalculatedValue(ai, "enemy healer target") {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.cpp b/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.cpp new file mode 100644 index 000000000..ab5ff0184 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.cpp @@ -0,0 +1,36 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "EnemyPlayerValue.h" +#include "TargetValue.h" + +using namespace ai; +using namespace std; + +class FindEnemyPlayerStrategy : public FindTargetStrategy +{ +public: + explicit FindEnemyPlayerStrategy(PlayerbotAI* ai) : FindTargetStrategy(ai) + { + } + +public: + virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager) + { + if (!result) + { + Player* enemy = dynamic_cast(attacker); + if (enemy && ai->IsOpposing(enemy) && enemy->IsPvP()) + { + result = attacker; + } + } + } + +}; + + +Unit* EnemyPlayerValue::Calculate() +{ + FindEnemyPlayerStrategy strategy(ai); + return FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.h b/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.h new file mode 100644 index 000000000..4f124697b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/EnemyPlayerValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + class EnemyPlayerValue : public TargetValue + { + public: + EnemyPlayerValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/Formations.cpp b/src/modules/Bots/playerbot/strategy/values/Formations.cpp new file mode 100644 index 000000000..4e6efa3b6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/Formations.cpp @@ -0,0 +1,514 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "Formations.h" +#include "Arrow.h" + +using namespace ai; + +WorldLocation Formation::NullLocation = WorldLocation(); + +bool IsSameLocation(WorldLocation const &a, WorldLocation const &b) +{ + return a.coord_x == b.coord_x && a.coord_y == b.coord_y && a.coord_z == b.coord_z && a.mapid == b.mapid; +} + +bool Formation::IsNullLocation(WorldLocation const& loc) +{ + return IsSameLocation(loc, Formation::NullLocation); +} + + +WorldLocation MoveAheadFormation::GetLocation() +{ + Player* master = GetMaster(); + if (!master) + { + return WorldLocation(); + } + + WorldLocation loc = GetLocationInternal(); + if (Formation::IsNullLocation(loc)) + { + return loc; + } + + float x = loc.coord_x; + float y = loc.coord_y; + float z = loc.coord_z; + + if (master->isMoving()) + { + float ori = master->GetOrientation(); + float x1 = x + sPlayerbotAIConfig.tooCloseDistance * cos(ori); + float y1 = y + sPlayerbotAIConfig.tooCloseDistance * sin(ori); + float ground = master->GetMap()->GetHeight(master->GetPhaseMask(), x1, y1, z + 0.5f); + if (ground > INVALID_HEIGHT) + { + x = x1; + y = y1; + } + } + float ground = master->GetMap()->GetHeight(master->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + return WorldLocation(master->GetMapId(), x, y, ground + 0.5f); +} + +namespace ai +{ + class MeleeFormation : public FollowFormation + { + public: + explicit MeleeFormation(PlayerbotAI* ai) : FollowFormation(ai, "melee") {} + virtual string GetTargetName() { return "master target"; } + }; + + class QueueFormation : public FollowFormation + { + public: + explicit QueueFormation(PlayerbotAI* ai) : FollowFormation(ai, "queue") {} + virtual string GetTargetName() { return "line target"; } + }; + + class NearFormation : public MoveAheadFormation + { + public: + explicit NearFormation(PlayerbotAI* ai) : MoveAheadFormation(ai, "near") {} + virtual WorldLocation GetLocationInternal() + { + Player* master = GetMaster(); + if (!master) + { + return WorldLocation(); + } + + float range = sPlayerbotAIConfig.followDistance; + float angle = GetFollowAngle(); + float x = master->GetPositionX() + cos(angle) * range; + float y = master->GetPositionY() + sin(angle) * range; + float z = master->GetPositionZ(); + float ground = master->GetMap()->GetHeight(master->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + return WorldLocation(master->GetMapId(), x, y, ground + 0.5f); + } + + virtual float GetMaxDistance() { return sPlayerbotAIConfig.followDistance; } + }; + + + class ChaosFormation : public MoveAheadFormation + { + public: + explicit ChaosFormation(PlayerbotAI* ai) : MoveAheadFormation(ai, "chaos"), lastChangeTime(0) {} + virtual WorldLocation GetLocationInternal() + { + Player* master = GetMaster(); + if (!master) + { + return WorldLocation(); + } + + float range = sPlayerbotAIConfig.followDistance; + float angle = GetFollowAngle(); + + time_t now = time(0); + if (!lastChangeTime || now - lastChangeTime >= 3) { + { + lastChangeTime = now; + } + dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig.tooCloseDistance; + dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig.tooCloseDistance; + dr = sqrt(dx*dx + dy*dy); + } + + float x = master->GetPositionX() + cos(angle) * range + dx; + float y = master->GetPositionY() + sin(angle) * range + dy; + float z = master->GetPositionZ(); + float ground = master->GetMap()->GetHeight(master->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + return WorldLocation(master->GetMapId(), x, y, ground + 0.5f); + } + + virtual float GetMaxDistance() { return sPlayerbotAIConfig.followDistance + dr; } + + private: + time_t lastChangeTime; + float dx = 0, dy = 0, dr = 0; + }; + + class CircleFormation : public MoveFormation + { + public: + explicit CircleFormation(PlayerbotAI* ai) : MoveFormation(ai, "circle") {} + virtual WorldLocation GetLocation() + { + float range = 2.0f; + + Unit* target = AI_VALUE(Unit*, "current target"); + Player* master = GetMaster(); + if (!target) + { + target = master; + } + + if (!target) + { + return Formation::NullLocation; + } + + switch (bot->getClass()) + { + case CLASS_HUNTER: + case CLASS_MAGE: + case CLASS_PRIEST: + case CLASS_WARLOCK: + range = sPlayerbotAIConfig.fleeDistance; + break; + case CLASS_DRUID: + if (!ai->IsTank(bot)) + { + range = sPlayerbotAIConfig.fleeDistance; + } + break; + case CLASS_SHAMAN: + if (ai->IsHeal(bot)) + { + range = sPlayerbotAIConfig.fleeDistance; + } + break; + } + + float x = target->GetPositionX(); + float y = target->GetPositionY(); + float z = target->GetPositionZ(); + float ground = target->GetMap()->GetHeight(target->GetPhaseMask(), x, y, z + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + float angle = GetFollowAngle(); + return WorldLocation(bot->GetMapId(), x + cos(angle) * range, y + sin(angle) * range, ground + 0.5f); + } + }; + + class LineFormation : public MoveAheadFormation + { + public: + explicit LineFormation(PlayerbotAI* ai) : MoveAheadFormation(ai, "line") {} + virtual WorldLocation GetLocationInternal() + { + Group* group = bot->GetGroup(); + if (!group) + { + return Formation::NullLocation; + } + + float range = 2.0f; + + Player* master = GetMaster(); + if (!master) + { + return Formation::NullLocation; + } + + float x = master->GetPositionX(); + float y = master->GetPositionY(); + float z = master->GetPositionZ(); + float orientation = master->GetOrientation(); + + vector players; + GroupReference *gref = group->GetFirstMember(); + while( gref ) + { + Player* member = gref->getSource(); + if (member != master) + { + players.push_back(member); + } + + gref = gref->next(); + } + + players.insert(players.begin() + group->GetMembersCount() / 2, master); + + return MoveLine(players, 0.0f, x, y, z, orientation, range); + } + }; + + class ShieldFormation : public MoveFormation + { + public: + explicit ShieldFormation(PlayerbotAI* ai) : MoveFormation(ai, "shield") {} + virtual WorldLocation GetLocation() + { + Group* group = bot->GetGroup(); + if (!group) + { + return Formation::NullLocation; + } + + float range = sPlayerbotAIConfig.followDistance; + + Player* master = GetMaster(); + if (!master) + { + return Formation::NullLocation; + } + + float x = master->GetPositionX(); + float y = master->GetPositionY(); + float z = master->GetPositionZ(); + float orientation = master->GetOrientation(); + + vector tanks; + vector dps; + GroupReference *gref = group->GetFirstMember(); + while( gref ) + { + Player* member = gref->getSource(); + if (member != master) + { + if (ai->IsTank(member)) + { + tanks.push_back(member); + } + else + { + dps.push_back(member); + } + } + + gref = gref->next(); + } + + if (ai->IsTank(master)) + { + tanks.insert(tanks.begin() + (tanks.size() + 1) / 2, master); + } + else + { + dps.insert(dps.begin() + (dps.size() + 1) / 2, master); + } + + if (ai->IsTank(bot) && ai->IsTank(master)) + { + return MoveLine(tanks, 0.0f, x, y, z, orientation, range); + } + if (!ai->IsTank(bot) && !ai->IsTank(master)) + { + return MoveLine(dps, 0.0f, x, y, z, orientation, range); + } + if (ai->IsTank(bot) && !ai->IsTank(master)) + { + float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig.tooCloseDistance / 2.0f : 0.0f; + return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation, range); + } + if (!ai->IsTank(bot) && ai->IsTank(master)) + { + float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig.tooCloseDistance / 2.0f : 0.0f; + return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation, range); + } + return Formation::NullLocation; + } + }; +}; + +float Formation::GetFollowAngle() +{ + Player* master = GetMaster(); + Group* group = bot->GetGroup(); + int index = 0, total = 1; + float start = (master ? master->GetOrientation() : 0.0f); + if (!group && master) + { + for (PlayerBotMap::const_iterator i = master->GetPlayerbotMgr()->GetPlayerBotsBegin(); i != master->GetPlayerbotMgr()->GetPlayerBotsEnd(); ++i) + { + if (i->second == bot) index = total; + { + total++; + } + } + } + else + { + for (GroupReference *ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if( ref->getSource() == master) + { + continue; + } + + if( ref->getSource() == bot) + { + index = total; + } + + total++; + } + } + + return start + (0.125f + 1.75f * index / total + (total == 2 ? 0.125f : 0.0f)) * M_PI; +} + +FormationValue::FormationValue(PlayerbotAI* ai) : ManualSetValue(ai, new NearFormation(ai), "formation") +{ +} + +bool SetFormationAction::Execute(Event event) +{ + string formation = event.getParam(); + + Value* value = context->GetValue("formation"); + if (formation == "?" || formation.empty()) + { + ostringstream str; str << "Formation: |cff00ff00" << value->Get()->getName(); + ai->TellMaster(str); + return true; + } + + if (formation == "melee") + { + if (value->Get()) delete value->Get(); + { + value->Set(new MeleeFormation(ai)); + } + } + else if (formation == "queue") + { + if (value->Get()) delete value->Get(); + { + value->Set(new QueueFormation(ai)); + } + } + else if (formation == "chaos") + { + if (value->Get()) delete value->Get(); + { + value->Set(new ChaosFormation(ai)); + } + } + else if (formation == "circle") + { + if (value->Get()) delete value->Get(); + { + value->Set(new CircleFormation(ai)); + } + } + else if (formation == "line") + { + if (value->Get()) delete value->Get(); + { + value->Set(new LineFormation(ai)); + } + } + else if (formation == "shield") + { + if (value->Get()) delete value->Get(); + { + value->Set(new ShieldFormation(ai)); + } + } + else if (formation == "arrow") + { + if (value->Get()) delete value->Get(); + { + value->Set(new ArrowFormation(ai)); + } + } + else if (formation == "near" || formation == "default") + { + if (value->Get()) delete value->Get(); + { + value->Set(new NearFormation(ai)); + } + } + else + { + ostringstream str; str << "Invalid formation: |cffff0000" << formation; + ai->TellMaster(str); + ai->TellMaster("Please set to any of:|cffffffff melee (default), queue, chaos, circle, line, shield, arrow, near"); + return false; + } + + ostringstream str; str << "Formation set to: " << formation; + ai->TellMaster(str); + return true; +} + + +WorldLocation MoveFormation::MoveLine(vector line, float diff, float cx, float cy, float cz, float orientation, float range) +{ + if (line.size() < 5) + { + return MoveSingleLine(line, diff, cx, cy, cz, orientation, range); + } + + int lines = ceil((double)line.size() / 5.0); + for (int i = 0; i < lines; i++) + { + float radius = range * i; + float x = cx + cos(orientation) * radius; + float y = cy + sin(orientation) * radius; + vector singleLine; + for (int j = 0; j < 5 && !line.empty(); j++) + { + singleLine.push_back(line[line.size() - 1]); + line.pop_back(); + } + + WorldLocation loc = MoveSingleLine(singleLine, diff, x, y,cz, orientation, range); + if (!Formation::IsNullLocation(loc)) + { + return loc; + } + } + + return Formation::NullLocation; +} + +WorldLocation MoveFormation::MoveSingleLine(vector line, float diff, float cx, float cy, float cz, float orientation, float range) +{ + float count = line.size(); + float angle = orientation - M_PI / 2.0f; + float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff); + float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff); + + int index = 0; + for (vector::iterator i = line.begin(); i != line.end(); i++) + { + Player* member = *i; + + if (member == bot) + { + float angle = orientation + M_PI / 2.0f; + float radius = range * index; + + float lx = x + cos(angle) * radius; + float ly = y + sin(angle) * radius; + float lz = cz; + float ground = bot->GetMap()->GetHeight(bot->GetPhaseMask(), lx, ly, lz + 0.5f); + if (ground <= INVALID_HEIGHT) + { + return Formation::NullLocation; + } + + return WorldLocation(bot->GetMapId(), lx, ly, ground + 0.5f); + } + + index++; + } + + return Formation::NullLocation; +} diff --git a/src/modules/Bots/playerbot/strategy/values/Formations.h b/src/modules/Bots/playerbot/strategy/values/Formations.h new file mode 100644 index 000000000..2c6a38fa4 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/Formations.h @@ -0,0 +1,61 @@ +#pragma once +#include "../Value.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class Formation : public AiNamedObject + { + public: + Formation(PlayerbotAI* ai, string name) : AiNamedObject (ai, name) {} + + public: + virtual string GetTargetName() { return ""; } + virtual WorldLocation GetLocation() { return NullLocation; } + virtual float GetMaxDistance() { return sPlayerbotAIConfig.followDistance; } + static WorldLocation NullLocation; + static bool IsNullLocation(WorldLocation const& loc); + + protected: + float GetFollowAngle(); + }; + + class FollowFormation : public Formation + { + public: + FollowFormation(PlayerbotAI* ai, string name) : Formation(ai, name) {} + }; + + class MoveFormation : public Formation + { + public: + MoveFormation(PlayerbotAI* ai, string name) : Formation(ai, name) {} + + protected: + WorldLocation MoveLine(vector line, float diff, float cx, float cy, float cz, float orientation, float range); + WorldLocation MoveSingleLine(vector line, float diff, float cx, float cy, float cz, float orientation, float range); + }; + + class MoveAheadFormation : public MoveFormation + { + public: + MoveAheadFormation(PlayerbotAI* ai, string name) : MoveFormation(ai, name) {} + virtual WorldLocation GetLocation(); + virtual WorldLocation GetLocationInternal() { return NullLocation; } + }; + + class FormationValue : public ManualSetValue + { + public: + FormationValue(PlayerbotAI* ai); + ~FormationValue() { if (value) { delete value; value = NULL; } } + }; + + class SetFormationAction : public Action + { + public: + SetFormationAction(PlayerbotAI* ai) : Action(ai, "set formation") {} + virtual bool Execute(Event event); + }; +}; + diff --git a/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.cpp new file mode 100644 index 000000000..9d620d960 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.cpp @@ -0,0 +1,150 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GrindTargetValue.h" +#include "../../PlayerbotAIConfig.h" +#include "../../RandomPlayerbotMgr.h" + +using namespace ai; + +Unit* GrindTargetValue::Calculate() +{ + uint32 memberCount = 1; + Group* group = bot->GetGroup(); + if (group) + { + memberCount = group->GetMembersCount(); + } + + Unit* target = NULL; + uint32 assistCount = 0; + while (!target && assistCount < memberCount) + { + target = FindTargetForGrinding(assistCount++); + } + + return target; +} + + +Unit* GrindTargetValue::FindTargetForGrinding(int assistCount) +{ + uint32 memberCount = 1; + Group* group = bot->GetGroup(); + Player* master = GetMaster(); + + list attackers = context->GetValue >("attackers")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || !unit->IsAlive()) + { + continue; + } + + return unit; + } + + list targets = *context->GetValue >("possible targets"); + + if(targets.empty()) + { + return NULL; + } + + float distance = 0; + Unit* result = NULL; + for(list::iterator tIter = targets.begin(); tIter != targets.end(); tIter++) + { + Unit* unit = ai->GetUnit(*tIter); + if (!unit) + { + continue; + } + + if (abs(bot->GetPositionZ() - unit->GetPositionZ()) > sPlayerbotAIConfig.spellDistance) + { + continue; + } + + if (GetTargetingPlayerCount(unit) > assistCount) + { + continue; + } + + if (master && master->GetDistance(unit) >= sPlayerbotAIConfig.grindDistance && !sRandomPlayerbotMgr.IsRandomBot(bot)) + { + continue; + } + + if ((int)unit->getLevel() - (int)bot->getLevel() > 4 && !unit->GetObjectGuid().IsPlayer()) + { + continue; + } + + Creature* creature = dynamic_cast(unit); + if (creature && creature->GetCreatureInfo() && creature->GetCreatureInfo()->Rank > CREATURE_ELITE_NORMAL) + { + continue; + } + + if (group) + { + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = sObjectMgr.GetPlayer(itr->guid); + if( !member || !member->IsAlive()) + { + continue; + } + + float d = member->GetDistance(unit); + if (!result || d < distance) + { + distance = d; + result = unit; + } + } + } + else + { + float d = bot->GetDistance(unit); + if (!result || d < distance) + { + distance = d; + result = unit; + } + } + } + + return result; +} + + +int GrindTargetValue::GetTargetingPlayerCount( Unit* unit ) +{ + Group* group = bot->GetGroup(); + if (!group) + { + return 0; + } + + int count = 0; + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = sObjectMgr.GetPlayer(itr->guid); + if( !member || !member->IsAlive() || member == bot) + { + continue; + } + + PlayerbotAI* ai = member->GetPlayerbotAI(); + if ((ai && *ai->GetAiObjectContext()->GetValue("current target") == unit) || + (!ai && member->GetSelectionGuid() == unit->GetObjectGuid())) + count++; + } + + return count; +} + diff --git a/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.h b/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.h new file mode 100644 index 000000000..c3b1ad5b3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/GrindTargetValue.h @@ -0,0 +1,20 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class GrindTargetValue : public TargetValue + { + public: + GrindTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + + private: + int GetTargetingPlayerCount(Unit* unit); + Unit* FindTargetForGrinding(int assistCount); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/HasAvailableLootValue.h b/src/modules/Bots/playerbot/strategy/values/HasAvailableLootValue.h new file mode 100644 index 000000000..87a796227 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/HasAvailableLootValue.h @@ -0,0 +1,20 @@ +#pragma once +#include "../Value.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class HasAvailableLootValue : public BoolCalculatedValue + { + public: + HasAvailableLootValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + public: + virtual bool Calculate() + { + return !AI_VALUE(bool, "can loot") && + AI_VALUE(LootObjectStack*, "available loot")->CanLoot(sPlayerbotAIConfig.lootDistance) && + !bot->IsMounted(); + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/HasTotemValue.h b/src/modules/Bots/playerbot/strategy/values/HasTotemValue.h new file mode 100644 index 000000000..e9f3f0c17 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/HasTotemValue.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" +#include "../../LootObjectStack.h" + +namespace ai +{ + class HasTotemValue : public BoolCalculatedValue, public Qualified + { + public: + HasTotemValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + public: + bool Calculate() + { + list units = *context->GetValue >("nearest npcs"); + for (list::iterator i = units.begin(); i != units.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (!unit) + { + continue; + } + + Creature* creature = dynamic_cast(unit); + if (!creature || !creature->IsTotem()) + { + continue; + } + + if (strstri(creature->GetName(), qualifier.c_str()) && bot->GetDistance(creature) <= sPlayerbotAIConfig.spellDistance) + { + return true; + } + } + + return false; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.cpp new file mode 100644 index 000000000..c460a2830 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.cpp @@ -0,0 +1,26 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "InvalidTargetValue.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +bool InvalidTargetValue::Calculate() +{ + Unit* target = AI_VALUE(Unit*, qualifier); + if (qualifier == "current target") + { + return !target || + target->GetMapId() != bot->GetMapId() || + target->IsDead() || + target->IsPolymorphed() || + target->IsCharmed() || + target->IsFeared() || + target->hasUnitState(UNIT_STAT_ISOLATED) || + target->IsFriendlyTo(bot) || + !bot->IsWithinDistInMap(target, sPlayerbotAIConfig.sightDistance) || + !bot->IsWithinLOSInMap(target); + } + + return !target; +} diff --git a/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.h b/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.h new file mode 100644 index 000000000..1299257b1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/InvalidTargetValue.h @@ -0,0 +1,14 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class InvalidTargetValue : public BoolCalculatedValue, public Qualified + { + public: + InvalidTargetValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + public: + virtual bool Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/IsBehindValue.h b/src/modules/Bots/playerbot/strategy/values/IsBehindValue.h new file mode 100644 index 000000000..714c00b2e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/IsBehindValue.h @@ -0,0 +1,27 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class IsBehindValue : public BoolCalculatedValue, public Qualified + { + public: + IsBehindValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + virtual bool Calculate() + { + Unit* target = AI_VALUE(Unit*, qualifier); + if (!target) + { + return false; + } + + + float targetOrientation = target->GetOrientation(); + float orientation = bot->GetOrientation(); + float distance = bot->GetDistance(target); + + return distance <= ATTACK_DISTANCE && abs(targetOrientation - orientation) < M_PI / 2; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/IsFacingValue.h b/src/modules/Bots/playerbot/strategy/values/IsFacingValue.h new file mode 100644 index 000000000..0972531be --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/IsFacingValue.h @@ -0,0 +1,22 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class IsFacingValue : public BoolCalculatedValue, public Qualified + { + public: + IsFacingValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + virtual bool Calculate() + { + Unit* target = AI_VALUE(Unit*, qualifier); + if (!target) + { + return false; + } + + return bot->IsInFront(target, sPlayerbotAIConfig.sightDistance, CAST_ANGLE_IN_FRONT); + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/IsMovingValue.h b/src/modules/Bots/playerbot/strategy/values/IsMovingValue.h new file mode 100644 index 000000000..631f5a4ef --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/IsMovingValue.h @@ -0,0 +1,64 @@ +#pragma once +#include "../Value.h" +#include "MotionGenerators/TargetedMovementGenerator.h" + +namespace ai +{ + class IsMovingValue : public BoolCalculatedValue, public Qualified + { + public: + IsMovingValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + virtual bool Calculate() + { + Unit* target = AI_VALUE(Unit*, qualifier); + Unit* chaseTarget; + + if (!target) + { + return false; + } + + switch (target->GetMotionMaster()->GetCurrentMovementGeneratorType()) + { + case FLEEING_MOTION_TYPE: + return true; + case CHASE_MOTION_TYPE: + if (target->GetTypeId() == TYPEID_PLAYER) + { + chaseTarget = static_cast const*>(target->GetMotionMaster()->GetCurrent())->GetTarget(); + } + else + { + chaseTarget = static_cast const*>(target->GetMotionMaster()->GetCurrent())->GetTarget(); + } + + if (!chaseTarget) + { + return false; + } + Player* chaseTargetPlayer = sObjectMgr.GetPlayer(chaseTarget->GetObjectGuid()); + return chaseTargetPlayer && !ai->IsTank(chaseTargetPlayer); + } + return false; + } + }; + + class IsSwimmingValue : public BoolCalculatedValue, public Qualified + { + public: + IsSwimmingValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + virtual bool Calculate() + { + Unit* target = AI_VALUE(Unit*, qualifier); + + if (!target) + { + return false; + } + + return target->IsUnderWater() || target->IsInWater(); + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemCountValue.cpp b/src/modules/Bots/playerbot/strategy/values/ItemCountValue.cpp new file mode 100644 index 000000000..726da1b39 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemCountValue.cpp @@ -0,0 +1,37 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ItemCountValue.h" + +using namespace ai; + +list InventoryItemValueBase::Find(string qualifier) +{ + list result; + + list items = InventoryAction::parseItems(qualifier); + for (list::iterator i = items.begin(); i != items.end(); i++) + { + result.push_back(*i); + } + + return result; +} + + +uint8 ItemCountValue::Calculate() +{ + uint8 count = 0; + list items = Find(qualifier); + for (list::iterator i = items.begin(); i != items.end(); ++i) + { + Item* item = *i; + count += item->GetCount(); + } + + return count; +} + +list InventoryItemValue::Calculate() +{ + return Find(qualifier); +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemCountValue.h b/src/modules/Bots/playerbot/strategy/values/ItemCountValue.h new file mode 100644 index 000000000..686548223 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemCountValue.h @@ -0,0 +1,35 @@ +#pragma once +#include "../Value.h" +#include "../ItemVisitors.h" +#include "../actions/InventoryAction.h" + +namespace ai +{ + class InventoryItemValueBase : public InventoryAction + { + public: + InventoryItemValueBase(PlayerbotAI* ai) : InventoryAction(ai, "empty") {} + virtual bool Execute(Event event) { return false; } + + protected: + list Find(string qualifier); + }; + + class ItemCountValue : public Uint8CalculatedValue, public Qualified, InventoryItemValueBase + { + public: + ItemCountValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai), InventoryItemValueBase(ai) {} + + public: + virtual uint8 Calculate(); + }; + + class InventoryItemValue : public CalculatedValue >, public Qualified, InventoryItemValueBase + { + public: + InventoryItemValue(PlayerbotAI* ai) : CalculatedValue >(ai), InventoryItemValueBase(ai) {} + + public: + virtual list Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.cpp b/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.cpp new file mode 100644 index 000000000..61243eb1d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.cpp @@ -0,0 +1,90 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ItemForSpellValue.h" + +using namespace ai; + +#ifndef WIN32 +inline int strcmpi(const char* s1, const char* s2) +{ + for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2); + { + return *s1 - *s2; + } +} +#endif + +Item* ItemForSpellValue::Calculate() +{ + uint32 spellid = atoi(qualifier.c_str()); + if (!spellid) + { + return NULL; + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid ); + if (!spellInfo) + { + return NULL; + } + + Item* itemForSpell = NULL; + Player* trader = bot->GetTrader(); + if (trader) + { + itemForSpell = trader->GetTradeData()->GetItem(TRADE_SLOT_NONTRADED); + if (itemForSpell && itemForSpell->IsFitToSpellRequirements(spellInfo)) + { + return itemForSpell; + } + } + + // Workaround as some spells have no item mask (e.g. shaman weapon enhancements) + if (!strcmpi(spellInfo->SpellName[0], "rockbiter weapon") || + !strcmpi(spellInfo->SpellName[0], "flametongue weapon") || + !strcmpi(spellInfo->SpellName[0], "earthliving weapon") || + !strcmpi(spellInfo->SpellName[0], "frostbrand weapon") || + !strcmpi(spellInfo->SpellName[0], "windfury weapon")) + { + itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_MAINHAND, spellInfo); + if (itemForSpell && itemForSpell->GetProto()->Class == ITEM_CLASS_WEAPON) + { + return itemForSpell; + } + + itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_OFFHAND, spellInfo); + if (itemForSpell && itemForSpell->GetProto()->Class == ITEM_CLASS_WEAPON) + { + return itemForSpell; + } + + return NULL; + } + + for( uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++ ) { + { + itemForSpell = GetItemFitsToSpellRequirements(slot, spellInfo); + } + if (itemForSpell) + { + return itemForSpell; + } + } + return NULL; +} + +Item* ItemForSpellValue::GetItemFitsToSpellRequirements(uint8 slot, SpellEntry const *spellInfo) +{ + Item* const itemForSpell = bot->GetItemByPos( INVENTORY_SLOT_BAG_0, slot ); + if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + return NULL; + } + + if (itemForSpell->IsFitToSpellRequirements(spellInfo)) + { + return itemForSpell; + } + + return NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.h b/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.h new file mode 100644 index 000000000..5c4792abd --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemForSpellValue.h @@ -0,0 +1,20 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class ItemForSpellValue : public CalculatedValue, public Qualified + { + public: + ItemForSpellValue(PlayerbotAI* ai) : CalculatedValue(ai) {} + + public: + virtual Item* Calculate(); + + private: + Item* GetItemFitsToSpellRequirements(uint8 slot, SpellEntry const *spellInfo); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.cpp b/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.cpp new file mode 100644 index 000000000..2a240751e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.cpp @@ -0,0 +1,194 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ItemUsageValue.h" + +#include "../../../ahbot/AhBot.h" +#include "../../GuildTaskMgr.h" +using namespace ai; + +ItemUsage ItemUsageValue::Calculate() +{ + uint32 itemId = atoi(qualifier.c_str()); + if (!itemId) + { + return ITEM_USAGE_NONE; + } + + const ItemPrototype* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto) + { + return ITEM_USAGE_NONE; + } + + if (IsItemUsefulForSkill(proto)) + { + return ITEM_USAGE_SKILL; + } + + switch (proto->Class) + { + case ITEM_CLASS_KEY: + case ITEM_CLASS_CONSUMABLE: + return ITEM_USAGE_USE; + } + + if (bot->GetGuildId() && sGuildTaskMgr.IsGuildTaskItem(itemId, bot->GetGuildId())) + { + return ITEM_USAGE_GUILD_TASK; + } + + ItemUsage equip = QueryItemUsageForEquip(proto); + if (equip != ITEM_USAGE_NONE) + { + return equip; + } + + if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) && proto->Bonding != BIND_WHEN_PICKED_UP && + ai->HasSkill(SKILL_ENCHANTING) && proto->Quality >= ITEM_QUALITY_UNCOMMON) + return ITEM_USAGE_DISENCHANT; + + return ITEM_USAGE_NONE; +} + +ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemPrototype const * item) +{ + if (bot->CanUseItem(item) != EQUIP_ERR_OK) + { + return ITEM_USAGE_NONE; + } + + if (item->InventoryType == INVTYPE_NON_EQUIP) + { + return ITEM_USAGE_NONE; + } + + Item *pItem = Item::CreateItem(item->ItemId, 1, bot); + if (!pItem) + { + return ITEM_USAGE_NONE; + } + + uint16 dest; + InventoryResult result = bot->CanEquipItem(NULL_SLOT, dest, pItem, true, false); + pItem->RemoveFromUpdateQueueOf(bot); + delete pItem; + + if( result != EQUIP_ERR_OK ) + { + return ITEM_USAGE_NONE; + } + + Item* existingItem = bot->GetItemByPos(dest); + if (!existingItem) + { + return ITEM_USAGE_EQUIP; + } + + const ItemPrototype* oldItem = existingItem->GetProto(); + if (oldItem->ItemLevel < item->ItemLevel && oldItem->ItemId != item->ItemId) + { + switch (item->Class) + { + case ITEM_CLASS_ARMOR: + if (oldItem->SubClass <= item->SubClass) { + { + return ITEM_USAGE_REPLACE; + } + } + break; + default: + return ITEM_USAGE_EQUIP; + } + } + + return ITEM_USAGE_NONE; +} + +bool ItemUsageValue::IsItemUsefulForSkill(ItemPrototype const * proto) +{ + switch (proto->Class) + { + case ITEM_CLASS_TRADE_GOODS: + case ITEM_CLASS_MISC: + case ITEM_CLASS_REAGENT: + { + if (ai->HasSkill(SKILL_TAILORING) && auctionbot.IsUsedBySkill(proto, SKILL_TAILORING)) + { + return true; + } + if (ai->HasSkill(SKILL_LEATHERWORKING) && auctionbot.IsUsedBySkill(proto, SKILL_LEATHERWORKING)) + { + return true; + } + if (ai->HasSkill(SKILL_ENGINEERING) && auctionbot.IsUsedBySkill(proto, SKILL_ENGINEERING)) + { + return true; + } + if (ai->HasSkill(SKILL_BLACKSMITHING) && auctionbot.IsUsedBySkill(proto, SKILL_BLACKSMITHING)) + { + return true; + } + if (ai->HasSkill(SKILL_ALCHEMY) && auctionbot.IsUsedBySkill(proto, SKILL_ALCHEMY)) + { + return true; + } + if (ai->HasSkill(SKILL_ENCHANTING) && auctionbot.IsUsedBySkill(proto, SKILL_ENCHANTING)) + { + return true; + } + if (ai->HasSkill(SKILL_FISHING) && auctionbot.IsUsedBySkill(proto, SKILL_FISHING)) + { + return true; + } + if (ai->HasSkill(SKILL_FIRST_AID) && auctionbot.IsUsedBySkill(proto, SKILL_FIRST_AID)) + { + return true; + } + if (ai->HasSkill(SKILL_COOKING) && auctionbot.IsUsedBySkill(proto, SKILL_COOKING)) + { + return true; + } + if (ai->HasSkill(SKILL_MINING) && + (auctionbot.IsUsedBySkill(proto, SKILL_MINING) || auctionbot.IsUsedBySkill(proto, SKILL_BLACKSMITHING) || auctionbot.IsUsedBySkill(proto, SKILL_ENGINEERING))) + return true; + if (ai->HasSkill(SKILL_SKINNING) && + (auctionbot.IsUsedBySkill(proto, SKILL_SKINNING) || auctionbot.IsUsedBySkill(proto, SKILL_LEATHERWORKING))) + return true; + if (ai->HasSkill(SKILL_HERBALISM) && + (auctionbot.IsUsedBySkill(proto, SKILL_HERBALISM) || auctionbot.IsUsedBySkill(proto, SKILL_ALCHEMY))) + return true; + + return false; + } + case ITEM_CLASS_RECIPE: + { + if (bot->HasSpell(proto->Spells[2].SpellId)) + { + break; + } + + switch (proto->SubClass) + { + case ITEM_SUBCLASS_LEATHERWORKING_PATTERN: + return ai->HasSkill(SKILL_LEATHERWORKING); + case ITEM_SUBCLASS_TAILORING_PATTERN: + return ai->HasSkill(SKILL_TAILORING); + case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC: + return ai->HasSkill(SKILL_ENGINEERING); + case ITEM_SUBCLASS_BLACKSMITHING: + return ai->HasSkill(SKILL_BLACKSMITHING); + case ITEM_SUBCLASS_COOKING_RECIPE: + return ai->HasSkill(SKILL_COOKING); + case ITEM_SUBCLASS_ALCHEMY_RECIPE: + return ai->HasSkill(SKILL_ALCHEMY); + case ITEM_SUBCLASS_FIRST_AID_MANUAL: + return ai->HasSkill(SKILL_FIRST_AID); + case ITEM_SUBCLASS_ENCHANTING_FORMULA: + return ai->HasSkill(SKILL_ENCHANTING); + case ITEM_SUBCLASS_FISHING_MANUAL: + return ai->HasSkill(SKILL_FISHING); + } + } + } + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.h b/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.h new file mode 100644 index 000000000..db7493802 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ItemUsageValue.h @@ -0,0 +1,29 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + enum ItemUsage + { + ITEM_USAGE_NONE = 0, + ITEM_USAGE_EQUIP = 1, + ITEM_USAGE_REPLACE = 2, + ITEM_USAGE_SKILL = 3, + ITEM_USAGE_USE = 4, + ITEM_USAGE_GUILD_TASK = 5, + ITEM_USAGE_DISENCHANT = 6 + }; + + class ItemUsageValue : public CalculatedValue, public Qualified + { + public: + ItemUsageValue(PlayerbotAI* ai) : CalculatedValue(ai) {} + + public: + virtual ItemUsage Calculate(); + + private: + ItemUsage QueryItemUsageForEquip(ItemPrototype const * proto); + bool IsItemUsefulForSkill(ItemPrototype const * proto); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LastMovementValue.h b/src/modules/Bots/playerbot/strategy/values/LastMovementValue.h new file mode 100644 index 000000000..8adcb09f0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LastMovementValue.h @@ -0,0 +1,61 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LastMovement + { + public: + LastMovement() + { + lastMoveToX = 0; + lastMoveToY = 0; + lastMoveToZ = 0; + lastMoveToOri = 0; + lastFollow = NULL; + } + + LastMovement(LastMovement& other) + { + taxiNodes = other.taxiNodes; + taxiMaster = other.taxiMaster; + lastFollow = other.lastFollow; + lastAreaTrigger = other.lastAreaTrigger; + lastMoveToX = other.lastMoveToX; + lastMoveToY = other.lastMoveToY; + lastMoveToZ = other.lastMoveToZ; + lastMoveToOri = other.lastMoveToOri; + } + + void Set(Unit* lastFollow) + { + Set(0.0f, 0.0f, 0.0f, 0.0f); + this->lastFollow = lastFollow; + } + + void Set(float x, float y, float z, float ori) + { + lastMoveToX = x; + lastMoveToY = y; + lastMoveToZ = z; + lastMoveToOri = ori; + lastFollow = NULL; + } + + public: + vector taxiNodes; + ObjectGuid taxiMaster; + Unit* lastFollow; + uint32 lastAreaTrigger; + float lastMoveToX, lastMoveToY, lastMoveToZ, lastMoveToOri; + }; + + class LastMovementValue : public ManualSetValue + { + public: + LastMovementValue(PlayerbotAI* ai) : ManualSetValue(ai, data) {} + + private: + LastMovement data; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LastSaidValue.h b/src/modules/Bots/playerbot/strategy/values/LastSaidValue.h new file mode 100644 index 000000000..bc96ed57c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LastSaidValue.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LastSaidValue : public ManualSetValue, public Qualified + { + public: + LastSaidValue(PlayerbotAI* ai) : ManualSetValue(ai, time(0) - 120, "last said") {} + }; + + class LastEmoteValue : public ManualSetValue, public Qualified + { + public: + LastEmoteValue(PlayerbotAI* ai) : ManualSetValue(ai, time(0) - 120, "last emote") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LastSpellCastTimeValue.h b/src/modules/Bots/playerbot/strategy/values/LastSpellCastTimeValue.h new file mode 100644 index 000000000..4906ee42b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LastSpellCastTimeValue.h @@ -0,0 +1,11 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LastSpellCastTimeValue : public ManualSetValue, public Qualified + { + public: + LastSpellCastTimeValue(PlayerbotAI* ai) : ManualSetValue(ai, 0), Qualified() {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LastSpellCastValue.h b/src/modules/Bots/playerbot/strategy/values/LastSpellCastValue.h new file mode 100644 index 000000000..2c4bcbc5f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LastSpellCastValue.h @@ -0,0 +1,39 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LastSpellCast + { + public: + LastSpellCast() : id(0),time(0) {} + + public: + void Set(uint32 id, ObjectGuid target, time_t time) + { + this->id = id; + this->target = target; + this->time = time; + } + + void Reset() + { + id = 0; + target.Set(0); + time = 0; + } + public: + uint32 id; + ObjectGuid target; + time_t time; + }; + + class LastSpellCastValue : public ManualSetValue + { + public: + LastSpellCastValue(PlayerbotAI* ai) : ManualSetValue(ai, data) {} + + private: + LastSpellCast data; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.cpp new file mode 100644 index 000000000..e112ab283 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.cpp @@ -0,0 +1,45 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LeastHpTargetValue.h" +#include "TargetValue.h" + +using namespace ai; +using namespace std; + +class FindLeastHpTargetStrategy : public FindTargetStrategy +{ +public: + explicit FindLeastHpTargetStrategy(PlayerbotAI* ai) : FindTargetStrategy(ai) + { + minHealth = 0; + } + +public: + virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager) + { + Group* group = ai->GetBot()->GetGroup(); + if (group) + { + uint64 guid = group->GetTargetIcon(4); + if (guid && attacker->GetObjectGuid() == ObjectGuid(guid)) + { + return; + } + } + + if (!result || result->GetHealth() > attacker->GetHealth()) + { + result = attacker; + } + } + +protected: + float minHealth; +}; + + +Unit* LeastHpTargetValue::Calculate() +{ + FindLeastHpTargetStrategy strategy(ai); + return FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.h b/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.h new file mode 100644 index 000000000..c0c6b5504 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LeastHpTargetValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + class LeastHpTargetValue : public TargetValue + { + public: + LeastHpTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LfgValues.h b/src/modules/Bots/playerbot/strategy/values/LfgValues.h new file mode 100644 index 000000000..290a4c430 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LfgValues.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../Value.h" + +namespace ai +{ +class LfgProposalValue : public ManualSetValue +{ +public: + LfgProposalValue(PlayerbotAI* ai) : ManualSetValue(ai, 0, "lfg proposal") {} +}; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LineTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/LineTargetValue.cpp new file mode 100644 index 000000000..fd9fb4044 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LineTargetValue.cpp @@ -0,0 +1,41 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LineTargetValue.h" + +using namespace ai; + +Unit* LineTargetValue::Calculate() +{ + Player* master = GetMaster(); + if (!master) + { + return NULL; + } + + Group* group = master->GetGroup(); + if (!group) + { + return NULL; + } + + Player *prev = master; + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *player = sObjectMgr.GetPlayer(itr->guid); + if( !player || !player->IsAlive() || player == master) + { + continue; + } + + if (player == bot) + { + return prev; + } + + prev = player; + } + + return master; +} + diff --git a/src/modules/Bots/playerbot/strategy/values/LineTargetValue.h b/src/modules/Bots/playerbot/strategy/values/LineTargetValue.h new file mode 100644 index 000000000..3fe5eeb2f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LineTargetValue.h @@ -0,0 +1,14 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LineTargetValue : public UnitCalculatedValue + { + public: + LineTargetValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + public: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LogLevelValue.h b/src/modules/Bots/playerbot/strategy/values/LogLevelValue.h new file mode 100644 index 000000000..8475fc12f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LogLevelValue.h @@ -0,0 +1,12 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class LogLevelValue : public ManualSetValue + { + public: + LogLevelValue(PlayerbotAI* ai) : + ManualSetValue(ai, LOG_LVL_DEBUG) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.cpp b/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.cpp new file mode 100644 index 000000000..5ec3891f7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "LootStrategyValue.h" +#include "../values/ItemUsageValue.h" + +using namespace ai; +using namespace std; + +namespace ai +{ + class NormalLootStrategy : public LootStrategy + { + public: + virtual bool CanLoot(ItemPrototype const *proto, AiObjectContext *context) + { + ostringstream out; out << proto->ItemId; + ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str()); + return usage != ITEM_USAGE_NONE && proto->Bonding != BIND_WHEN_PICKED_UP; + } + virtual string GetName() { return "normal"; } + }; + + class GrayLootStrategy : public NormalLootStrategy + { + public: + virtual bool CanLoot(ItemPrototype const *proto, AiObjectContext *context) + { + return NormalLootStrategy::CanLoot(proto, context) || proto->Quality == ITEM_QUALITY_POOR; + } + virtual string GetName() { return "gray"; } + }; + + class DisenchantLootStrategy : public NormalLootStrategy + { + public: + virtual bool CanLoot(ItemPrototype const *proto, AiObjectContext *context) + { + return NormalLootStrategy::CanLoot(proto, context) || + (proto->Quality >= ITEM_QUALITY_UNCOMMON && proto->Bonding != BIND_WHEN_PICKED_UP && + (proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON)); + } + virtual string GetName() { return "disenchant"; } + }; + + class AllLootStrategy : public LootStrategy + { + public: + virtual bool CanLoot(ItemPrototype const *proto, AiObjectContext *context) + { + return true; + } + virtual string GetName() { return "all"; } + }; +} + +LootStrategy *LootStrategyValue::normal = new NormalLootStrategy(); +LootStrategy *LootStrategyValue::gray = new GrayLootStrategy(); +LootStrategy *LootStrategyValue::disenchant = new DisenchantLootStrategy(); +LootStrategy *LootStrategyValue::all = new AllLootStrategy(); + +LootStrategy* LootStrategyValue::instance(string& strategy) +{ + if (strategy == "*" || strategy == "all") + { + return all; + } + + if (strategy == "g" || strategy == "gray") + { + return gray; + } + + if (strategy == "d" || strategy == "e" || strategy == "disenchant" || strategy == "enchant") + { + return disenchant; + } + + return normal; +} diff --git a/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.h b/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.h new file mode 100644 index 000000000..2267948e7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/LootStrategyValue.h @@ -0,0 +1,16 @@ +#pragma once +#include "../../LootObjectStack.h" +#include "../Value.h" + +namespace ai +{ + class LootStrategyValue : public ManualSetValue + { + public: + LootStrategyValue(PlayerbotAI* ai) : ManualSetValue(ai, normal) {} + virtual ~LootStrategyValue() { delete defaultValue; } + + static LootStrategy *normal, *gray, *all, *disenchant; + static LootStrategy* instance(string& name); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ManaSaveLevelValue.h b/src/modules/Bots/playerbot/strategy/values/ManaSaveLevelValue.h new file mode 100644 index 000000000..2c11b06b2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ManaSaveLevelValue.h @@ -0,0 +1,11 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class ManaSaveLevelValue : public ManualSetValue + { + public: + ManaSaveLevelValue(PlayerbotAI* ai) : ManualSetValue(ai, 1.0, "mana save level") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/MasterTargetValue.h b/src/modules/Bots/playerbot/strategy/values/MasterTargetValue.h new file mode 100644 index 000000000..49114a09c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/MasterTargetValue.h @@ -0,0 +1,13 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class MasterTargetValue : public UnitCalculatedValue + { + public: + MasterTargetValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + virtual Unit* Calculate() { return ai->GetMaster(); } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.cpp b/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.cpp new file mode 100644 index 000000000..a7562ea7e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.cpp @@ -0,0 +1,11 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestAdsValue.h" + +using namespace ai; + +bool NearestAdsValue::AcceptUnit(Unit* unit) +{ + Unit* target = AI_VALUE(Unit*, "current target"); + return unit != target; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.h b/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.h new file mode 100644 index 000000000..91687df8c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestAdsValue.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" +#include "PossibleTargetsValue.h" + +namespace ai +{ + class NearestAdsValue : public PossibleTargetsValue + { + public: + NearestAdsValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.tooCloseDistance) : + PossibleTargetsValue(ai, range) {} + + protected: + bool AcceptUnit(Unit* unit); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.cpp b/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.cpp new file mode 100644 index 000000000..52efcb677 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.cpp @@ -0,0 +1,36 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestCorpsesValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +class AnyDeadUnitInObjectRangeCheck +{ +public: + AnyDeadUnitInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(Unit* u) + { + return !u->IsAlive() && i_obj->IsWithinDistInMap(u, i_range); + } +private: + WorldObject const* i_obj; + float i_range; +}; + +void NearestCorpsesValue::FindUnits(list &targets) +{ + AnyDeadUnitInObjectRangeCheck u_check(bot, range); + UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool NearestCorpsesValue::AcceptUnit(Unit* unit) +{ + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.h b/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.h new file mode 100644 index 000000000..6e0f11342 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestCorpsesValue.h @@ -0,0 +1,19 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestCorpsesValue : public NearestUnitsValue + { + public: + NearestCorpsesValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + NearestUnitsValue(ai) {} + + protected: + void FindUnits(list &targets); + bool AcceptUnit(Unit* unit); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.cpp b/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.cpp new file mode 100644 index 000000000..c4257fcd8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestFriendlyPlayersValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +void NearestFriendlyPlayersValue::FindUnits(list &targets) +{ + AnyFriendlyUnitInObjectRangeCheck u_check(bot, range); + UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool NearestFriendlyPlayersValue::AcceptUnit(Unit* unit) +{ + ObjectGuid guid = unit->GetObjectGuid(); + return guid.IsPlayer() && guid != ai->GetBot()->GetObjectGuid(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.h b/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.h new file mode 100644 index 000000000..aa1c6e884 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestFriendlyPlayersValue.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestFriendlyPlayersValue : public NearestUnitsValue + { + public: + NearestFriendlyPlayersValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.spellDistance) : + NearestUnitsValue(ai) {} + + protected: + void FindUnits(list &targets); + bool AcceptUnit(Unit* unit); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.cpp b/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.cpp new file mode 100644 index 000000000..bd78d3341 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.cpp @@ -0,0 +1,51 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestGameObjects.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +class AnyGameObjectInObjectRangeCheck +{ +public: + AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {} + WorldObject const& GetFocusObject() const { return *i_obj; } + bool operator()(GameObject* u) + { + if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo()) + { + return true; + } + + return false; + } + +private: + WorldObject const* i_obj; + float i_range; +}; + +list NearestGameObjects::Calculate() +{ + list targets; + + AnyGameObjectInObjectRangeCheck u_check(bot, range); + GameObjectListSearcher searcher(targets, u_check); + Cell::VisitAllObjects((const WorldObject*)bot, searcher, range); + + list result; + for(list::iterator tIter = targets.begin(); tIter != targets.end(); ++tIter) + { + GameObject* go = *tIter; + if(bot->IsWithinLOSInMap(go)) + { + result.push_back(go->GetObjectGuid()); + } + } + + return result; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.h b/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.h new file mode 100644 index 000000000..f163d6e72 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestGameObjects.h @@ -0,0 +1,19 @@ +#pragma once +#include "../Value.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestGameObjects : public ObjectGuidListCalculatedValue + { + public: + NearestGameObjects(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + ObjectGuidListCalculatedValue(ai), range(range) {} + + protected: + virtual list Calculate(); + + private: + float range; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.cpp b/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.cpp new file mode 100644 index 000000000..c9880d813 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestNonBotPlayersValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +void NearestNonBotPlayersValue::FindUnits(list &targets) +{ + AnyUnitInObjectRangeCheck u_check(bot, range); + UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool NearestNonBotPlayersValue::AcceptUnit(Unit* unit) +{ + ObjectGuid guid = unit->GetObjectGuid(); + return guid.IsPlayer() && !(static_cast(unit)->GetPlayerbotAI()); +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.h b/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.h new file mode 100644 index 000000000..7077d3bf2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestNonBotPlayersValue.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestNonBotPlayersValue : public NearestUnitsValue + { + public: + NearestNonBotPlayersValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.reactDistance) : + NearestUnitsValue(ai) {} + + protected: + void FindUnits(list &targets); + bool AcceptUnit(Unit* unit); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.cpp b/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.cpp new file mode 100644 index 000000000..8a92288ed --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.cpp @@ -0,0 +1,22 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NearestNpcsValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +void NearestNpcsValue::FindUnits(list &targets) +{ + AnyFriendlyUnitInObjectRangeCheck u_check(bot, range); + UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool NearestNpcsValue::AcceptUnit(Unit* unit) +{ + return !dynamic_cast(unit); +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.h b/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.h new file mode 100644 index 000000000..4b56dd3c7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestNpcsValue.h @@ -0,0 +1,18 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestNpcsValue : public NearestUnitsValue + { + public: + NearestNpcsValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + NearestUnitsValue(ai) {} + + protected: + void FindUnits(list &targets); + bool AcceptUnit(Unit* unit); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NearestUnitsValue.h b/src/modules/Bots/playerbot/strategy/values/NearestUnitsValue.h new file mode 100644 index 000000000..9b194c31e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NearestUnitsValue.h @@ -0,0 +1,38 @@ +#pragma once +#include "../Value.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class NearestUnitsValue : public ObjectGuidListCalculatedValue + { + public: + NearestUnitsValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + ObjectGuidListCalculatedValue(ai, "nearest units", 2), range(range) {} + + public: + list Calculate() + { + list targets; + FindUnits(targets); + + list results; + for(list::iterator i = targets.begin(); i!= targets.end(); ++i) + { + Unit* unit = *i; + if(bot->IsWithinLOSInMap(unit) && AcceptUnit(unit)) + { + results.push_back(unit->GetObjectGuid()); + } + } + return results; + } + + protected: + virtual void FindUnits(list &targets) = 0; + virtual bool AcceptUnit(Unit* unit) = 0; + + protected: + float range; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.cpp b/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.cpp new file mode 100644 index 000000000..8fbbeb78e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.cpp @@ -0,0 +1,21 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "NewPlayerNearbyValue.h" + +using namespace ai; + +ObjectGuid NewPlayerNearbyValue::Calculate() +{ + list players = ai->GetAiObjectContext()->GetValue >("nearest friendly players")->Get(); + set& alreadySeenPlayers = ai->GetAiObjectContext()->GetValue& >("already seen players")->Get(); + for (list::iterator i = players.begin(); i != players.end(); ++i) + { + ObjectGuid guid = *i; + if (alreadySeenPlayers.find(guid) == alreadySeenPlayers.end()) + { + return guid; + } + } + + return ObjectGuid(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.h b/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.h new file mode 100644 index 000000000..291b9a21e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/NewPlayerNearbyValue.h @@ -0,0 +1,21 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class NewPlayerNearbyValue : public CalculatedValue + { + public: + NewPlayerNearbyValue(PlayerbotAI* ai) : CalculatedValue(ai, "new player nearby") {} + virtual ObjectGuid Calculate(); + }; + + class AlreadySeenPlayersValue : public ManualSetValue& > + { + public: + AlreadySeenPlayersValue(PlayerbotAI* ai) : ManualSetValue& >(ai, data, "already seen players") {} + + private: + set data; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/OutfitListValue.h b/src/modules/Bots/playerbot/strategy/values/OutfitListValue.h new file mode 100644 index 000000000..06d178fbc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/OutfitListValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + typedef list Outfit; + class OutfitListValue : public ManualSetValue + { + public: + OutfitListValue(PlayerbotAI* ai) : ManualSetValue(ai, list) {} + + private: + Outfit list; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.cpp b/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.cpp new file mode 100644 index 000000000..ddbb1357d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.cpp @@ -0,0 +1,35 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PartyMemberToDispel.h" + +using namespace ai; + +class PartyMemberToDispelPredicate : public FindPlayerPredicate, public PlayerbotAIAware +{ +public: + PartyMemberToDispelPredicate(PlayerbotAI* ai, uint32 dispelType) : + PlayerbotAIAware(ai), FindPlayerPredicate(), dispelType(dispelType) {} + +public: + virtual bool Check(Unit* unit) + { + Pet* pet = dynamic_cast(unit); + if (pet && (pet->getPetType() == MINI_PET || pet->getPetType() == SUMMON_PET)) + { + return false; + } + + return unit->IsAlive() && ai->HasAuraToDispel(unit, dispelType); + } + +private: + uint32 dispelType; +}; + +Unit* PartyMemberToDispel::Calculate() +{ + uint32 dispelType = atoi(qualifier.c_str()); + + PartyMemberToDispelPredicate predicate(ai, dispelType); + return FindPartyMember(predicate); +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.h b/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.h new file mode 100644 index 000000000..534459cbc --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToDispel.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "PartyMemberValue.h" + +namespace ai +{ + class PartyMemberToDispel : public PartyMemberValue, public Qualified + { + public: + PartyMemberToDispel(PlayerbotAI* ai) : + PartyMemberValue(ai), Qualified() {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.cpp b/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.cpp new file mode 100644 index 000000000..4cf8b324e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.cpp @@ -0,0 +1,66 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PartyMemberToHeal.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +class IsTargetOfHealingSpell : public SpellEntryPredicate +{ +public: + virtual bool Check(SpellEntry const* spell) { + for (int i=0; i<3; i++) { + if (spell->Effect[i] == SPELL_EFFECT_HEAL || + spell->Effect[i] == SPELL_EFFECT_HEAL_MAX_HEALTH || + spell->Effect[i] == SPELL_EFFECT_HEAL_MECHANICAL) + return true; + } + return false; + } + +}; + +Unit* PartyMemberToHeal::Calculate() +{ + + IsTargetOfHealingSpell predicate; + + Group* group = bot->GetGroup(); + if (!group) + { + return NULL; + } + + bool isRaid = bot->GetGroup()->isRaidGroup(); + MinValueCalculator calc(100); + for (GroupReference *gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if (!Check(player) || !player->IsAlive()) + { + continue; + } + + uint8 health = player->GetHealthPercent(); + if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate)) + { + calc.probe(health, player); + } + + Pet* pet = player->GetPet(); + if (pet && CanHealPet(pet)) + { + health = pet->GetHealthPercent(); + if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate)) + { + calc.probe(health, player); + } + } + } + return static_cast(calc.param); +} + +bool PartyMemberToHeal::CanHealPet(Pet* pet) +{ + return MINI_PET != pet->getPetType(); +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.h b/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.h new file mode 100644 index 000000000..114b1c45b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToHeal.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" +#include "PartyMemberValue.h" + +namespace ai +{ + class PartyMemberToHeal : public PartyMemberValue + { + public: + PartyMemberToHeal(PlayerbotAI* ai) : + PartyMemberValue(ai) {} + + protected: + virtual Unit* Calculate(); + bool CanHealPet(Pet* pet); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.cpp b/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.cpp new file mode 100644 index 000000000..b541aae7f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.cpp @@ -0,0 +1,44 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PartyMemberToResurrect.h" + +using namespace ai; + +class IsTargetOfResurrectSpell : public SpellEntryPredicate +{ +public: + virtual bool Check(SpellEntry const* spell) + { + for (int i=0; i<3; i++) + { + if (spell->Effect[i] == SPELL_EFFECT_RESURRECT || + spell->Effect[i] == SPELL_EFFECT_RESURRECT_NEW || + spell->Effect[i] == SPELL_EFFECT_SELF_RESURRECT) + return true; + } + return false; + } + +}; + +class FindDeadPlayer : public FindPlayerPredicate +{ +public: + explicit FindDeadPlayer(PartyMemberValue* value) : value(value) {} + + virtual bool Check(Unit* unit) + { + Player* player = dynamic_cast(unit); + return player && player->GetDeathState() == CORPSE && !value->IsTargetOfSpellCast(player, predicate); + } + +private: + PartyMemberValue* value; + IsTargetOfResurrectSpell predicate; +}; + +Unit* PartyMemberToResurrect::Calculate() +{ + FindDeadPlayer finder(this); + return FindPartyMember(finder); +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.h b/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.h new file mode 100644 index 000000000..d6da5a677 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberToResurrect.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "PartyMemberValue.h" + +namespace ai +{ + class PartyMemberToResurrect : public PartyMemberValue + { + public: + PartyMemberToResurrect(PlayerbotAI* ai) : + PartyMemberValue(ai) {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.cpp b/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.cpp new file mode 100644 index 000000000..55941834b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.cpp @@ -0,0 +1,139 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PartyMemberValue.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; +using namespace std; + +Unit* PartyMemberValue::FindPartyMember(list* party, FindPlayerPredicate &predicate) +{ + for (list::iterator i = party->begin(); i != party->end(); ++i) + { + Player* player = *i; + + if (!player) + { + continue; + } + + if (Check(player) && predicate.Check(player)) + { + return player; + } + + Pet* pet = player->GetPet(); + if (pet && Check(pet) && predicate.Check(pet)) + { + return pet; + } + } + + return NULL; +} + +Unit* PartyMemberValue::FindPartyMember(FindPlayerPredicate &predicate) +{ + Player* master = GetMaster(); + list nearestPlayers = AI_VALUE(list, "nearest friendly players"); + + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference *ref = group->GetFirstMember(); ref; ref = ref->next()) + { + if( ref->getSource() == bot) + { + continue; + } + + nearestPlayers.push_back(ref->getSource()->GetObjectGuid()); + } + } + + list healers, tanks, others, masters; + if (master) masters.push_back(master); + for (list::iterator i = nearestPlayers.begin(); i != nearestPlayers.end(); ++i) + { + Player* player = dynamic_cast(ai->GetUnit(*i)); + if (!player || player == bot) continue; + + if (ai->IsHeal(player)) + { + healers.push_back(player); + } + else if (ai->IsTank(player)) + { + tanks.push_back(player); + } + else if (player != master) + { + others.push_back(player); + } + } + + list* > lists; + lists.push_back(&healers); + lists.push_back(&tanks); + lists.push_back(&masters); + lists.push_back(&others); + + for (list* >::iterator i = lists.begin(); i != lists.end(); ++i) + { + list* party = *i; + Unit* target = FindPartyMember(party, predicate); + if (target) + { + return target; + } + } + + return NULL; +} + +bool PartyMemberValue::Check(Unit* player) +{ + return player && player != bot && player->GetMapId() == bot->GetMapId() && + bot->GetDistance(player) < sPlayerbotAIConfig.spellDistance && + bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()); +} + +bool PartyMemberValue::IsTargetOfSpellCast(Player* target, SpellEntryPredicate &predicate) +{ + list nearestPlayers = AI_VALUE(list, "nearest friendly players"); + ObjectGuid targetGuid = target ? target->GetObjectGuid() : bot->GetObjectGuid(); + ObjectGuid corpseGuid = target && target->GetCorpse() ? target->GetCorpse()->GetObjectGuid() : ObjectGuid(); + + for (list::iterator i = nearestPlayers.begin(); i != nearestPlayers.end(); ++i) + { + Player* player = dynamic_cast(ai->GetUnit(*i)); + if (!player || player == bot) + { + continue; + } + + if (player->IsNonMeleeSpellCasted(true)) + { + for (int type = CURRENT_GENERIC_SPELL; type < CURRENT_MAX_SPELL; type++) + { + Spell* spell = player->GetCurrentSpell((CurrentSpellTypes)type); + if (spell && predicate.Check(spell->m_spellInfo)) + { + ObjectGuid unitTarget = spell->m_targets.getUnitTargetGuid(); + if (unitTarget == targetGuid) + { + return true; + } + + ObjectGuid corpseTarget = spell->m_targets.getCorpseTargetGuid(); + if (corpseTarget == corpseGuid) + { + return true; + } + } + } + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.h b/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.h new file mode 100644 index 000000000..bf3121189 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberValue.h @@ -0,0 +1,31 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class FindPlayerPredicate + { + public: + virtual bool Check(Unit*) = 0; + }; + + class SpellEntryPredicate + { + public: + virtual bool Check(SpellEntry const*) = 0; + }; + + class PartyMemberValue : public UnitCalculatedValue + { + public: + PartyMemberValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + public: + bool IsTargetOfSpellCast(Player* target, SpellEntryPredicate &predicate); + + protected: + Unit* FindPartyMember(FindPlayerPredicate &predicate); + Unit* FindPartyMember(list* party, FindPlayerPredicate &predicate); + bool Check(Unit* player); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.cpp b/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.cpp new file mode 100644 index 000000000..13049d005 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.cpp @@ -0,0 +1,33 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PartyMemberWithoutAuraValue.h" + +using namespace ai; + +class PlayerWithoutAuraPredicate : public FindPlayerPredicate, public PlayerbotAIAware +{ +public: + PlayerWithoutAuraPredicate(PlayerbotAI* ai, string& aura) : + PlayerbotAIAware(ai), FindPlayerPredicate(), aura(aura) {} + +public: + virtual bool Check(Unit* unit) + { + Pet* pet = dynamic_cast(unit); + if (pet && (pet->getPetType() == MINI_PET || pet->getPetType() == SUMMON_PET)) + { + return false; + } + + return unit->IsAlive() && !ai->HasAura(aura, unit); + } + +private: + string aura; +}; + +Unit* PartyMemberWithoutAuraValue::Calculate() +{ + PlayerWithoutAuraPredicate predicate(ai, qualifier); + return FindPartyMember(predicate); +} diff --git a/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.h b/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.h new file mode 100644 index 000000000..ba2755fa1 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PartyMemberWithoutAuraValue.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" +#include "PartyMemberValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class PartyMemberWithoutAuraValue : public PartyMemberValue, public Qualified + { + public: + PartyMemberWithoutAuraValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + PartyMemberValue(ai) {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PetTargetValue.h b/src/modules/Bots/playerbot/strategy/values/PetTargetValue.h new file mode 100644 index 000000000..b84d665f8 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PetTargetValue.h @@ -0,0 +1,13 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class PetTargetValue : public UnitCalculatedValue + { + public: + PetTargetValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + virtual Unit* Calculate() { return (Unit*)(ai->GetBot()->GetPet()); } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PositionValue.cpp b/src/modules/Bots/playerbot/strategy/values/PositionValue.cpp new file mode 100644 index 000000000..46136b781 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PositionValue.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PositionValue.h" + +using namespace ai; + +PositionValue::PositionValue(PlayerbotAI* ai) + : ManualSetValue(ai, positions) +{ +} diff --git a/src/modules/Bots/playerbot/strategy/values/PositionValue.h b/src/modules/Bots/playerbot/strategy/values/PositionValue.h new file mode 100644 index 000000000..fb0e90dd7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PositionValue.h @@ -0,0 +1,29 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class Position + { + public: + Position() : valueSet(false), x(0), y(0), z(0) {} + Position(const Position &other) : valueSet(other.valueSet), x(other.x), y(other.y), z(other.z) {} + void Set(double x, double y, double z) { this->x = x; this->y = y; this->z = z; this->valueSet = true; } + void Reset() { valueSet = false; } + bool isSet() { return valueSet; } + + double x, y, z; + bool valueSet; + }; + + typedef map PositionMap; + + class PositionValue : public ManualSetValue + { + public: + PositionValue(PlayerbotAI* ai); + + private: + PositionMap positions; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.cpp b/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.cpp new file mode 100644 index 000000000..48538f91f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.cpp @@ -0,0 +1,23 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "PossibleTargetsValue.h" + +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" + +using namespace ai; +using namespace MaNGOS; + +void PossibleTargetsValue::FindUnits(list &targets) +{ + MaNGOS::AnyUnfriendlyUnitInObjectRangeCheck u_check(bot, range); + MaNGOS::UnitListSearcher searcher(targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool PossibleTargetsValue::AcceptUnit(Unit* unit) +{ + return !unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE) && + (unit->IsHostileTo(bot) || (unit->getLevel() > 1 && !unit->IsFriendlyTo(bot))); +} diff --git a/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.h b/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.h new file mode 100644 index 000000000..372db1698 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/PossibleTargetsValue.h @@ -0,0 +1,19 @@ +#pragma once +#include "../Value.h" +#include "NearestUnitsValue.h" +#include "../../PlayerbotAIConfig.h" + +namespace ai +{ + class PossibleTargetsValue : public NearestUnitsValue + { + public: + PossibleTargetsValue(PlayerbotAI* ai, float range = sPlayerbotAIConfig.sightDistance) : + NearestUnitsValue(ai) {} + + protected: + virtual void FindUnits(list &targets); + virtual bool AcceptUnit(Unit* unit); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/RandomBotUpdateValue.h b/src/modules/Bots/playerbot/strategy/values/RandomBotUpdateValue.h new file mode 100644 index 000000000..47366cc32 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/RandomBotUpdateValue.h @@ -0,0 +1,11 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class RandomBotUpdateValue : public ManualSetValue + { + public: + RandomBotUpdateValue(PlayerbotAI* ai) : ManualSetValue(ai, false) {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/RtiTargetValue.h b/src/modules/Bots/playerbot/strategy/values/RtiTargetValue.h new file mode 100644 index 000000000..59e36f122 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/RtiTargetValue.h @@ -0,0 +1,85 @@ +#pragma once +#include "../../PlayerbotAIConfig.h" +#include "../Value.h" +#include "Group.h" +#include "TargetValue.h" + +namespace ai +{ + class RtiTargetValue : public TargetValue + { + public: + RtiTargetValue(PlayerbotAI* ai) : TargetValue(ai) + {} + + public: + static int GetRtiIndex(string rti) + { + int index = -1; + if(rti == "star") + { + index = 0; + } + else if(rti == "circle") + { + index = 1; + } + else if(rti == "diamond") + { + index = 2; + } + else if(rti == "triangle") + { + index = 3; + } + else if(rti == "moon") + { + index = 4; + } + else if(rti == "square") + { + index = 5; + } + else if(rti == "cross") + { + index = 6; + } + else if(rti == "skull") + { + index = 7; + } + return index; + } + + Unit *Calculate() + { + Group *group = bot->GetGroup(); + if(!group) + { + return NULL; + } + + string rti = AI_VALUE(string, "rti"); + int index = GetRtiIndex(rti); + + if (index == -1) + { + return NULL; + } + + uint64 guid = group->GetTargetIcon(index); + if (!guid) + { + return NULL; + } + + Unit* unit = ai->GetUnit(ObjectGuid(guid)); + if (!unit || unit->IsDead() || bot->GetDistance2d(unit) >= sPlayerbotAIConfig.reactDistance) + { + return NULL; + } + + return unit; + } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/RtiValue.cpp b/src/modules/Bots/playerbot/strategy/values/RtiValue.cpp new file mode 100644 index 000000000..22251e82c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/RtiValue.cpp @@ -0,0 +1,10 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "RtiValue.h" + +using namespace ai; + +RtiValue::RtiValue(PlayerbotAI* ai) + : ManualSetValue(ai, "skull") +{ +} diff --git a/src/modules/Bots/playerbot/strategy/values/RtiValue.h b/src/modules/Bots/playerbot/strategy/values/RtiValue.h new file mode 100644 index 000000000..396e02096 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/RtiValue.h @@ -0,0 +1,11 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class RtiValue : public ManualSetValue + { + public: + RtiValue(PlayerbotAI* ai); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SelfTargetValue.h b/src/modules/Bots/playerbot/strategy/values/SelfTargetValue.h new file mode 100644 index 000000000..a562023d7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SelfTargetValue.h @@ -0,0 +1,13 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class SelfTargetValue : public UnitCalculatedValue + { + public: + SelfTargetValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + virtual Unit* Calculate() { return ai->GetBot(); } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SkipSpellsListValue.h b/src/modules/Bots/playerbot/strategy/values/SkipSpellsListValue.h new file mode 100644 index 000000000..7f44c96a5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SkipSpellsListValue.h @@ -0,0 +1,14 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class SkipSpellsListValue : public ManualSetValue&> + { + public: + SkipSpellsListValue(PlayerbotAI* ai) : ManualSetValue&>(ai, list) {} + + private: + set list; + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.cpp new file mode 100644 index 000000000..a08ecbb65 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.cpp @@ -0,0 +1,56 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SnareTargetValue.h" +#include "../../PlayerbotAIConfig.h" +#include "MotionGenerators/TargetedMovementGenerator.h" + +using namespace ai; + +Unit* SnareTargetValue::Calculate() +{ + string spell = qualifier; + + list attackers = ai->GetAiObjectContext()->GetValue >("attackers")->Get(); + Unit* target = ai->GetAiObjectContext()->GetValue("current target")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); ++i) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || unit == target) + { + continue; + } + + if (bot->GetDistance(unit) > sPlayerbotAIConfig.spellDistance) + { + continue; + } + + Unit* chaseTarget; + switch (unit->GetMotionMaster()->GetCurrentMovementGeneratorType()) + { + case FLEEING_MOTION_TYPE: + return unit; + case CHASE_MOTION_TYPE: + if (unit->GetTypeId() == TYPEID_PLAYER) + { + chaseTarget = static_cast const*>(unit->GetMotionMaster()->GetCurrent())->GetTarget(); + } + else + { + chaseTarget = static_cast const*>(unit->GetMotionMaster()->GetCurrent())->GetTarget(); + } + + if (!chaseTarget) + { + continue; + } + Player* chaseTargetPlayer = sObjectMgr.GetPlayer(chaseTarget->GetObjectGuid()); + if (chaseTargetPlayer && !ai->IsTank(chaseTargetPlayer)) + { + return unit; + } + } + } + + return NULL; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.h b/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.h new file mode 100644 index 000000000..f63ebf2e9 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SnareTargetValue.h @@ -0,0 +1,15 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class SnareTargetValue : public UnitCalculatedValue, public Qualified + { + public: + SnareTargetValue(PlayerbotAI* ai) : + UnitCalculatedValue(ai, "snare target") {} + + protected: + virtual Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.cpp b/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.cpp new file mode 100644 index 000000000..86945c63c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.cpp @@ -0,0 +1,90 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SpellCastUsefulValue.h" +#include "LastSpellCastValue.h" + +using namespace ai; + +bool SpellCastUsefulValue::Calculate() +{ + uint32 spellid = AI_VALUE2(uint32, "spell id", qualifier); + if (!spellid) + { + return true; // there can be known alternatives + } + + SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellid); + if (!spellInfo) + { + return true; // there can be known alternatives + } + + if (spellInfo->Attributes & SPELL_ATTR_ON_NEXT_SWING_1 || + spellInfo->Attributes & SPELL_ATTR_ON_NEXT_SWING_2) + { + Spell* spell = bot->GetCurrentSpell(CURRENT_MELEE_SPELL); + if (spell && spell->m_spellInfo->Id == spellid && spell->IsNextMeleeSwingSpell() && bot->hasUnitState(UNIT_STAT_MELEE_ATTACKING)) + { + return false; + } + } + else + { + uint32 lastSpellId = AI_VALUE(LastSpellCast&, "last spell cast").id; + if (spellid == lastSpellId) + { + Spell* const pSpell = bot->FindCurrentSpellBySpellId(lastSpellId); + if (pSpell) + { + return false; + } + } + } + + if (IsAutoRepeatRangedSpell(spellInfo) && bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL) && + bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)->m_spellInfo->Id == spellid) + { + return false; + } + + // TODO: workaround + if (qualifier == "windfury weapon" || qualifier == "flametongue weapon" || qualifier == "frostbrand weapon" || + qualifier == "rockbiter weapon" || qualifier == "earthliving weapon" || qualifier == "spellstone") + { + Item *item = AI_VALUE2(Item*, "item for spell", spellid); + if (item && item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) + { + return false; + } + } + + set& skipSpells = AI_VALUE(set&, "skip spells list"); + if (skipSpells.find(spellid) != skipSpells.end()) + { + return false; + } + + const string spellName = spellInfo->SpellName[0]; + for (set::iterator i = skipSpells.begin(); i != skipSpells.end(); ++i) + { + SpellEntry const *spell = sSpellStore.LookupEntry(*i); + if (!spell) + { + continue; + } + + wstring wnamepart; + if (!Utf8toWStr(spell->SpellName[0], wnamepart)) + { + continue; + } + + wstrToLower(wnamepart); + if (!spellName.empty() && spellName.length() == wnamepart.length() && Utf8FitTo(spellName, wnamepart)) + { + return false; + } + } + + return true; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.h b/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.h new file mode 100644 index 000000000..5454f0c5d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SpellCastUsefulValue.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class SpellCastUsefulValue : public BoolCalculatedValue, public Qualified + { + public: + SpellCastUsefulValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + public: + virtual bool Calculate(); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SpellIdValue.cpp b/src/modules/Bots/playerbot/strategy/values/SpellIdValue.cpp new file mode 100644 index 000000000..42252903b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SpellIdValue.cpp @@ -0,0 +1,137 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "SpellIdValue.h" +#include "../../PlayerbotAIConfig.h" + +using namespace ai; + +SpellIdValue::SpellIdValue(PlayerbotAI* ai) : + CalculatedValue(ai, "spell id") +{ +} + +uint32 SpellIdValue::Calculate() +{ + string namepart = qualifier; + ItemIds itemIds = ChatHelper::parseItems(namepart); + + PlayerbotChatHandler handler(bot); + uint32 extractedSpellId = handler.extractSpellId(namepart); + if (extractedSpellId) + { + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(extractedSpellId); + if (pSpellInfo) namepart = pSpellInfo->SpellName[0]; + } + + wstring wnamepart; + + if (!Utf8toWStr(namepart, wnamepart)) + { + return 0; + } + + wstrToLower(wnamepart); + char firstSymbol = tolower(namepart[0]); + int spellLength = wnamepart.length(); + + int loc = bot->GetSession()->GetSessionDbcLocale(); + + uint32 foundSpellId = 0; + bool foundMatchUsesNoReagents = false; + + for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr) + { + uint32 spellId = itr->first; + + if (itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId)) + { + continue; + } + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + if (pSpellInfo->Effect[0] == SPELL_EFFECT_LEARN_SPELL) + { + continue; + } + + bool useByItem = false; + for (int i = 0; i < 3; ++i) + { + if (pSpellInfo->Effect[i] == SPELL_EFFECT_CREATE_ITEM && itemIds.find(pSpellInfo->EffectItemType[i]) != itemIds.end()) + { + useByItem = true; + break; + } + } + + char* spellName = pSpellInfo->SpellName[loc]; + if (!useByItem && (tolower(spellName[0]) != firstSymbol || strlen(spellName) != spellLength || !Utf8FitTo(spellName, wnamepart))) + { + continue; + } + + bool usesNoReagents = (pSpellInfo->Reagent[0] <= 0); + + // if we already found a spell + bool useThisSpell = true; + if (foundSpellId > 0) { + if (usesNoReagents && !foundMatchUsesNoReagents) + { + + } + else if (spellId > foundSpellId) + { + + } + else + { + useThisSpell = false; + } + } + if (useThisSpell) { + { + foundSpellId = spellId; + } + foundMatchUsesNoReagents = usesNoReagents; + } + } + + Pet* pet = bot->GetPet(); + if (!foundSpellId && pet) + { + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if(itr->second.state == PETSPELL_REMOVED) + { + continue; + } + + uint32 spellId = itr->first; + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + { + continue; + } + + if (pSpellInfo->Effect[0] == SPELL_EFFECT_LEARN_SPELL) + { + continue; + } + + char* spellName = pSpellInfo->SpellName[loc]; + if (tolower(spellName[0]) != firstSymbol || strlen(spellName) != spellLength || !Utf8FitTo(spellName, wnamepart)) + { + continue; + } + + foundSpellId = spellId; + } + } + + return foundSpellId; +} diff --git a/src/modules/Bots/playerbot/strategy/values/SpellIdValue.h b/src/modules/Bots/playerbot/strategy/values/SpellIdValue.h new file mode 100644 index 000000000..06e88c56c --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/SpellIdValue.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class SpellIdValue : public CalculatedValue, public Qualified + { + public: + SpellIdValue(PlayerbotAI* ai); + + public: + virtual uint32 Calculate(); + + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/StatsValues.cpp b/src/modules/Bots/playerbot/strategy/values/StatsValues.cpp new file mode 100644 index 000000000..449e236bf --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/StatsValues.cpp @@ -0,0 +1,174 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "StatsValues.h" + +using namespace ai; + +uint8 HealthValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return 100; + } + return (static_cast (target->GetHealth()) / target->GetMaxHealth()) * 100; +} + +bool IsDeadValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return false; + } + return target->GetDeathState() != ALIVE; +} + +bool PetIsDeadValue::Calculate() +{ + if (!bot->GetPet()) + { + uint32 ownerid = bot->GetGUIDLow(); + QueryResult* result = CharacterDatabase.PQuery("SELECT id FROM character_pet WHERE owner = '%u'", ownerid); + if (!result) + { + return false; + } + + delete result; + return true; + } + if (bot->GetPetGuid() && !bot->GetPet()) + { + return true; + } + + return bot->GetPet() && bot->GetPet()->GetDeathState() != ALIVE; +} + +bool PetIsHappyValue::Calculate() +{ + /*PetDatabaseStatus status = Pet::GetStatusFromDB(bot); + if (status == PET_DB_DEAD) + { + return true; + }*/ + + return !bot->GetPet() || bot->GetPet()->GetHappinessState() == HAPPY; +} + + +uint8 RageValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return 0; + } + return (static_cast (target->GetPower(POWER_RAGE))); +} + +uint8 EnergyValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return 0; + } + return (static_cast (target->GetPower(POWER_ENERGY))); +} + +uint8 ManaValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return 100; + } + return (static_cast (target->GetPower(POWER_MANA)) / target->GetMaxPower(POWER_MANA)) * 100; +} + +bool HasManaValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return false; + } + return target->GetPower(POWER_MANA); +} + + +uint8 ComboPointsValue::Calculate() +{ + Unit *target = GetTarget(); + if (!target || target->GetObjectGuid() != bot->GetComboTargetGuid()) + { + return 0; + } + + return bot->GetComboPoints(); +} + +bool IsMountedValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return false; + } + + return target->IsMounted(); +} + + +bool IsInCombatValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return false; + } + + return target->IsInCombat(); +} + +uint8 BagSpaceValue::Calculate() +{ + uint32 totalused = 0, total = 16; + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) + { + if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + totalused++; + } + } + + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + const Bag* const pBag = (Bag*) bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); + if (pBag) + { + ItemPrototype const* pBagProto = pBag->GetProto(); + if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER) + { + total += pBag->GetBagSize(); + } + } + + } + + return (static_cast (totalused) / total) * 100; +} + +uint8 SpeedValue::Calculate() +{ + Unit* target = GetTarget(); + if (!target) + { + return 100; + } + + return (uint8) (100.0f * target->GetSpeedRate(MOVE_RUN)); +} + diff --git a/src/modules/Bots/playerbot/strategy/values/StatsValues.h b/src/modules/Bots/playerbot/strategy/values/StatsValues.h new file mode 100644 index 000000000..d1d5d3028 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/StatsValues.h @@ -0,0 +1,159 @@ +#pragma once +#include "../Value.h" + +class Unit; + +namespace ai +{ + class HealthValue : public Uint8CalculatedValue, public Qualified + { + public: + HealthValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class IsDeadValue : public BoolCalculatedValue, public Qualified + { + public: + IsDeadValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual bool Calculate(); + }; + + class PetIsDeadValue : public BoolCalculatedValue + { + public: + PetIsDeadValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + virtual bool Calculate(); + }; + + class PetIsHappyValue : public BoolCalculatedValue + { + public: + PetIsHappyValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + virtual bool Calculate(); + }; + + class RageValue : public Uint8CalculatedValue, public Qualified + { + public: + RageValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class EnergyValue : public Uint8CalculatedValue, public Qualified + { + public: + EnergyValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class ManaValue : public Uint8CalculatedValue, public Qualified + { + public: + ManaValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class HasManaValue : public BoolCalculatedValue, public Qualified + { + public: + HasManaValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual bool Calculate(); + }; + + class ComboPointsValue : public Uint8CalculatedValue, public Qualified + { + public: + ComboPointsValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; + + class IsMountedValue : public BoolCalculatedValue, public Qualified + { + public: + IsMountedValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual bool Calculate(); + }; + + class IsInCombatValue : public BoolCalculatedValue, public Qualified + { + public: + IsInCombatValue(PlayerbotAI* ai) : BoolCalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual bool Calculate() ; + }; + + class BagSpaceValue : public Uint8CalculatedValue + { + public: + BagSpaceValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + virtual uint8 Calculate(); + }; + + class SpeedValue : public Uint8CalculatedValue, public Qualified + { + public: + SpeedValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + Unit* GetTarget() + { + AiObjectContext* ctx = AiObject::context; + return ctx->GetValue(qualifier)->Get(); + } + virtual uint8 Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/TankTargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/TankTargetValue.cpp new file mode 100644 index 000000000..c48899f9a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/TankTargetValue.cpp @@ -0,0 +1,57 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TankTargetValue.h" + +using namespace ai; + +class FindTargetForTankStrategy : public FindTargetStrategy +{ +public: + explicit FindTargetForTankStrategy(PlayerbotAI* ai) : FindTargetStrategy(ai) + { + minThreat = 0; + minTankCount = 0; + maxDpsCount = 0; + } + +public: + virtual void CheckAttacker(Unit* creature, ThreatManager* threatManager) + { + Player* bot = ai->GetBot(); + Group* group = bot->GetGroup(); + if (group) + { + uint64 guid = group->GetTargetIcon(4); + if (guid && creature->GetObjectGuid() == ObjectGuid(guid)) + { + return; + } + } + + float threat = threatManager->getThreat(bot); + int tankCount, dpsCount; + GetPlayerCount(creature, &tankCount, &dpsCount); + + if (!result || + (minThreat >= threat && + (minTankCount >= tankCount || maxDpsCount <= dpsCount))) + { + minThreat = threat; + minTankCount = tankCount; + maxDpsCount = dpsCount; + result = creature; + } + } + +protected: + float minThreat; + int minTankCount; + int maxDpsCount; +}; + + +Unit* TankTargetValue::Calculate() +{ + FindTargetForTankStrategy strategy(ai); + return FindTarget(&strategy); +} diff --git a/src/modules/Bots/playerbot/strategy/values/TankTargetValue.h b/src/modules/Bots/playerbot/strategy/values/TankTargetValue.h new file mode 100644 index 000000000..2eaf57709 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/TankTargetValue.h @@ -0,0 +1,16 @@ +#pragma once +#include "../Value.h" +#include "TargetValue.h" + +namespace ai +{ + + class TankTargetValue : public TargetValue + { + public: + TankTargetValue(PlayerbotAI* ai) : TargetValue(ai) {} + + public: + Unit* Calculate(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/TargetValue.cpp b/src/modules/Bots/playerbot/strategy/values/TargetValue.cpp new file mode 100644 index 000000000..0fdc4a3c2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/TargetValue.cpp @@ -0,0 +1,66 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "TargetValue.h" +#include "Unit.h" + +using namespace ai; + +Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) +{ + list attackers = ai->GetAiObjectContext()->GetValue >("attackers")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); ++i) + { + Unit* unit = ai->GetUnit(*i); + if (!unit) + { + continue; + } + + ThreatManager &threatManager = unit->GetThreatManager(); + strategy->CheckAttacker(unit, &threatManager); + } + + return strategy->GetResult(); +} + +void FindTargetStrategy::GetPlayerCount(Unit* creature, int* tankCount, int* dpsCount) +{ + Player* bot = ai->GetBot(); + if (tankCountCache.find(creature) != tankCountCache.end()) + { + *tankCount = tankCountCache[creature]; + *dpsCount = dpsCountCache[creature]; + return; + } + + *tankCount = 0; + *dpsCount = 0; + + Unit::AttackerSet attackers(creature->getAttackers()); + for (set::const_iterator i = attackers.begin(); i != attackers.end(); i++) + { + Unit* attacker = *i; + if (!attacker || !attacker->IsAlive() || attacker == bot) + { + continue; + } + + Player* player = dynamic_cast(attacker); + if (!player) + { + continue; + } + + if (ai->IsTank(player)) + { + (*tankCount)++; + } + else + { + (*dpsCount)++; + } + } + + tankCountCache[creature] = *tankCount; + dpsCountCache[creature] = *dpsCount; +} diff --git a/src/modules/Bots/playerbot/strategy/values/TargetValue.h b/src/modules/Bots/playerbot/strategy/values/TargetValue.h new file mode 100644 index 000000000..f02c194b0 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/TargetValue.h @@ -0,0 +1,39 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class FindTargetStrategy + { + public: + FindTargetStrategy(PlayerbotAI* ai) + { + result = NULL; + this->ai = ai; + } + + public: + Unit* GetResult() { return result; } + + public: + virtual void CheckAttacker(Unit* attacker, ThreatManager* threatManager) = 0; + void GetPlayerCount(Unit* creature, int* tankCount, int* dpsCount); + + protected: + Unit* result; + PlayerbotAI* ai; + + protected: + map tankCountCache; + map dpsCountCache; + }; + + class TargetValue : public UnitCalculatedValue + { + public: + TargetValue(PlayerbotAI* ai) : UnitCalculatedValue(ai) {} + + protected: + Unit* FindTarget(FindTargetStrategy* strategy); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ThreatValues.cpp b/src/modules/Bots/playerbot/strategy/values/ThreatValues.cpp new file mode 100644 index 000000000..09ff3820e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ThreatValues.cpp @@ -0,0 +1,79 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "ThreatValues.h" +#include "ThreatManager.h" + +using namespace ai; + +uint8 ThreatValue::Calculate() +{ + if (qualifier == "aoe") + { + uint8 maxThreat = 0; + list attackers = context->GetValue >("attackers")->Get(); + for (list::iterator i = attackers.begin(); i != attackers.end(); i++) + { + Unit* unit = ai->GetUnit(*i); + if (!unit || !unit->IsAlive()) + { + continue; + } + + uint8 threat = Calculate(unit); + if (!maxThreat || threat > maxThreat) + { + maxThreat = threat; + } + } + + return maxThreat; + } + + Unit* target = AI_VALUE(Unit*, qualifier); + return Calculate(target); +} + +uint8 ThreatValue::Calculate(Unit* target) +{ + if (!target) + { + return 0; + } + + if (target->GetObjectGuid().IsPlayer()) + { + return 0; + } + + Group* group = bot->GetGroup(); + if (!group) + { + return 0; + } + + float botThreat = target->GetThreatManager().getThreat(bot); + float maxThreat = 0; + + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *player = sObjectMgr.GetPlayer(itr->guid); + if( !player || !player->IsAlive() || player == bot) + { + continue; + } + + float threat = target->GetThreatManager().getThreat(player); + if (maxThreat < threat) + { + maxThreat = threat; + } + } + + if (maxThreat <= 0) + { + return 0; + } + + return botThreat * 100 / maxThreat; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ThreatValues.h b/src/modules/Bots/playerbot/strategy/values/ThreatValues.h new file mode 100644 index 000000000..6ce721065 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ThreatValues.h @@ -0,0 +1,17 @@ +#pragma once +#include "../Value.h" + +namespace ai +{ + class ThreatValue : public Uint8CalculatedValue, public Qualified + { + public: + ThreatValue(PlayerbotAI* ai) : Uint8CalculatedValue(ai) {} + + public: + virtual uint8 Calculate(); + + protected: + uint8 Calculate(Unit* target); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/values/ValueContext.h b/src/modules/Bots/playerbot/strategy/values/ValueContext.h new file mode 100644 index 000000000..aa2cfae32 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/values/ValueContext.h @@ -0,0 +1,268 @@ +#pragma once + +#include "NearestGameObjects.h" +#include "LogLevelValue.h" +#include "NearestNpcsValue.h" +#include "PossibleTargetsValue.h" +#include "NearestAdsValue.h" +#include "NearestCorpsesValue.h" +#include "PartyMemberWithoutAuraValue.h" +#include "PartyMemberToHeal.h" +#include "PartyMemberToResurrect.h" +#include "CurrentTargetValue.h" +#include "SelfTargetValue.h" +#include "MasterTargetValue.h" +#include "LineTargetValue.h" +#include "TankTargetValue.h" +#include "DpsTargetValue.h" +#include "CcTargetValue.h" +#include "CurrentCcTargetValue.h" +#include "PetTargetValue.h" +#include "GrindTargetValue.h" +#include "RtiTargetValue.h" +#include "PartyMemberToDispel.h" +#include "StatsValues.h" +#include "AttackerCountValues.h" +#include "AttackersValue.h" +#include "AvailableLootValue.h" +#include "AlwaysLootListValue.h" +#include "LootStrategyValue.h" +#include "HasAvailableLootValue.h" +#include "LastMovementValue.h" +#include "DistanceValue.h" +#include "IsMovingValue.h" +#include "IsBehindValue.h" +#include "IsFacingValue.h" +#include "ItemCountValue.h" +#include "SpellIdValue.h" +#include "ItemForSpellValue.h" +#include "SpellCastUsefulValue.h" +#include "LastSpellCastValue.h" +#include "ChatValue.h" +#include "HasTotemValue.h" +#include "LeastHpTargetValue.h" +#include "AoeHealValues.h" +#include "AoeValues.h" +#include "RtiValue.h" +#include "PositionValue.h" +#include "ThreatValues.h" +#include "DuelTargetValue.h" +#include "InvalidTargetValue.h" +#include "EnemyPlayerValue.h" +#include "AttackerWithoutAuraTargetValue.h" +#include "LastSpellCastTimeValue.h" +#include "ManaSaveLevelValue.h" +#include "LfgValues.h" +#include "EnemyHealerTargetValue.h" +#include "Formations.h" +#include "ItemUsageValue.h" +#include "LastSaidValue.h" +#include "NearestFriendlyPlayersValue.h" +#include "NearestNonBotPlayersValue.h" +#include "NewPlayerNearbyValue.h" +#include "OutfitListValue.h" +#include "RandomBotUpdateValue.h" +#include "SkipSpellsListValue.h" +#include "SnareTargetValue.h" + +namespace ai +{ + class ValueContext : public NamedObjectContext + { + public: + ValueContext() + { + creators["skip spells list"] = &ValueContext::skip_spells_list_value; + creators["nearest game objects"] = &ValueContext::nearest_game_objects; + creators["nearest npcs"] = &ValueContext::nearest_npcs; + creators["nearest friendly players"] = &ValueContext::nearest_friendly_players; + creators["possible targets"] = &ValueContext::possible_targets; + creators["nearest adds"] = &ValueContext::nearest_adds; + creators["nearest corpses"] = &ValueContext::nearest_corpses; + creators["log level"] = &ValueContext::log_level; + creators["party member without aura"] = &ValueContext::party_member_without_aura; + creators["attacker without aura"] = &ValueContext::attacker_without_aura; + creators["party member to heal"] = &ValueContext::party_member_to_heal; + creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect; + creators["current target"] = &ValueContext::current_target; + creators["self target"] = &ValueContext::self_target; + creators["master target"] = &ValueContext::master; + creators["line target"] = &ValueContext::line_target; + creators["tank target"] = &ValueContext::tank_target; + creators["dps target"] = &ValueContext::dps_target; + creators["least hp target"] = &ValueContext::least_hp_target; + creators["enemy player target"] = &ValueContext::enemy_player_target; + creators["cc target"] = &ValueContext::cc_target; + creators["current cc target"] = &ValueContext::current_cc_target; + creators["pet target"] = &ValueContext::pet_target; + creators["old target"] = &ValueContext::old_target; + creators["grind target"] = &ValueContext::grind_target; + creators["rti target"] = &ValueContext::rti_target; + creators["duel target"] = &ValueContext::duel_target; + creators["party member to dispel"] = &ValueContext::party_member_to_dispel; + creators["health"] = &ValueContext::health; + creators["rage"] = &ValueContext::rage; + creators["energy"] = &ValueContext::energy; + creators["mana"] = &ValueContext::mana; + creators["combo"] = &ValueContext::combo; + creators["dead"] = &ValueContext::dead; + creators["pet dead"] = &ValueContext::pet_dead; + creators["pet happy"] = &ValueContext::pet_happy; + creators["has mana"] = &ValueContext::has_mana; + creators["attacker count"] = &ValueContext::attacker_count; + creators["my attacker count"] = &ValueContext::my_attacker_count; + creators["has aggro"] = &ValueContext::has_aggro; + creators["mounted"] = &ValueContext::mounted; + + creators["can loot"] = &ValueContext::can_loot; + creators["loot target"] = &ValueContext::loot_target; + creators["available loot"] = &ValueContext::available_loot; + creators["has available loot"] = &ValueContext::has_available_loot; + creators["always loot list"] = &ValueContext::always_loot_list; + creators["loot strategy"] = &ValueContext::loot_strategy; + creators["last movement"] = &ValueContext::last_movement; + creators["last taxi"] = &ValueContext::last_movement; + creators["last area trigger"] = &ValueContext::last_movement; + creators["distance"] = &ValueContext::distance; + creators["moving"] = &ValueContext::moving; + creators["swimming"] = &ValueContext::swimming; + creators["behind"] = &ValueContext::behind; + creators["facing"] = &ValueContext::facing; + + creators["item count"] = &ValueContext::item_count; + creators["inventory items"] = &ValueContext::inventory_item; + + creators["spell id"] = &ValueContext::spell_id; + creators["item for spell"] = &ValueContext::item_for_spell; + creators["spell cast useful"] = &ValueContext::spell_cast_useful; + creators["last spell cast"] = &ValueContext::last_spell_cast; + creators["last spell cast time"] = &ValueContext::last_spell_cast_time; + creators["chat"] = &ValueContext::chat; + creators["has totem"] = &ValueContext::has_totem; + + creators["aoe heal"] = &ValueContext::aoe_heal; + + creators["rti"] = &ValueContext::rti; + creators["position"] = &ValueContext::position; + creators["threat"] = &ValueContext::threat; + + creators["balance"] = &ValueContext::balance; + creators["attackers"] = &ValueContext::attackers; + creators["invalid target"] = &ValueContext::invalid_target; + creators["mana save level"] = &ValueContext::mana_save_level; + creators["combat"] = &ValueContext::combat; + creators["lfg proposal"] = &ValueContext::lfg_proposal; + creators["bag space"] = &ValueContext::bag_space; + creators["enemy healer target"] = &ValueContext::enemy_healer_target; + creators["snare target"] = &ValueContext::snare_target; + creators["formation"] = &ValueContext::formation; + creators["item usage"] = &ValueContext::item_usage; + creators["speed"] = &ValueContext::speed; + creators["last said"] = &ValueContext::last_said; + creators["last emote"] = &ValueContext::last_emote; + + creators["aoe count"] = &ValueContext::aoe_count; + creators["aoe position"] = &ValueContext::aoe_position; + creators["outfit list"] = &ValueContext::outfit_list_value; + + creators["random bot update"] = &ValueContext::random_bot_update_value; + creators["nearest non bot players"] = &ValueContext::nearest_non_bot_players; + creators["new player nearby"] = &ValueContext::new_player_nearby; + creators["already seen players"] = &ValueContext::already_seen_players; + } + + private: + static UntypedValue* already_seen_players(PlayerbotAI* ai) { return new AlreadySeenPlayersValue(ai); } + static UntypedValue* new_player_nearby(PlayerbotAI* ai) { return new NewPlayerNearbyValue(ai); } + static UntypedValue* item_usage(PlayerbotAI* ai) { return new ItemUsageValue(ai); } + static UntypedValue* formation(PlayerbotAI* ai) { return new FormationValue(ai); } + static UntypedValue* mana_save_level(PlayerbotAI* ai) { return new ManaSaveLevelValue(ai); } + static UntypedValue* invalid_target(PlayerbotAI* ai) { return new InvalidTargetValue(ai); } + static UntypedValue* balance(PlayerbotAI* ai) { return new BalancePercentValue(ai); } + static UntypedValue* attackers(PlayerbotAI* ai) { return new AttackersValue(ai); } + + static UntypedValue* position(PlayerbotAI* ai) { return new PositionValue(ai); } + static UntypedValue* rti(PlayerbotAI* ai) { return new RtiValue(ai); } + + static UntypedValue* aoe_heal(PlayerbotAI* ai) { return new AoeHealValue(ai); } + + static UntypedValue* chat(PlayerbotAI* ai) { return new ChatValue(ai); } + static UntypedValue* last_spell_cast(PlayerbotAI* ai) { return new LastSpellCastValue(ai); } + static UntypedValue* last_spell_cast_time(PlayerbotAI* ai) { return new LastSpellCastTimeValue(ai); } + static UntypedValue* spell_cast_useful(PlayerbotAI* ai) { return new SpellCastUsefulValue(ai); } + static UntypedValue* item_for_spell(PlayerbotAI* ai) { return new ItemForSpellValue(ai); } + static UntypedValue* spell_id(PlayerbotAI* ai) { return new SpellIdValue(ai); } + static UntypedValue* inventory_item(PlayerbotAI* ai) { return new InventoryItemValue(ai); } + static UntypedValue* item_count(PlayerbotAI* ai) { return new ItemCountValue(ai); } + static UntypedValue* behind(PlayerbotAI* ai) { return new IsBehindValue(ai); } + static UntypedValue* facing(PlayerbotAI* ai) { return new IsFacingValue(ai); } + static UntypedValue* moving(PlayerbotAI* ai) { return new IsMovingValue(ai); } + static UntypedValue* swimming(PlayerbotAI* ai) { return new IsSwimmingValue(ai); } + static UntypedValue* distance(PlayerbotAI* ai) { return new DistanceValue(ai); } + static UntypedValue* last_movement(PlayerbotAI* ai) { return new LastMovementValue(ai); } + + static UntypedValue* can_loot(PlayerbotAI* ai) { return new CanLootValue(ai); } + static UntypedValue* available_loot(PlayerbotAI* ai) { return new AvailableLootValue(ai); } + static UntypedValue* loot_target(PlayerbotAI* ai) { return new LootTargetValue(ai); } + static UntypedValue* has_available_loot(PlayerbotAI* ai) { return new HasAvailableLootValue(ai); } + static UntypedValue* always_loot_list(PlayerbotAI* ai) { return new AlwaysLootListValue(ai); } + static UntypedValue* loot_strategy(PlayerbotAI* ai) { return new LootStrategyValue(ai); } + + static UntypedValue* attacker_count(PlayerbotAI* ai) { return new AttackerCountValue(ai); } + static UntypedValue* my_attacker_count(PlayerbotAI* ai) { return new MyAttackerCountValue(ai); } + static UntypedValue* has_aggro(PlayerbotAI* ai) { return new HasAggroValue(ai); } + static UntypedValue* mounted(PlayerbotAI* ai) { return new IsMountedValue(ai); } + static UntypedValue* health(PlayerbotAI* ai) { return new HealthValue(ai); } + static UntypedValue* rage(PlayerbotAI* ai) { return new RageValue(ai); } + static UntypedValue* energy(PlayerbotAI* ai) { return new EnergyValue(ai); } + static UntypedValue* mana(PlayerbotAI* ai) { return new ManaValue(ai); } + static UntypedValue* combo(PlayerbotAI* ai) { return new ComboPointsValue(ai); } + static UntypedValue* dead(PlayerbotAI* ai) { return new IsDeadValue(ai); } + static UntypedValue* pet_happy(PlayerbotAI* ai) { return new PetIsHappyValue(ai); } + static UntypedValue* pet_dead(PlayerbotAI* ai) { return new PetIsDeadValue(ai); } + static UntypedValue* has_mana(PlayerbotAI* ai) { return new HasManaValue(ai); } + static UntypedValue* nearest_game_objects(PlayerbotAI* ai) { return new NearestGameObjects(ai); } + static UntypedValue* log_level(PlayerbotAI* ai) { return new LogLevelValue(ai); } + static UntypedValue* nearest_npcs(PlayerbotAI* ai) { return new NearestNpcsValue(ai); } + static UntypedValue* nearest_friendly_players(PlayerbotAI* ai) { return new NearestFriendlyPlayersValue(ai); } + static UntypedValue* nearest_corpses(PlayerbotAI* ai) { return new NearestCorpsesValue(ai); } + static UntypedValue* possible_targets(PlayerbotAI* ai) { return new PossibleTargetsValue(ai); } + static UntypedValue* nearest_adds(PlayerbotAI* ai) { return new NearestAdsValue(ai); } + static UntypedValue* party_member_without_aura(PlayerbotAI* ai) { return new PartyMemberWithoutAuraValue(ai); } + static UntypedValue* attacker_without_aura(PlayerbotAI* ai) { return new AttackerWithoutAuraTargetValue(ai); } + static UntypedValue* party_member_to_heal(PlayerbotAI* ai) { return new PartyMemberToHeal(ai); } + static UntypedValue* party_member_to_resurrect(PlayerbotAI* ai) { return new PartyMemberToResurrect(ai); } + static UntypedValue* party_member_to_dispel(PlayerbotAI* ai) { return new PartyMemberToDispel(ai); } + static UntypedValue* current_target(PlayerbotAI* ai) { return new CurrentTargetValue(ai); } + static UntypedValue* old_target(PlayerbotAI* ai) { return new CurrentTargetValue(ai); } + static UntypedValue* self_target(PlayerbotAI* ai) { return new SelfTargetValue(ai); } + static UntypedValue* master(PlayerbotAI* ai) { return new MasterTargetValue(ai); } + static UntypedValue* line_target(PlayerbotAI* ai) { return new LineTargetValue(ai); } + static UntypedValue* tank_target(PlayerbotAI* ai) { return new TankTargetValue(ai); } + static UntypedValue* dps_target(PlayerbotAI* ai) { return new DpsTargetValue(ai); } + static UntypedValue* least_hp_target(PlayerbotAI* ai) { return new LeastHpTargetValue(ai); } + static UntypedValue* enemy_player_target(PlayerbotAI* ai) { return new EnemyPlayerValue(ai); } + static UntypedValue* cc_target(PlayerbotAI* ai) { return new CcTargetValue(ai); } + static UntypedValue* current_cc_target(PlayerbotAI* ai) { return new CurrentCcTargetValue(ai); } + static UntypedValue* pet_target(PlayerbotAI* ai) { return new PetTargetValue(ai); } + static UntypedValue* grind_target(PlayerbotAI* ai) { return new GrindTargetValue(ai); } + static UntypedValue* rti_target(PlayerbotAI* ai) { return new RtiTargetValue(ai); } + static UntypedValue* duel_target(PlayerbotAI* ai) { return new DuelTargetValue(ai); } + static UntypedValue* has_totem(PlayerbotAI* ai) { return new HasTotemValue(ai); } + static UntypedValue* threat(PlayerbotAI* ai) { return new ThreatValue(ai); } + static UntypedValue* combat(PlayerbotAI* ai) { return new IsInCombatValue(ai); } + static UntypedValue* lfg_proposal(PlayerbotAI* ai) { return new LfgProposalValue(ai); } + static UntypedValue* bag_space(PlayerbotAI* ai) { return new BagSpaceValue(ai); } + static UntypedValue* enemy_healer_target(PlayerbotAI* ai) { return new EnemyHealerTargetValue(ai); } + static UntypedValue* snare_target(PlayerbotAI* ai) { return new SnareTargetValue(ai); } + static UntypedValue* speed(PlayerbotAI* ai) { return new SpeedValue(ai); } + static UntypedValue* last_said(PlayerbotAI* ai) { return new LastSaidValue(ai); } + static UntypedValue* last_emote(PlayerbotAI* ai) { return new LastEmoteValue(ai); } + static UntypedValue* aoe_count(PlayerbotAI* ai) { return new AoeCountValue(ai); } + static UntypedValue* aoe_position(PlayerbotAI* ai) { return new AoePositionValue(ai); } + static UntypedValue* outfit_list_value(PlayerbotAI* ai) { return new OutfitListValue(ai); } + static UntypedValue* random_bot_update_value(PlayerbotAI* ai) { return new RandomBotUpdateValue(ai); } + static UntypedValue* nearest_non_bot_players(PlayerbotAI* ai) { return new NearestNonBotPlayersValue(ai); } + static UntypedValue* skip_spells_list_value(PlayerbotAI* ai) { return new SkipSpellsListValue(ai); } + }; +}; diff --git a/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.cpp b/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.cpp new file mode 100644 index 000000000..5651a27a5 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.cpp @@ -0,0 +1,76 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockTriggers.h" +#include "WarlockMultipliers.h" +#include "DpsWarlockStrategy.h" +#include "WarlockActions.h" + +using namespace ai; + +class DpsWarlockStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + DpsWarlockStrategyActionNodeFactory() + { + creators["shadow bolt"] = &shadow_bolt; + } +private: + static ActionNode* shadow_bolt(PlayerbotAI* ai) + { + return new ActionNode ("shadow bolt", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shoot"), NULL), + /*C*/ NULL); + } +}; + +DpsWarlockStrategy::DpsWarlockStrategy(PlayerbotAI* ai) : GenericWarlockStrategy(ai) +{ + actionNodeFactories.Add(new DpsWarlockStrategyActionNodeFactory()); +} + + +NextAction** DpsWarlockStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("incinirate", 10.0f), new NextAction("shadow bolt", 10.0f), NULL); +} + +void DpsWarlockStrategy::InitTriggers(std::list &triggers) +{ + GenericWarlockStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "shadow trance", + NextAction::array(0, new NextAction("shadow bolt", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "backlash", + NextAction::array(0, new NextAction("shadow bolt", 20.0f), NULL))); +} + +void DpsAoeWarlockStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "high aoe", + NextAction::array(0, new NextAction("rain of fire", 30.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("seed of corruption", 31.0f), NULL))); + + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("shadowfury", 29.0f), NULL))); + + triggers.push_back(new TriggerNode( + "corruption on attacker", + NextAction::array(0, new NextAction("corruption on attacker", 28.0f), NULL))); + +} + +void DpsWarlockDebuffStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "corruption", + NextAction::array(0, new NextAction("corruption", 12.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.h b/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.h new file mode 100644 index 000000000..846f9df36 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/DpsWarlockStrategy.h @@ -0,0 +1,39 @@ +#pragma once + +#include "GenericWarlockStrategy.h" +#include "../generic/CombatStrategy.h" + +namespace ai +{ + class DpsWarlockStrategy : public GenericWarlockStrategy + { + public: + DpsWarlockStrategy(PlayerbotAI* ai); + virtual string getName() { return "dps"; } + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + }; + + class DpsAoeWarlockStrategy : public CombatStrategy + { + public: + DpsAoeWarlockStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "aoe"; } + }; + + class DpsWarlockDebuffStrategy : public CombatStrategy + { + public: + DpsWarlockDebuffStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps debuff"; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.cpp new file mode 100644 index 000000000..0835ea416 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.cpp @@ -0,0 +1,65 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockMultipliers.h" +#include "GenericWarlockNonCombatStrategy.h" + +using namespace ai; + +class GenericWarlockNonCombatStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericWarlockNonCombatStrategyActionNodeFactory() + { + creators["fel armor"] = &fel_armor; + creators["demon armor"] = &demon_armor; + } +private: + static ActionNode* fel_armor(PlayerbotAI* ai) + { + return new ActionNode ("fel armor", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("demon armor"), NULL), + /*C*/ NULL); + } + static ActionNode* demon_armor(PlayerbotAI* ai) + { + return new ActionNode ("demon armor", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("demon skin"), NULL), + /*C*/ NULL); + } +}; + +GenericWarlockNonCombatStrategy::GenericWarlockNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericWarlockNonCombatStrategyActionNodeFactory()); +} + +void GenericWarlockNonCombatStrategy::InitTriggers(std::list &triggers) +{ + NonCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "demon armor", + NextAction::array(0, new NextAction("fel armor", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no healthstone", + NextAction::array(0, new NextAction("create healthstone", 15.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no firestone", + NextAction::array(0, new NextAction("create firestone", 14.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no spellstone", + NextAction::array(0, new NextAction("create spellstone", 13.0f), NULL))); + + triggers.push_back(new TriggerNode( + "spellstone", + NextAction::array(0, new NextAction("spellstone", 13.0f), NULL))); + + triggers.push_back(new TriggerNode( + "no pet", + NextAction::array(0, new NextAction("summon imp", 10.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.h new file mode 100644 index 000000000..c5e483f8a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockNonCombatStrategy.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericWarlockNonCombatStrategy : public NonCombatStrategy + { + public: + GenericWarlockNonCombatStrategy(PlayerbotAI* ai); + virtual string getName() { return "nc"; } + + public: + virtual void InitTriggers(std::list &triggers); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.cpp b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.cpp new file mode 100644 index 000000000..b498b5ae3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.cpp @@ -0,0 +1,74 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockMultipliers.h" +#include "GenericWarlockStrategy.h" + +using namespace ai; + +class GenericWarlockStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericWarlockStrategyActionNodeFactory() + { + creators["summon voidwalker"] = &summon_voidwalker; + creators["banish"] = &banish; + } +private: + static ActionNode* summon_voidwalker(PlayerbotAI* ai) + { + return new ActionNode ("summon voidwalker", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("drain soul"), NULL), + /*C*/ NULL); + } + static ActionNode* banish(PlayerbotAI* ai) + { + return new ActionNode ("banish", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("fear"), NULL), + /*C*/ NULL); + } +}; + +GenericWarlockStrategy::GenericWarlockStrategy(PlayerbotAI* ai) : RangedCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericWarlockStrategyActionNodeFactory()); +} + +NextAction** GenericWarlockStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("shoot", 10.0f), NULL); +} + +void GenericWarlockStrategy::InitTriggers(std::list &triggers) +{ + RangedCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "curse of agony", + NextAction::array(0, new NextAction("curse of agony", 11.0f), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("drain life", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "low mana", + NextAction::array(0, new NextAction("life tap", ACTION_EMERGENCY + 5), NULL))); + + triggers.push_back(new TriggerNode( + "target critical health", + NextAction::array(0, new NextAction("drain soul", 30.0f), NULL))); + + triggers.push_back(new TriggerNode( + "banish", + NextAction::array(0, new NextAction("banish", 21.0f), NULL))); + + triggers.push_back(new TriggerNode( + "fear", + NextAction::array(0, new NextAction("fear on cc", 20.0f), NULL))); + + triggers.push_back(new TriggerNode( + "immolate", + NextAction::array(0, new NextAction("immolate", 19.0f), new NextAction("conflagrate", 19.0f), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.h b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.h new file mode 100644 index 000000000..473da5903 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/GenericWarlockStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/RangedCombatStrategy.h" + +namespace ai +{ + class GenericWarlockStrategy : public RangedCombatStrategy + { + public: + GenericWarlockStrategy(PlayerbotAI* ai); + virtual string getName() { return "warlock"; } + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.cpp b/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.cpp new file mode 100644 index 000000000..f037d45eb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.cpp @@ -0,0 +1,51 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockMultipliers.h" +#include "TankWarlockStrategy.h" + +using namespace ai; + +class GenericWarlockStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericWarlockStrategyActionNodeFactory() + { + creators["summon voidwalker"] = &summon_voidwalker; + creators["summon felguard"] = &summon_felguard; + } +private: + static ActionNode* summon_voidwalker(PlayerbotAI* ai) + { + return new ActionNode ("summon voidwalker", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("drain soul"), NULL), + /*C*/ NULL); + } + static ActionNode* summon_felguard(PlayerbotAI* ai) + { + return new ActionNode ("summon felguard", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("summon voidwalker"), NULL), + /*C*/ NULL); + } +}; + +TankWarlockStrategy::TankWarlockStrategy(PlayerbotAI* ai) : GenericWarlockStrategy(ai) +{ + actionNodeFactories.Add(new GenericWarlockStrategyActionNodeFactory()); +} + +NextAction** TankWarlockStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("shoot", 10.0f), NULL); +} + +void TankWarlockStrategy::InitTriggers(std::list &triggers) +{ + GenericWarlockStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "no pet", + NextAction::array(0, new NextAction("summon felguard", 50.0f), NULL))); + +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.h b/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.h new file mode 100644 index 000000000..9cdd6e62f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/TankWarlockStrategy.h @@ -0,0 +1,17 @@ +#pragma once + +#include "GenericWarlockStrategy.h" + +namespace ai +{ + class TankWarlockStrategy : public GenericWarlockStrategy + { + public: + TankWarlockStrategy(PlayerbotAI* ai); + virtual string getName() { return "tank"; } + + public: + virtual void InitTriggers(std::list &triggers); + virtual NextAction** getDefaultActions(); + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.cpp b/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.cpp new file mode 100644 index 000000000..46e9370bb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.cpp @@ -0,0 +1,5 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "WarlockActions.h" + +using namespace ai; diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.h b/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.h new file mode 100644 index 000000000..5e8e38ed7 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockActions.h @@ -0,0 +1,176 @@ +#pragma once + +#include "../actions/GenericActions.h" + +namespace ai +{ + class CastDemonSkinAction : public CastBuffSpellAction { + public: + CastDemonSkinAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "demon skin") {} + }; + + class CastDemonArmorAction : public CastBuffSpellAction + { + public: + CastDemonArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "demon armor") {} + }; + + class CastFelArmorAction : public CastBuffSpellAction + { + public: + CastFelArmorAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "fel armor") {} + }; + + BEGIN_RANGED_SPELL_ACTION(CastShadowBoltAction, "shadow bolt") + END_SPELL_ACTION() + + class CastDrainSoulAction : public CastSpellAction + { + public: + CastDrainSoulAction(PlayerbotAI* ai) : CastSpellAction(ai, "drain soul") {} + virtual bool isUseful() + { + return AI_VALUE2(uint8, "item count", "soul shard") < 2; + } + }; + + class CastDrainManaAction : public CastSpellAction + { + public: + CastDrainManaAction(PlayerbotAI* ai) : CastSpellAction(ai, "drain mana") {} + }; + + class CastDrainLifeAction : public CastSpellAction + { + public: + CastDrainLifeAction(PlayerbotAI* ai) : CastSpellAction(ai, "drain life") {} + }; + + class CastCurseOfAgonyAction : public CastDebuffSpellAction + { + public: + CastCurseOfAgonyAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "curse of agony") {} + }; + + class CastCurseOfWeaknessAction : public CastDebuffSpellAction + { + public: + CastCurseOfWeaknessAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "curse of weakness") {} + }; + + class CastCorruptionAction : public CastDebuffSpellAction + { + public: + CastCorruptionAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "corruption") {} + }; + + class CastCorruptionOnAttackerAction : public CastDebuffSpellOnAttackerAction + { + public: + CastCorruptionOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "corruption") {} + }; + + + class CastSummonVoidwalkerAction : public CastBuffSpellAction + { + public: + CastSummonVoidwalkerAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "summon voidwalker") {} + }; + + class CastSummonFelguardAction : public CastBuffSpellAction + { + public: + CastSummonFelguardAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "summon felguard") {} + }; + + class CastSummonImpAction : public CastBuffSpellAction + { + public: + CastSummonImpAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "summon imp") {} + }; + + class CastCreateHealthstoneAction : public CastBuffSpellAction + { + public: + CastCreateHealthstoneAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "create healthstone") {} + }; + + class CastCreateFirestoneAction : public CastBuffSpellAction + { + public: + CastCreateFirestoneAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "create firestone") {} + }; + + class CastCreateSpellstoneAction : public CastBuffSpellAction + { + public: + CastCreateSpellstoneAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "create spellstone") {} + }; + + class CastBanishAction : public CastBuffSpellAction + { + public: + CastBanishAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "banish on cc") {} + virtual Value* GetTargetValue() { return context->GetValue("cc target", "banish"); } + virtual bool Execute(Event event) { return ai->CastSpell("banish", GetTarget()); } + }; + + class CastSeedOfCorruptionAction : public CastDebuffSpellAction + { + public: + CastSeedOfCorruptionAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "seed of corruption") {} + }; + + class CastRainOfFireAction : public CastSpellAction + { + public: + CastRainOfFireAction(PlayerbotAI* ai) : CastSpellAction(ai, "rain of fire") {} + }; + + class CastShadowfuryAction : public CastSpellAction + { + public: + CastShadowfuryAction(PlayerbotAI* ai) : CastSpellAction(ai, "shadowfury") {} + }; + + class CastImmolateAction : public CastDebuffSpellAction + { + public: + CastImmolateAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "immolate") {} + }; + + class CastConflagrateAction : public CastSpellAction + { + public: + CastConflagrateAction(PlayerbotAI* ai) : CastSpellAction(ai, "conflagrate") {} + }; + + class CastIncinirateAction : public CastSpellAction + { + public: + CastIncinirateAction(PlayerbotAI* ai) : CastSpellAction(ai, "incinirate") {} + }; + + class CastFearAction : public CastDebuffSpellAction + { + public: + CastFearAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "fear") {} + }; + + class CastFearOnCcAction : public CastBuffSpellAction + { + public: + CastFearOnCcAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "fear on cc") {} + virtual Value* GetTargetValue() { return context->GetValue("cc target", "fear"); } + virtual bool Execute(Event event) { return ai->CastSpell("fear", GetTarget()); } + }; + + class CastLifeTapAction: public CastSpellAction + { + public: + CastLifeTapAction(PlayerbotAI* ai) : CastSpellAction(ai, "life tap") {} + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.lowHealth; } + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.cpp new file mode 100644 index 000000000..f004eca68 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.cpp @@ -0,0 +1,183 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockActions.h" +#include "WarlockAiObjectContext.h" +#include "DpsWarlockStrategy.h" +#include "GenericWarlockNonCombatStrategy.h" +#include "TankWarlockStrategy.h" +#include "../generic/PullStrategy.h" +#include "WarlockTriggers.h" +#include "../NamedObjectContext.h" +#include "../actions/UseItemAction.h" + +using namespace ai; + +namespace ai +{ + namespace warlock + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &warlock::StrategyFactoryInternal::nc; + creators["pull"] = &warlock::StrategyFactoryInternal::pull; + creators["aoe"] = &warlock::StrategyFactoryInternal::aoe; + creators["dps debuff"] = &warlock::StrategyFactoryInternal::dps_debuff; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new GenericWarlockNonCombatStrategy(ai); } + static Strategy* aoe(PlayerbotAI* ai) { return new DpsAoeWarlockStrategy(ai); } + static Strategy* dps_debuff(PlayerbotAI* ai) { return new DpsWarlockDebuffStrategy(ai); } + static Strategy* pull(PlayerbotAI* ai) { return new PullStrategy(ai, "shoot"); } + }; + + class CombatStrategyFactoryInternal : public NamedObjectContext + { + public: + CombatStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["dps"] = &warlock::CombatStrategyFactoryInternal::dps; + creators["tank"] = &warlock::CombatStrategyFactoryInternal::tank; + } + + private: + static Strategy* tank(PlayerbotAI* ai) { return new TankWarlockStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new DpsWarlockStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace warlock + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["shadow trance"] = &TriggerFactoryInternal::shadow_trance; + creators["demon armor"] = &TriggerFactoryInternal::demon_armor; + creators["no healthstone"] = &TriggerFactoryInternal::HasHealthstone; + creators["no firestone"] = &TriggerFactoryInternal::HasFirestone; + creators["no spellstone"] = &TriggerFactoryInternal::HasSpellstone; + creators["corruption"] = &TriggerFactoryInternal::corruption; + creators["corruption on attacker"] = &TriggerFactoryInternal::corruption_on_attacker; + creators["curse of agony"] = &TriggerFactoryInternal::curse_of_agony; + creators["banish"] = &TriggerFactoryInternal::banish; + creators["spellstone"] = &TriggerFactoryInternal::spellstone; + creators["backlash"] = &TriggerFactoryInternal::backlash; + creators["fear"] = &TriggerFactoryInternal::fear; + creators["immolate"] = &TriggerFactoryInternal::immolate; + + + } + + private: + static Trigger* shadow_trance(PlayerbotAI* ai) { return new ShadowTranceTrigger(ai); } + static Trigger* demon_armor(PlayerbotAI* ai) { return new DemonArmorTrigger(ai); } + static Trigger* HasHealthstone(PlayerbotAI* ai) { return new HasHealthstoneTrigger(ai); } + static Trigger* HasFirestone(PlayerbotAI* ai) { return new HasFirestoneTrigger(ai); } + static Trigger* HasSpellstone(PlayerbotAI* ai) { return new HasSpellstoneTrigger(ai); } + static Trigger* corruption(PlayerbotAI* ai) { return new CorruptionTrigger(ai); } + static Trigger* corruption_on_attacker(PlayerbotAI* ai) { return new CorruptionOnAttackerTrigger(ai); } + static Trigger* curse_of_agony(PlayerbotAI* ai) { return new CurseOfAgonyTrigger(ai); } + static Trigger* banish(PlayerbotAI* ai) { return new BanishTrigger(ai); } + static Trigger* spellstone(PlayerbotAI* ai) { return new SpellstoneTrigger(ai); } + static Trigger* backlash(PlayerbotAI* ai) { return new BacklashTrigger(ai); } + static Trigger* fear(PlayerbotAI* ai) { return new FearTrigger(ai); } + static Trigger* immolate(PlayerbotAI* ai) { return new ImmolateTrigger(ai); } + + }; + }; +}; + +namespace ai +{ + namespace warlock + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["summon imp"] = &AiObjectContextInternal::summon_imp; + creators["fel armor"] = &AiObjectContextInternal::fel_armor; + creators["demon armor"] = &AiObjectContextInternal::demon_armor; + creators["demon skin"] = &AiObjectContextInternal::demon_skin; + creators["create healthstone"] = &AiObjectContextInternal::create_healthstone; + creators["create firestone"] = &AiObjectContextInternal::create_firestone; + creators["create spellstone"] = &AiObjectContextInternal::create_spellstone; + creators["spellstone"] = &AiObjectContextInternal::spellstone; + creators["summon voidwalker"] = &AiObjectContextInternal::summon_voidwalker; + creators["summon felguard"] = &AiObjectContextInternal::summon_felguard; + creators["immolate"] = &AiObjectContextInternal::immolate; + creators["corruption"] = &AiObjectContextInternal::corruption; + creators["corruption on attacker"] = &AiObjectContextInternal::corruption_on_attacker; + creators["curse of agony"] = &AiObjectContextInternal::curse_of_agony; + creators["shadow bolt"] = &AiObjectContextInternal::shadow_bolt; + creators["drain soul"] = &AiObjectContextInternal::drain_soul; + creators["drain mana"] = &AiObjectContextInternal::drain_mana; + creators["drain life"] = &AiObjectContextInternal::drain_life; + creators["banish"] = &AiObjectContextInternal::banish; + creators["seed of corruption"] = &AiObjectContextInternal::seed_of_corruption; + creators["rain of fire"] = &AiObjectContextInternal::rain_of_fire; + creators["shadowfury"] = &AiObjectContextInternal::shadowfury; + creators["life tap"] = &AiObjectContextInternal::life_tap; + creators["fear"] = &AiObjectContextInternal::fear; + creators["fear on cc"] = &AiObjectContextInternal::fear_on_cc; + creators["incinirate"] = &AiObjectContextInternal::incinirate; + creators["conflagrate"] = &AiObjectContextInternal::conflagrate; + } + + private: + static Action* conflagrate(PlayerbotAI* ai) { return new CastConflagrateAction(ai); } + static Action* incinirate(PlayerbotAI* ai) { return new CastIncinirateAction(ai); } + static Action* fear_on_cc(PlayerbotAI* ai) { return new CastFearOnCcAction(ai); } + static Action* fear(PlayerbotAI* ai) { return new CastFearAction(ai); } + static Action* immolate(PlayerbotAI* ai) { return new CastImmolateAction(ai); } + static Action* summon_imp(PlayerbotAI* ai) { return new CastSummonImpAction(ai); } + static Action* fel_armor(PlayerbotAI* ai) { return new CastFelArmorAction(ai); } + static Action* demon_armor(PlayerbotAI* ai) { return new CastDemonArmorAction(ai); } + static Action* demon_skin(PlayerbotAI* ai) { return new CastDemonSkinAction(ai); } + static Action* create_healthstone(PlayerbotAI* ai) { return new CastCreateHealthstoneAction(ai); } + static Action* create_firestone(PlayerbotAI* ai) { return new CastCreateFirestoneAction(ai); } + static Action* create_spellstone(PlayerbotAI* ai) { return new CastCreateSpellstoneAction(ai); } + static Action* spellstone(PlayerbotAI* ai) { return new UseSpellItemAction(ai, "spellstone", true); } + static Action* summon_voidwalker(PlayerbotAI* ai) { return new CastSummonVoidwalkerAction(ai); } + static Action* summon_felguard(PlayerbotAI* ai) { return new CastSummonFelguardAction(ai); } + static Action* corruption(PlayerbotAI* ai) { return new CastCorruptionAction(ai); } + static Action* corruption_on_attacker(PlayerbotAI* ai) { return new CastCorruptionOnAttackerAction(ai); } + static Action* curse_of_agony(PlayerbotAI* ai) { return new CastCurseOfAgonyAction(ai); } + static Action* shadow_bolt(PlayerbotAI* ai) { return new CastShadowBoltAction(ai); } + static Action* drain_soul(PlayerbotAI* ai) { return new CastDrainSoulAction(ai); } + static Action* drain_mana(PlayerbotAI* ai) { return new CastDrainManaAction(ai); } + static Action* drain_life(PlayerbotAI* ai) { return new CastDrainLifeAction(ai); } + static Action* banish(PlayerbotAI* ai) { return new CastBanishAction(ai); } + static Action* seed_of_corruption(PlayerbotAI* ai) { return new CastSeedOfCorruptionAction(ai); } + static Action* rain_of_fire(PlayerbotAI* ai) { return new CastRainOfFireAction(ai); } + static Action* shadowfury(PlayerbotAI* ai) { return new CastShadowfuryAction(ai); } + static Action* life_tap(PlayerbotAI* ai) { return new CastLifeTapAction(ai); } + + }; + }; +}; + + + +WarlockAiObjectContext::WarlockAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::warlock::StrategyFactoryInternal()); + strategyContexts.Add(new ai::warlock::CombatStrategyFactoryInternal()); + actionContexts.Add(new ai::warlock::AiObjectContextInternal()); + triggerContexts.Add(new ai::warlock::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.h b/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.h new file mode 100644 index 000000000..0d3fa27f3 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class WarlockAiObjectContext : public AiObjectContext + { + public: + WarlockAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.cpp b/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.cpp new file mode 100644 index 000000000..d4894976d --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "WarlockMultipliers.h" +//#include "WarlockActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.h b/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.cpp b/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.cpp new file mode 100644 index 000000000..65fbabac6 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.cpp @@ -0,0 +1,19 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarlockTriggers.h" +#include "WarlockActions.h" + +using namespace ai; + +bool DemonArmorTrigger::IsActive() +{ + Unit* target = GetTarget(); + return !ai->HasAura("demon skin", target) && + !ai->HasAura("demon armor", target) && + !ai->HasAura("fel armor", target); +} + +bool SpellstoneTrigger::IsActive() +{ + return BuffTrigger::IsActive() && AI_VALUE2(uint8, "item count", getName()) > 0; +} diff --git a/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.h b/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.h new file mode 100644 index 000000000..227c39889 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warlock/WarlockTriggers.h @@ -0,0 +1,81 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + class DemonArmorTrigger : public BuffTrigger + { + public: + DemonArmorTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "demon armor") {} + virtual bool IsActive(); + }; + + class SpellstoneTrigger : public BuffTrigger + { + public: + SpellstoneTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "spellstone") {} + virtual bool IsActive(); + }; + + DEBUFF_TRIGGER(CurseOfAgonyTrigger, "curse of agony", "curse of agony"); + DEBUFF_TRIGGER(CorruptionTrigger, "corruption", "corruption"); + + class CorruptionOnAttackerTrigger : public DebuffOnAttackerTrigger + { + public: + CorruptionOnAttackerTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "corruption") {} + }; + + DEBUFF_TRIGGER(ImmolateTrigger, "immolate", "immolate"); + + class ShadowTranceTrigger : public HasAuraTrigger + { + public: + ShadowTranceTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "shadow trance") {} + }; + + class BacklashTrigger : public HasAuraTrigger + { + public: + BacklashTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "backlash") {} + }; + + class BanishTrigger : public HasCcTargetTrigger + { + public: + BanishTrigger(PlayerbotAI* ai) : HasCcTargetTrigger(ai, "banish") {} + }; + + class WarlockConjuredItemTrigger : public ItemCountTrigger + { + public: + WarlockConjuredItemTrigger(PlayerbotAI* ai, string item) : ItemCountTrigger(ai, item, 1) {} + + virtual bool IsActive() { return ItemCountTrigger::IsActive() && AI_VALUE2(uint8, "item count", "soul shard") > 0; } + }; + + class HasSpellstoneTrigger : public WarlockConjuredItemTrigger + { + public: + HasSpellstoneTrigger(PlayerbotAI* ai) : WarlockConjuredItemTrigger(ai, "spellstone") {} + }; + + class HasFirestoneTrigger : public WarlockConjuredItemTrigger + { + public: + HasFirestoneTrigger(PlayerbotAI* ai) : WarlockConjuredItemTrigger(ai, "firestone") {} + }; + + class HasHealthstoneTrigger : public WarlockConjuredItemTrigger + { + public: + HasHealthstoneTrigger(PlayerbotAI* ai) : WarlockConjuredItemTrigger(ai, "healthstone") {} + }; + + class FearTrigger : public HasCcTargetTrigger + { + public: + FearTrigger(PlayerbotAI* ai) : HasCcTargetTrigger(ai, "fear") {} + }; + +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.cpp b/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.cpp new file mode 100644 index 000000000..25e453901 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.cpp @@ -0,0 +1,130 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarriorMultipliers.h" +#include "DpsWarriorStrategy.h" + +using namespace ai; + +class DpsWarriorStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + DpsWarriorStrategyActionNodeFactory() + { + creators["overpower"] = &overpower; + creators["melee"] = &melee; + creators["charge"] = &charge; + creators["bloodthirst"] = &bloodthirst; + creators["rend"] = &rend; + creators["mocking blow"] = &mocking_blow; + creators["death wish"] = &death_wish; + creators["execute"] = &execute; + } +private: + static ActionNode* overpower(PlayerbotAI* ai) + { + return new ActionNode ("overpower", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NextAction::array(0, new NextAction("charge"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* charge(PlayerbotAI* ai) + { + return new ActionNode ("charge", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NextAction::array(0, new NextAction("reach melee"), NULL), + /*C*/ NULL); + } + static ActionNode* bloodthirst(PlayerbotAI* ai) + { + return new ActionNode ("bloodthirst", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NextAction::array(0, new NextAction("heroic strike"), NULL), + /*C*/ NULL); + } + static ActionNode* rend(PlayerbotAI* ai) + { + return new ActionNode ("rend", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* mocking_blow(PlayerbotAI* ai) + { + return new ActionNode ("mocking blow", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NextAction::array(0, NULL), + /*C*/ NULL); + } + static ActionNode* death_wish(PlayerbotAI* ai) + { + return new ActionNode ("death wish", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("berserker rage"), NULL), + /*C*/ NULL); + } + static ActionNode* execute(PlayerbotAI* ai) + { + return new ActionNode ("execute", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NextAction::array(0, new NextAction("heroic strike"), NULL), + /*C*/ NULL); + } +}; + +DpsWarriorStrategy::DpsWarriorStrategy(PlayerbotAI* ai) : GenericWarriorStrategy(ai) +{ + actionNodeFactories.Add(new DpsWarriorStrategyActionNodeFactory()); +} + +NextAction** DpsWarriorStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("bloodthirst", ACTION_NORMAL + 1), NULL); +} + +void DpsWarriorStrategy::InitTriggers(std::list &triggers) +{ + GenericWarriorStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "enemy out of melee", + NextAction::array(0, new NextAction("charge", ACTION_NORMAL + 9), NULL))); + + triggers.push_back(new TriggerNode( + "target critical health", + NextAction::array(0, new NextAction("execute", ACTION_HIGH + 4), NULL))); + + triggers.push_back(new TriggerNode( + "hamstring", + NextAction::array(0, new NextAction("hamstring", ACTION_INTERRUPT), NULL))); + + triggers.push_back(new TriggerNode( + "victory rush", + NextAction::array(0, new NextAction("victory rush", ACTION_HIGH + 3), NULL))); + + triggers.push_back(new TriggerNode( + "death wish", + NextAction::array(0, new NextAction("death wish", ACTION_HIGH + 2), NULL))); +} + + +void DpsWarrirorAoeStrategy::InitTriggers(std::list &triggers) +{ + triggers.push_back(new TriggerNode( + "rend on attacker", + NextAction::array(0, new NextAction("rend on attacker", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 2), new NextAction("demoralizing shout", ACTION_HIGH + 2), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("cleave", ACTION_HIGH + 3), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.h b/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.h new file mode 100644 index 000000000..3ef29c335 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/DpsWarriorStrategy.h @@ -0,0 +1,28 @@ +#pragma once + +#include "GenericWarriorStrategy.h" + +namespace ai +{ + class DpsWarriorStrategy : public GenericWarriorStrategy + { + public: + DpsWarriorStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "dps"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; } + }; + + class DpsWarrirorAoeStrategy : public CombatStrategy + { + public: + DpsWarrirorAoeStrategy(PlayerbotAI* ai) : CombatStrategy(ai) {} + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "aoe"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.cpp new file mode 100644 index 000000000..7ac6cb79a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.cpp @@ -0,0 +1,7 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarriorMultipliers.h" +#include "GenericWarriorNonCombatStrategy.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.h b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.h new file mode 100644 index 000000000..1041a2eeb --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorNonCombatStrategy.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../generic/NonCombatStrategy.h" + +namespace ai +{ + class GenericWarriorNonCombatStrategy : public NonCombatStrategy + { + public: + GenericWarriorNonCombatStrategy(PlayerbotAI* ai) : NonCombatStrategy(ai) {} + virtual string getName() { return "nc"; } + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.cpp b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.cpp new file mode 100644 index 000000000..c8898ec78 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.cpp @@ -0,0 +1,73 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "GenericWarriorStrategy.h" +#include "WarriorAiObjectContext.h" + +using namespace ai; + +class GenericWarriorStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + GenericWarriorStrategyActionNodeFactory() + { + creators["hamstring"] = &hamstring; + creators["heroic strike"] = &heroic_strike; + creators["battle shout"] = &battle_shout; + } +private: + static ActionNode* hamstring(PlayerbotAI* ai) + { + return new ActionNode ("hamstring", + /*P*/ NextAction::array(0, new NextAction("battle stance"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* heroic_strike(PlayerbotAI* ai) + { + return new ActionNode ("heroic strike", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* battle_shout(PlayerbotAI* ai) + { + return new ActionNode ("battle shout", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } +}; + +GenericWarriorStrategy::GenericWarriorStrategy(PlayerbotAI* ai) : MeleeCombatStrategy(ai) +{ + actionNodeFactories.Add(new GenericWarriorStrategyActionNodeFactory()); +} + +void GenericWarriorStrategy::InitTriggers(std::list &triggers) +{ + MeleeCombatStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "battle shout", + NextAction::array(0, new NextAction("battle shout", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "rend", + NextAction::array(0, new NextAction("rend", ACTION_NORMAL + 1), NULL))); + + triggers.push_back(new TriggerNode( + "bloodrage", + NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "shield bash", + NextAction::array(0, new NextAction("shield bash", ACTION_INTERRUPT + 4), NULL))); + + triggers.push_back(new TriggerNode( + "shield bash on enemy healer", + NextAction::array(0, new NextAction("shield bash on enemy healer", ACTION_INTERRUPT + 3), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("intimidating shout", ACTION_EMERGENCY), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.h b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.h new file mode 100644 index 000000000..0ef2dcf8f --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/GenericWarriorStrategy.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Strategy.h" +#include "../generic/MeleeCombatStrategy.h" + +namespace ai +{ + class AiObjectContext; + + class GenericWarriorStrategy : public MeleeCombatStrategy + { + public: + GenericWarriorStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "warrior"; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.cpp b/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.cpp new file mode 100644 index 000000000..f1206f30e --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.cpp @@ -0,0 +1,126 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarriorMultipliers.h" +#include "TankWarriorStrategy.h" + +using namespace ai; + +class TankWarriorStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + TankWarriorStrategyActionNodeFactory() + { + creators["melee"] = &melee; + creators["shield wall"] = &shield_wall; + creators["rend"] = &rend; + creators["revenge"] = &revenge; + creators["devastate"] = &devastate; + creators["shockwave"] = &shockwave; + creators["taunt"] = &taunt; + } +private: + static ActionNode* melee(PlayerbotAI* ai) + { + return new ActionNode ("melee", + /*P*/ NextAction::array(0, new NextAction("defensive stance"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* shield_wall(PlayerbotAI* ai) + { + return new ActionNode ("shield wall", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("shield block"), NULL), + /*C*/ NULL); + } + static ActionNode* rend(PlayerbotAI* ai) + { + return new ActionNode ("rend", + /*P*/ NextAction::array(0, new NextAction("defensive stance"), NULL), + /*A*/ NULL, + /*C*/ NULL); + } + static ActionNode* revenge(PlayerbotAI* ai) + { + return new ActionNode ("revenge", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("melee"), NULL), + /*C*/ NULL); + } + static ActionNode* devastate(PlayerbotAI* ai) + { + return new ActionNode ("devastate", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("sunder armor"), NULL), + /*C*/ NULL); + } + static ActionNode* shockwave(PlayerbotAI* ai) + { + return new ActionNode ("shockwave", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("cleave"), NULL), + /*C*/ NULL); + } + static ActionNode* taunt(PlayerbotAI* ai) + { + return new ActionNode ("taunt", + /*P*/ NULL, + /*A*/ NextAction::array(0, new NextAction("mocking blow"), NULL), + /*C*/ NULL); + } +}; + +TankWarriorStrategy::TankWarriorStrategy(PlayerbotAI* ai) : GenericWarriorStrategy(ai) +{ + actionNodeFactories.Add(new TankWarriorStrategyActionNodeFactory()); +} + +NextAction** TankWarriorStrategy::getDefaultActions() +{ + return NextAction::array(0, new NextAction("devastate", ACTION_NORMAL + 1), new NextAction("revenge", ACTION_NORMAL + 1), NULL); +} + +void TankWarriorStrategy::InitTriggers(std::list &triggers) +{ + GenericWarriorStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode( + "medium rage available", + NextAction::array(0, new NextAction("shield slam", ACTION_NORMAL + 2), new NextAction("heroic strike", ACTION_NORMAL + 2), NULL))); + + triggers.push_back(new TriggerNode( + "disarm", + NextAction::array(0, new NextAction("disarm", ACTION_NORMAL), NULL))); + + triggers.push_back(new TriggerNode( + "lose aggro", + NextAction::array(0, new NextAction("taunt", ACTION_HIGH + 9), NULL))); + + triggers.push_back(new TriggerNode( + "medium health", + NextAction::array(0, new NextAction("shield wall", ACTION_MEDIUM_HEAL), NULL))); + + triggers.push_back(new TriggerNode( + "critical health", + NextAction::array(0, new NextAction("last stand", ACTION_EMERGENCY + 3), NULL))); + + triggers.push_back(new TriggerNode( + "medium aoe", + NextAction::array(0, new NextAction("shockwave", ACTION_HIGH + 2), NULL))); + + triggers.push_back(new TriggerNode( + "light aoe", + NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 2), new NextAction("demoralizing shout", ACTION_HIGH + 2), new NextAction("cleave", ACTION_HIGH + 1), NULL))); + + triggers.push_back(new TriggerNode( + "high aoe", + NextAction::array(0, new NextAction("challenging shout", ACTION_HIGH + 3), NULL))); + + triggers.push_back(new TriggerNode( + "concussion blow", + NextAction::array(0, new NextAction("concussion blow", ACTION_INTERRUPT), NULL))); + + triggers.push_back(new TriggerNode( + "sword and board", + NextAction::array(0, new NextAction("shield slam", ACTION_HIGH + 3), NULL))); +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.h b/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.h new file mode 100644 index 000000000..0f989460a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/TankWarriorStrategy.h @@ -0,0 +1,18 @@ +#pragma once + +#include "GenericWarriorStrategy.h" + +namespace ai +{ + class TankWarriorStrategy : public GenericWarriorStrategy + { + public: + TankWarriorStrategy(PlayerbotAI* ai); + + public: + virtual void InitTriggers(std::list &triggers); + virtual string getName() { return "tank"; } + virtual NextAction** getDefaultActions(); + virtual int GetType() { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; } + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.cpp b/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.cpp new file mode 100644 index 000000000..5888c0cef --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.cpp @@ -0,0 +1,15 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarriorActions.h" + +using namespace ai; + +NextAction** CastDisarmAction::getPrerequisites() +{ + return NextAction::merge( NextAction::array(0, new NextAction("defensive stance"), NULL), CastDebuffSpellAction::getPrerequisites()); +} + +NextAction** CastRevengeAction::getPrerequisites() +{ + return NextAction::merge( NextAction::array(0, new NextAction("defensive stance"), NULL), CastMeleeSpellAction::getPrerequisites()); +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.h b/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.h new file mode 100644 index 000000000..00570a5af --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorActions.h @@ -0,0 +1,207 @@ +#pragma once +#include "../actions/GenericActions.h" + +namespace ai +{ + // battle + class CastBattleMeleeSpellAction : public CastMeleeSpellAction { + public: + CastBattleMeleeSpellAction(PlayerbotAI* ai, string spell) : CastMeleeSpellAction(ai, spell) {} + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("battle stance"), NULL), CastMeleeSpellAction::getPrerequisites()); + } + }; + + // defensive + class CastDefensiveMeleeSpellAction : public CastMeleeSpellAction { + public: + CastDefensiveMeleeSpellAction(PlayerbotAI* ai, string spell) : CastMeleeSpellAction(ai, spell) {} + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("defensive stance"), NULL), CastMeleeSpellAction::getPrerequisites()); + } + }; + + // all + class CastHeroicStrikeAction : public CastMeleeSpellAction { + public: + CastHeroicStrikeAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "heroic strike") {} + }; + + // all + class CastCleaveAction : public CastMeleeSpellAction { + public: + CastCleaveAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "cleave") {} + }; + + // battle, berserker + class CastMockingBlowAction : public CastMeleeSpellAction { + public: + CastMockingBlowAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "mocking blow") {} + }; + + class CastBloodthirstAction : public CastMeleeSpellAction { + public: + CastBloodthirstAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "bloodthirst") {} + }; + + // battle, berserker + class CastExecuteAction : public CastMeleeSpellAction { + public: + CastExecuteAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "execute") {} + }; + + // battle + class CastOverpowerAction : public CastBattleMeleeSpellAction { + public: + CastOverpowerAction(PlayerbotAI* ai) : CastBattleMeleeSpellAction(ai, "overpower") {} + }; + + // battle, berserker + class CastHamstringAction : public CastSnareSpellAction { + public: + CastHamstringAction(PlayerbotAI* ai) : CastSnareSpellAction(ai, "hamstring") {} + }; + + // defensive + class CastTauntAction : public CastSpellAction { + public: + CastTauntAction(PlayerbotAI* ai) : CastSpellAction(ai, "taunt") {} + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("defensive stance"), NULL), CastSpellAction::getPrerequisites()); + } + }; + + // defensive + class CastShieldBlockAction : public CastBuffSpellAction { + public: + CastShieldBlockAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "shield block") {} + virtual NextAction** getPrerequisites() { + return NextAction::merge( NextAction::array(0, new NextAction("defensive stance"), NULL), CastSpellAction::getPrerequisites()); + } + }; + + // defensive + class CastShieldWallAction : public CastDefensiveMeleeSpellAction { + public: + CastShieldWallAction(PlayerbotAI* ai) : CastDefensiveMeleeSpellAction(ai, "shield wall") {} + }; + + class CastBloodrageAction : public CastBuffSpellAction { + public: + CastBloodrageAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "bloodrage") {} + }; + + // defensive + class CastDevastateAction : public CastDefensiveMeleeSpellAction { + public: + CastDevastateAction(PlayerbotAI* ai) : CastDefensiveMeleeSpellAction(ai, "devastate") {} + }; + + // all + class CastSlamAction : public CastMeleeSpellAction { + public: + CastSlamAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "slam") {} + }; + + // all + class CastShieldSlamAction : public CastMeleeSpellAction { + public: + CastShieldSlamAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "shield slam") {} + }; + + // after dodge + BEGIN_MELEE_SPELL_ACTION(CastRevengeAction, "revenge") + virtual NextAction** getPrerequisites(); + END_SPELL_ACTION() + + + //debuffs + BEGIN_DEBUFF_ACTION(CastRendAction, "rend") + END_SPELL_ACTION() + + class CastRendOnAttackerAction : public CastDebuffSpellOnAttackerAction + { + public: + CastRendOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "rend") {} + }; + + BEGIN_DEBUFF_ACTION(CastDisarmAction, "disarm") + virtual NextAction** getPrerequisites(); + END_SPELL_ACTION() + + BEGIN_DEBUFF_ACTION(CastSunderArmorAction, "sunder armor") // 5 times + END_SPELL_ACTION() + + class CastDemoralizingShoutAction : public CastDebuffSpellAction { + public: + CastDemoralizingShoutAction(PlayerbotAI* ai) : CastDebuffSpellAction(ai, "demoralizing shout") {} + }; + + BEGIN_MELEE_SPELL_ACTION(CastChallengingShoutAction, "challenging shout") + END_SPELL_ACTION() + + // stuns + BEGIN_MELEE_SPELL_ACTION(CastShieldBashAction, "shield bash") + END_SPELL_ACTION() + + BEGIN_MELEE_SPELL_ACTION(CastIntimidatingShoutAction, "intimidating shout") + END_SPELL_ACTION() + + BEGIN_MELEE_SPELL_ACTION(CastThunderClapAction, "thunder clap") + END_SPELL_ACTION() + + // buffs + class CastBattleShoutAction : public CastBuffSpellAction { + public: + CastBattleShoutAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "battle shout") {} + }; + + class CastDefensiveStanceAction : public CastBuffSpellAction { + public: + CastDefensiveStanceAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "defensive stance") {} + }; + + class CastBattleStanceAction : public CastBuffSpellAction { + public: + CastBattleStanceAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "battle stance") {} + }; + + BEGIN_RANGED_SPELL_ACTION(CastChargeAction, "charge") + END_SPELL_ACTION() + + class CastDeathWishAction : public CastBuffSpellAction { + public: + CastDeathWishAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "death wish") {} + }; + + class CastBerserkerRageAction : public CastBuffSpellAction { + public: + CastBerserkerRageAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "berserker rage") {} + }; + + class CastLastStandAction : public CastBuffSpellAction { + public: + CastLastStandAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "last stand") {} + }; + + // defensive + class CastShockwaveAction : public CastDefensiveMeleeSpellAction { + public: + CastShockwaveAction(PlayerbotAI* ai) : CastDefensiveMeleeSpellAction(ai, "shockwave") {} + }; + + // defensive + class CastConcussionBlowAction : public CastSnareSpellAction { + public: + CastConcussionBlowAction(PlayerbotAI* ai) : CastSnareSpellAction(ai, "concussion blow") {} + }; + + BEGIN_MELEE_SPELL_ACTION(CastVictoryRushAction, "victory rush") + END_SPELL_ACTION() + + class CastShieldBashOnEnemyHealerAction : public CastSpellOnEnemyHealerAction + { + public: + CastShieldBashOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "shield bash") {} + }; +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.cpp new file mode 100644 index 000000000..cafe463a2 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.cpp @@ -0,0 +1,192 @@ +#include "botpch.h" +#include "../../playerbot.h" +#include "WarriorActions.h" +#include "WarriorAiObjectContext.h" +#include "GenericWarriorNonCombatStrategy.h" +#include "TankWarriorStrategy.h" +#include "DpsWarriorStrategy.h" +#include "../generic/PullStrategy.h" +#include "WarriorTriggers.h" +#include "../NamedObjectContext.h" + +using namespace ai; + + +namespace ai +{ + namespace warrior + { + using namespace ai; + + class StrategyFactoryInternal : public NamedObjectContext + { + public: + StrategyFactoryInternal() + { + creators["nc"] = &warrior::StrategyFactoryInternal::nc; + creators["pull"] = &warrior::StrategyFactoryInternal::pull; + creators["aoe"] = &warrior::StrategyFactoryInternal::aoe; + } + + private: + static Strategy* nc(PlayerbotAI* ai) { return new GenericWarriorNonCombatStrategy(ai); } + static Strategy* aoe(PlayerbotAI* ai) { return new DpsWarrirorAoeStrategy(ai); } + static Strategy* pull(PlayerbotAI* ai) { return new PullStrategy(ai, "shoot"); } + }; + + class CombatStrategyFactoryInternal : public NamedObjectContext + { + public: + CombatStrategyFactoryInternal() : NamedObjectContext(false, true) + { + creators["tank"] = &warrior::CombatStrategyFactoryInternal::tank; + creators["dps"] = &warrior::CombatStrategyFactoryInternal::dps; + } + + private: + static Strategy* tank(PlayerbotAI* ai) { return new TankWarriorStrategy(ai); } + static Strategy* dps(PlayerbotAI* ai) { return new DpsWarriorStrategy(ai); } + }; + }; +}; + +namespace ai +{ + namespace warrior + { + using namespace ai; + + class TriggerFactoryInternal : public NamedObjectContext + { + public: + TriggerFactoryInternal() + { + creators["hamstring"] = &TriggerFactoryInternal::hamstring; + creators["victory rush"] = &TriggerFactoryInternal::victory_rush; + creators["death wish"] = &TriggerFactoryInternal::death_wish; + creators["battle shout"] = &TriggerFactoryInternal::battle_shout; + creators["rend"] = &TriggerFactoryInternal::rend; + creators["rend on attacker"] = &TriggerFactoryInternal::rend_on_attacker; + creators["bloodrage"] = &TriggerFactoryInternal::bloodrage; + creators["shield bash"] = &TriggerFactoryInternal::shield_bash; + creators["disarm"] = &TriggerFactoryInternal::disarm; + creators["concussion blow"] = &TriggerFactoryInternal::concussion_blow; + creators["sword and board"] = &TriggerFactoryInternal::SwordAndBoard; + creators["shield bash on enemy healer"] = &TriggerFactoryInternal::shield_bash_on_enemy_healer; + + } + + private: + static Trigger* hamstring(PlayerbotAI* ai) { return new HamstringTrigger(ai); } + static Trigger* victory_rush(PlayerbotAI* ai) { return new VictoryRushTrigger(ai); } + static Trigger* death_wish(PlayerbotAI* ai) { return new DeathWishTrigger(ai); } + static Trigger* battle_shout(PlayerbotAI* ai) { return new BattleShoutTrigger(ai); } + static Trigger* rend(PlayerbotAI* ai) { return new RendDebuffTrigger(ai); } + static Trigger* rend_on_attacker(PlayerbotAI* ai) { return new RendDebuffOnAttackerTrigger(ai); } + static Trigger* bloodrage(PlayerbotAI* ai) { return new BloodrageDebuffTrigger(ai); } + static Trigger* shield_bash(PlayerbotAI* ai) { return new ShieldBashInterruptSpellTrigger(ai); } + static Trigger* disarm(PlayerbotAI* ai) { return new DisarmDebuffTrigger(ai); } + static Trigger* concussion_blow(PlayerbotAI* ai) { return new ConcussionBlowTrigger(ai); } + static Trigger* SwordAndBoard(PlayerbotAI* ai) { return new SwordAndBoardTrigger(ai); } + static Trigger* shield_bash_on_enemy_healer(PlayerbotAI* ai) { return new ShieldBashInterruptEnemyHealerSpellTrigger(ai); } + }; + }; +}; + + +namespace ai +{ + namespace warrior + { + using namespace ai; + + class AiObjectContextInternal : public NamedObjectContext + { + public: + AiObjectContextInternal() + { + creators["devastate"] = &AiObjectContextInternal::devastate; + creators["overpower"] = &AiObjectContextInternal::overpower; + creators["charge"] = &AiObjectContextInternal::charge; + creators["bloodthirst"] = &AiObjectContextInternal::bloodthirst; + creators["rend"] = &AiObjectContextInternal::rend; + creators["rend on attacker"] = &AiObjectContextInternal::rend_on_attacker; + creators["mocking blow"] = &AiObjectContextInternal::mocking_blow; + creators["death wish"] = &AiObjectContextInternal::death_wish; + creators["berserker rage"] = &AiObjectContextInternal::berserker_rage; + creators["victory rush"] = &AiObjectContextInternal::victory_rush; + creators["execute"] = &AiObjectContextInternal::execute; + creators["defensive stance"] = &AiObjectContextInternal::defensive_stance; + creators["hamstring"] = &AiObjectContextInternal::hamstring; + creators["shield bash"] = &AiObjectContextInternal::shield_bash; + creators["shield block"] = &AiObjectContextInternal::shield_block; + creators["bloodrage"] = &AiObjectContextInternal::bloodrage; + creators["battle stance"] = &AiObjectContextInternal::battle_stance; + creators["heroic strike"] = &AiObjectContextInternal::heroic_strike; + creators["intimidating shout"] = &AiObjectContextInternal::intimidating_shout; + creators["demoralizing shout"] = &AiObjectContextInternal::demoralizing_shout; + creators["challenging shout"] = &AiObjectContextInternal::challenging_shout; + creators["shield wall"] = &AiObjectContextInternal::shield_wall; + creators["battle shout"] = &AiObjectContextInternal::battle_shout; + creators["thunder clap"] = &AiObjectContextInternal::thunder_clap; + creators["taunt"] = &AiObjectContextInternal::taunt; + creators["revenge"] = &AiObjectContextInternal::revenge; + creators["slam"] = &AiObjectContextInternal::slam; + creators["shield slam"] = &AiObjectContextInternal::shield_slam; + creators["disarm"] = &AiObjectContextInternal::disarm; + creators["sunder armor"] = &AiObjectContextInternal::sunder_armor; + creators["last stand"] = &AiObjectContextInternal::last_stand; + creators["shockwave"] = &AiObjectContextInternal::shockwave; + creators["cleave"] = &AiObjectContextInternal::cleave; + creators["concussion blow"] = &AiObjectContextInternal::concussion_blow; + creators["shield bash on enemy healer"] = &AiObjectContextInternal::shield_bash_on_enemy_healer; + } + + private: + static Action* devastate(PlayerbotAI* ai) { return new CastDevastateAction(ai); } + static Action* last_stand(PlayerbotAI* ai) { return new CastLastStandAction(ai); } + static Action* shockwave(PlayerbotAI* ai) { return new CastShockwaveAction(ai); } + static Action* cleave(PlayerbotAI* ai) { return new CastCleaveAction(ai); } + static Action* concussion_blow(PlayerbotAI* ai) { return new CastConcussionBlowAction(ai); } + static Action* taunt(PlayerbotAI* ai) { return new CastTauntAction(ai); } + static Action* revenge(PlayerbotAI* ai) { return new CastRevengeAction(ai); } + static Action* slam(PlayerbotAI* ai) { return new CastSlamAction(ai); } + static Action* shield_slam(PlayerbotAI* ai) { return new CastShieldSlamAction(ai); } + static Action* disarm(PlayerbotAI* ai) { return new CastDisarmAction(ai); } + static Action* sunder_armor(PlayerbotAI* ai) { return new CastSunderArmorAction(ai); } + static Action* overpower(PlayerbotAI* ai) { return new CastOverpowerAction(ai); } + static Action* charge(PlayerbotAI* ai) { return new CastChargeAction(ai); } + static Action* bloodthirst(PlayerbotAI* ai) { return new CastBloodthirstAction(ai); } + static Action* rend(PlayerbotAI* ai) { return new CastRendAction(ai); } + static Action* rend_on_attacker(PlayerbotAI* ai) { return new CastRendOnAttackerAction(ai); } + static Action* mocking_blow(PlayerbotAI* ai) { return new CastMockingBlowAction(ai); } + static Action* death_wish(PlayerbotAI* ai) { return new CastDeathWishAction(ai); } + static Action* berserker_rage(PlayerbotAI* ai) { return new CastBerserkerRageAction(ai); } + static Action* victory_rush(PlayerbotAI* ai) { return new CastVictoryRushAction(ai); } + static Action* execute(PlayerbotAI* ai) { return new CastExecuteAction(ai); } + static Action* defensive_stance(PlayerbotAI* ai) { return new CastDefensiveStanceAction(ai); } + static Action* hamstring(PlayerbotAI* ai) { return new CastHamstringAction(ai); } + static Action* shield_bash(PlayerbotAI* ai) { return new CastShieldBashAction(ai); } + static Action* shield_block(PlayerbotAI* ai) { return new CastShieldBlockAction(ai); } + static Action* bloodrage(PlayerbotAI* ai) { return new CastBloodrageAction(ai); } + static Action* battle_stance(PlayerbotAI* ai) { return new CastBattleStanceAction(ai); } + static Action* heroic_strike(PlayerbotAI* ai) { return new CastHeroicStrikeAction(ai); } + static Action* intimidating_shout(PlayerbotAI* ai) { return new CastIntimidatingShoutAction(ai); } + static Action* demoralizing_shout(PlayerbotAI* ai) { return new CastDemoralizingShoutAction(ai); } + static Action* challenging_shout(PlayerbotAI* ai) { return new CastChallengingShoutAction(ai); } + static Action* shield_wall(PlayerbotAI* ai) { return new CastShieldWallAction(ai); } + static Action* battle_shout(PlayerbotAI* ai) { return new CastBattleShoutAction(ai); } + static Action* thunder_clap(PlayerbotAI* ai) { return new CastThunderClapAction(ai); } + static Action* shield_bash_on_enemy_healer(PlayerbotAI* ai) { return new CastShieldBashOnEnemyHealerAction(ai); } + + }; + }; +}; + +WarriorAiObjectContext::WarriorAiObjectContext(PlayerbotAI* ai) : AiObjectContext(ai) +{ + strategyContexts.Add(new ai::warrior::StrategyFactoryInternal()); + strategyContexts.Add(new ai::warrior::CombatStrategyFactoryInternal()); + actionContexts.Add(new ai::warrior::AiObjectContextInternal()); + triggerContexts.Add(new ai::warrior::TriggerFactoryInternal()); +} diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.h b/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.h new file mode 100644 index 000000000..bc1b5d49a --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorAiObjectContext.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../AiObjectContext.h" + +namespace ai +{ + class WarriorAiObjectContext : public AiObjectContext + { + public: + WarriorAiObjectContext(PlayerbotAI* ai); + }; +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.cpp b/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.cpp new file mode 100644 index 000000000..7f521513b --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.cpp @@ -0,0 +1,6 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "WarriorMultipliers.h" +//#include "WarriorActions.h" + +using namespace ai; \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.h b/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.h new file mode 100644 index 000000000..480768d53 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorMultipliers.h @@ -0,0 +1,6 @@ +#pragma once + +namespace ai +{ + +} \ No newline at end of file diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.cpp b/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.cpp new file mode 100644 index 000000000..cd6b206fa --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.cpp @@ -0,0 +1,7 @@ +#include "botpch.h" +//#include "../../playerbot.h" +//#include "WarriorTriggers.h" +//#include "WarriorActions.h" + +using namespace ai; + diff --git a/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.h b/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.h new file mode 100644 index 000000000..1396bd627 --- /dev/null +++ b/src/modules/Bots/playerbot/strategy/warrior/WarriorTriggers.h @@ -0,0 +1,78 @@ +#pragma once +#include "../triggers/GenericTriggers.h" + +namespace ai +{ + BUFF_TRIGGER(BattleShoutTrigger, "battle shout", "battle shout") + + DEBUFF_TRIGGER(RendDebuffTrigger, "rend", "rend") + DEBUFF_TRIGGER(DisarmDebuffTrigger, "disarm", "disarm") + DEBUFF_TRIGGER(SunderArmorDebuffTrigger, "sunder armor", "sunder armor") + + class RendDebuffOnAttackerTrigger : public DebuffOnAttackerTrigger + { + public: + RendDebuffOnAttackerTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "rend") {} + }; + + class RevengeAvailableTrigger : public SpellCanBeCastTrigger + { + public: + RevengeAvailableTrigger(PlayerbotAI* ai) : SpellCanBeCastTrigger(ai, "revenge") {} + }; + + class BloodrageDebuffTrigger : public DebuffTrigger + { + public: + BloodrageDebuffTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "bloodrage") {} + virtual bool IsActive() + { + return DebuffTrigger::IsActive() && + AI_VALUE2(uint8, "health", "self target") >= 75 && + AI_VALUE2(uint8, "rage", "self target") < 20; + } + }; + + class ShieldBashInterruptSpellTrigger : public InterruptSpellTrigger + { + public: + ShieldBashInterruptSpellTrigger(PlayerbotAI* ai) : InterruptSpellTrigger(ai, "shield bash") {} + }; + + class VictoryRushTrigger : public HasAuraTrigger + { + public: + VictoryRushTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "victory rush") {} + }; + + class SwordAndBoardTrigger : public HasAuraTrigger + { + public: + SwordAndBoardTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "sword and board") {} + }; + + class ConcussionBlowTrigger : public SnareTargetTrigger + { + public: + ConcussionBlowTrigger(PlayerbotAI* ai) : SnareTargetTrigger(ai, "concussion blow") {} + }; + + class HamstringTrigger : public SnareTargetTrigger + { + public: + HamstringTrigger(PlayerbotAI* ai) : SnareTargetTrigger(ai, "hamstring") {} + }; + + class DeathWishTrigger : public BoostTrigger + { + public: + DeathWishTrigger(PlayerbotAI* ai) : BoostTrigger(ai, "death wish") {} + }; + + class ShieldBashInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger + { + public: + ShieldBashInterruptEnemyHealerSpellTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "shield bash") {} + }; + +} diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 8355e1da8..1fb7fafcf 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -21,3 +21,7 @@ if(SCRIPT_LIB_SD3) add_subdirectory(SD3) endif() + +if(PLAYERBOTS) + add_subdirectory(Bots) +endif()