diff --git a/src/ChatHelper.cpp b/src/ChatHelper.cpp index 3465b1cd..ed476011 100644 --- a/src/ChatHelper.cpp +++ b/src/ChatHelper.cpp @@ -518,7 +518,7 @@ std::string const ChatHelper::FormatClass(Player* player, int8 spec) out << (c1 ? "|h|cff00ff00" : "") << c1 << "|h|cffffffff/"; out << (c2 ? "|h|cff00ff00" : "") << c2 << "|h|cffffffff"; - out << ") " << classes[cls]; + out << ")|r " << classes[cls]; return out.str(); } diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index cd49bcc5..5eee9bc0 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -27,6 +27,7 @@ #include "UpdateTime.h" #include "Vehicle.h" #include "GuildMgr.h" +#include "SayAction.h" std::vector& split(std::string const s, char delim, std::vector& elems); std::vector split(std::string const s, char delim); @@ -295,6 +296,27 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal chatCommands.push(*i); } + // chat replies + std::list delayedResponses; + while (!chatReplies.empty()) + { + ChatQueuedReply holder = chatReplies.front(); + time_t checkTime = holder.m_time; + if (checkTime && time(0) < checkTime) + { + delayedResponses.push_back(holder); + chatReplies.pop(); + continue; + } + ChatReplyAction::ChatReplyDo(bot, holder.m_type, holder.m_guid1, holder.m_guid2, holder.m_msg, holder.m_chanName, holder.m_name); + chatReplies.pop(); + } + + for (std::list::iterator i = delayedResponses.begin(); i != delayedResponses.end(); ++i) + { + chatReplies.push(*i); + } + // logout if logout timer is ready or if instant logout is possible if (bot->GetSession()->isLogingOut()) { @@ -464,6 +486,9 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro if (type == CHAT_MSG_ADDON) return; + if (type == CHAT_MSG_SYSTEM) + return; + if (text.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos) { std::vector commands; @@ -593,6 +618,9 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) { + if (packet.empty()) + return; + switch (packet.GetOpcode()) { case SMSG_SPELL_FAILURE: @@ -631,13 +659,75 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) uint32 emoteId; p.rpos(0); p >> emoteId >> source; - if (!source.IsPlayer()) - return; - else + if (source.IsPlayer()) botOutgoingPacketHandlers.AddPacket(packet); return; } + case SMSG_MESSAGECHAT: // do not react to self or if not ready to reply + { + if (!AllowActivity()) + return; + + WorldPacket p(packet); + if (!p.empty() && (p.GetOpcode() == SMSG_MESSAGECHAT || p.GetOpcode() == SMSG_GM_MESSAGECHAT)) + { + p.rpos(0); + uint8 msgtype, chatTag; + uint32 lang, textLen, nameLen, unused; + ObjectGuid guid1, guid2; + std::string name, chanName, message; + p >> msgtype >> lang; + p >> guid1 >> unused; + if (guid1.IsEmpty() || p.size() > p.DEFAULT_SIZE) + return; + switch (msgtype) + { + case CHAT_MSG_CHANNEL: + p >> chanName; + [[fallthrough]]; + case CHAT_MSG_SAY: + case CHAT_MSG_PARTY: + case CHAT_MSG_YELL: + case CHAT_MSG_WHISPER: + case CHAT_MSG_GUILD: + p >> guid2; + p >> textLen >> message >> chatTag; + + if (guid1 != bot->GetGUID()) // do not reply to self + { + // try to always reply to real player + time_t lastChat = GetAiObjectContext()->GetValue("last said", "chat")->Get(); + bool isPaused = time(0) < lastChat; + bool shouldReply = false; + bool isRandomBot = false; + sCharacterCache->GetCharacterNameByGuid(guid1, name); + uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid1); + isRandomBot = sPlayerbotAIConfig->IsInRandomAccountList(accountId); + bool isMentioned = message.find(bot->GetName()) != std::string::npos; + + // random bot speaks, chat CD + if (isRandomBot && isPaused) + return; + // BG: react only if mentioned or if not channel and real player spoke + if (bot->InBattleground() && bot->GetBattleground() && !(isMentioned || (msgtype != CHAT_MSG_CHANNEL && !isRandomBot))) + return; + + if ((isRandomBot && !isPaused && (!urand(0, 20) || (!urand(0, 10) && message.find(bot->GetName()) != std::string::npos))) || (!isRandomBot && (isMentioned || msgtype != CHAT_MSG_CHANNEL || !urand(0, 4)))) + { + QueueChatResponse(msgtype, guid1, ObjectGuid(), message, chanName, name); + GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 25)); + return; + } + } + break; + default: + break; + } + } + + return; + } case SMSG_MOVE_KNOCK_BACK: // handle knockbacks { // Peiru: Disable Knockback handling for now until spline crash can be resolved @@ -1357,7 +1447,7 @@ bool PlayerbotAI::TellMasterNoFacing(std::string const text, PlayerbotSecurityLe bool PlayerbotAI::TellError(std::string const text, PlayerbotSecurityLevel securityLevel) { Player* master = GetMaster(); - if (!IsTellAllowed(securityLevel) || GET_PLAYERBOT_AI(master)) + if (!IsTellAllowed(securityLevel) || !master || GET_PLAYERBOT_AI(master)) return false; if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master)) @@ -1736,9 +1826,9 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget) if (pet && pet->HasSpell(spellId)) { bool autocast = false; - for(AutoSpellList::iterator i = pet->m_autospells.begin(); i != pet->m_autospells.end(); ++i) + for(unsigned int & m_autospell : pet->m_autospells) { - if (*i == spellId) + if (m_autospell == spellId) { autocast = true; break; @@ -1893,9 +1983,9 @@ bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* ite if (pet && pet->HasSpell(spellId)) { bool autocast = false; - for (AutoSpellList::iterator i = pet->m_autospells.begin(); i != pet->m_autospells.end(); ++i) + for (unsigned int & m_autospell : pet->m_autospells) { - if (*i == spellId) + if (m_autospell == spellId) { autocast = true; break; @@ -3526,4 +3616,9 @@ bool PlayerbotAI::IsInRealGuild() return false; return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount)); +} + +void PlayerbotAI::QueueChatResponse(uint8 msgtype, ObjectGuid guid1, ObjectGuid guid2, std::string message, std::string chanName, std::string name) +{ + chatReplies.push(ChatQueuedReply(msgtype, guid1.GetCounter(), guid2.GetCounter(), message, chanName, name, time(0) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15))); } \ No newline at end of file diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index e48d6529..f76cb32e 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -14,6 +14,7 @@ #include "PlayerbotAIConfig.h" #include "PlayerbotSecurity.h" #include "WorldPacket.h" +#include "PlayerbotTextMgr.h" #include #include @@ -249,6 +250,7 @@ class PlayerbotAI : public PlayerbotAIBase std::string const HandleRemoteCommand(std::string const command); void HandleCommand(uint32 type, std::string const text, Player* fromPlayer); + void QueueChatResponse(uint8 msgtype, ObjectGuid guid1, ObjectGuid guid2, std::string message, std::string chanName, std::string name); void HandleBotOutgoingPacket(WorldPacket const& packet); void HandleMasterIncomingPacket(WorldPacket const& packet); void HandleMasterOutgoingPacket(WorldPacket const& packet); @@ -382,6 +384,7 @@ class PlayerbotAI : public PlayerbotAIBase BotState currentState; ChatHelper chatHelper; std::queue chatCommands; + std::queue chatReplies; PacketHandlingHelper botOutgoingPacketHandlers; PacketHandlingHelper masterIncomingPacketHandlers; PacketHandlingHelper masterOutgoingPacketHandlers; diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index e8a02a5c..5e328c43 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -140,8 +140,8 @@ bool PlayerbotAIConfig::Initialize() randomChangeMultiplier = sConfigMgr->GetOption("AiPlayerbot.RandomChangeMultiplier", 1.0); - randomBotCombatStrategies = sConfigMgr->GetOption("AiPlayerbot.RandomBotCombatStrategies", "-threat"); - randomBotNonCombatStrategies = sConfigMgr->GetOption("AiPlayerbot.RandomBotNonCombatStrategies", ""); + randomBotCombatStrategies = sConfigMgr->GetOption("AiPlayerbot.RandomBotCombatStrategies", "-threat,+custom::say"); + randomBotNonCombatStrategies = sConfigMgr->GetOption("AiPlayerbot.RandomBotNonCombatStrategies", "+custom::say"); combatStrategies = sConfigMgr->GetOption("AiPlayerbot.CombatStrategies", "+custom::say"); nonCombatStrategies = sConfigMgr->GetOption("AiPlayerbot.NonCombatStrategies", "+custom::say,+return"); @@ -308,6 +308,8 @@ bool PlayerbotAIConfig::Initialize() PlayerbotFactory::Init(); sRandomItemMgr->Init(); sRandomItemMgr->InitAfterAhBot(); + sPlayerbotTextMgr->LoadBotTexts(); + sPlayerbotTextMgr->LoadBotTextChance(); if (!sPlayerbotAIConfig->autoDoQuests) { diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 191994b7..8e3a8a94 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -7,6 +7,7 @@ #include "PlayerbotDbStore.h" #include "PlayerbotFactory.h" #include "WorldSession.h" +#include "ChannelMgr.h" PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) { @@ -415,6 +416,53 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) botAI->SetNextCheckDelay(urand(2000, 4000)); botAI->TellMaster("Hello!"); + + // bots join World chat if not solo oriented + if (bot->getLevel() >= 10 && sRandomPlayerbotMgr->IsRandomBot(bot) && GET_PLAYERBOT_AI(bot) && GET_PLAYERBOT_AI(bot)->GetGrouperType() != GrouperType::SOLO) + { + // TODO make action/config + // Make the bot join the world channel for chat + WorldPacket pkt(CMSG_JOIN_CHANNEL); + pkt << uint32(0) << uint8(0) << uint8(0); + pkt << std::string("World"); + pkt << ""; // Pass + bot->GetSession()->HandleJoinChannel(pkt); + } + // join standard channels + AreaTableEntry const* current_zone = sAreaTableStore.LookupEntry(bot->GetAreaId()); + ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()); + std::string current_zone_name = current_zone ? current_zone->area_name[0] : ""; + + if (current_zone && cMgr) + { + for (uint32 i = 0; i < sChatChannelsStore.GetNumRows(); ++i) + { + ChatChannelsEntry const* channel = sChatChannelsStore.LookupEntry(i); + if (!channel) continue; + + bool isLfg = (channel->flags & CHANNEL_DBC_FLAG_LFG) != 0; + + // skip non built-in channels or global channel without zone name in pattern + if (!isLfg && (!channel || (channel->flags & 4) == 4)) + continue; + + // new channel + Channel* new_channel = nullptr; + if (isLfg) + { + std::string lfgChannelName = channel->pattern[0]; + new_channel = cMgr->GetJoinChannel("LookingForGroup", channel->ChannelID); + } + else + { + char new_channel_name_buf[100]; + snprintf(new_channel_name_buf, 100, channel->pattern[0], current_zone_name.c_str()); + new_channel = cMgr->GetJoinChannel(new_channel_name_buf, channel->ChannelID); + } + if (new_channel && new_channel->GetName().length() > 0) + new_channel->JoinChannel(bot, ""); + } + } } std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, ObjectGuid guid, ObjectGuid masterguid, bool admin, uint32 masterAccountId, uint32 masterGuildId) @@ -951,6 +999,9 @@ void PlayerbotMgr::OnBotLoginInternal(Player * const bot) void PlayerbotMgr::OnPlayerLogin(Player* player) { + // set locale priority for bot texts + sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale()); + if (sPlayerbotAIConfig->selfBotLevel > 2) HandlePlayerbotCommand("self", player); diff --git a/src/PlayerbotTextMgr.cpp b/src/PlayerbotTextMgr.cpp index 38791a09..f4f57927 100644 --- a/src/PlayerbotTextMgr.cpp +++ b/src/PlayerbotTextMgr.cpp @@ -5,9 +5,17 @@ #include "PlayerbotTextMgr.h" #include "Playerbots.h" -void replaceAll(std::string& str, std::string const from, std::string const to); +void PlayerbotTextMgr::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' + } +} -void PlayerbotTextMgr::LoadTemplates() +void PlayerbotTextMgr::LoadBotTexts() { LOG_INFO("playerbots", "Loading playerbots texts..."); @@ -16,10 +24,17 @@ void PlayerbotTextMgr::LoadTemplates() { do { + std::map text; Field* fields = result->Fetch(); - std::string const key = fields[0].Get(); - std::string const text = fields[1].Get(); - templates[key].push_back(text); + std::string name = fields[0].Get(); + text[0] = fields[1].Get(); + uint32 sayType = fields[2].Get(); + uint32 replyType = fields[3].Get(); + for (uint8 i = 1; i < MAX_LOCALES; ++i) + { + text[i] = fields[i + 3].Get(); + } + botTexts[name].push_back(BotTextEntry(name, text, sayType, replyType)); ++count; } while (result->NextRow()); @@ -28,22 +43,158 @@ void PlayerbotTextMgr::LoadTemplates() LOG_INFO("playerbots", "{} playerbots texts loaded", count); } -std::string const PlayerbotTextMgr::Format(std::string const key, std::map placeholders) +void PlayerbotTextMgr::LoadBotTextChance() { - if (templates.empty()) - LoadTemplates(); - - std::vector& list = templates[key]; - if (list.empty()) + if (botTextChance.empty()) { - std::ostringstream out; - out << "Unknown text: " << key; - return out.str(); + QueryResult results = PlayerbotsDatabase.Query("SELECT name, probability FROM ai_playerbot_texts_chance"); + if (results) + { + do + { + Field* fields = results->Fetch(); + std::string name = fields[0].Get(); + uint32 probability = fields[1].Get(); + + botTextChance[name] = probability; + } while (results->NextRow()); + } + } +} + +// general texts + +std::string PlayerbotTextMgr::GetBotText(std::string name) +{ + if (botTexts.empty()) + { + LOG_ERROR("playerbots", "Can't get bot text {}! No bots texts loaded!", name); + return ""; } - std::string str = list[urand(0, list.size() - 1)]; - for (std::map::iterator i = placeholders.begin(); i != placeholders.end(); ++i) - replaceAll(str, i->first, i->second); + if (botTexts[name].empty()) + { + LOG_ERROR("playerbots", "Can't get bot text {}! No bots texts for this name!", name); + return ""; + } - return str; + std::vector& list = botTexts[name]; + BotTextEntry textEntry = list[urand(0, list.size() - 1)]; + return !textEntry.m_text[GetLocalePriority()].empty() ? textEntry.m_text[GetLocalePriority()] : textEntry.m_text[0]; +} + +std::string PlayerbotTextMgr::GetBotText(std::string name, std::map placeholders) +{ + std::string botText = GetBotText(name); + if (botText.empty()) + return ""; + + for (std::map::iterator i = placeholders.begin(); i != placeholders.end(); ++i) + replaceAll(botText, i->first, i->second); + + return botText; +} + +// chat replies + +std::string PlayerbotTextMgr::GetBotText(ChatReplyType replyType, std::map placeholders) +{ + if (botTexts.empty()) + { + LOG_ERROR("playerbots", "Can't get bot text reply {}! No bots texts loaded!", replyType); + return ""; + } + if (botTexts["reply"].empty()) + { + LOG_ERROR("playerbots", "Can't get bot text reply {}! No bots texts replies!", replyType); + return ""; + } + + std::vector& list = botTexts["reply"]; + std::vector proper_list; + for (auto text : list) + { + if (text.m_replyType == replyType) + proper_list.push_back(text); + } + + BotTextEntry textEntry = proper_list[urand(0, proper_list.size() - 1)]; + std::string botText = !textEntry.m_text[GetLocalePriority()].empty() ? textEntry.m_text[GetLocalePriority()] : textEntry.m_text[0]; + for (auto & placeholder : placeholders) + replaceAll(botText, placeholder.first, placeholder.second); + + return botText; +} + + +std::string PlayerbotTextMgr::GetBotText(ChatReplyType replyType, std::string name) +{ + std::map placeholders; + placeholders["%s"] = name; + + return GetBotText(replyType, placeholders); +} + +// probabilities + +bool PlayerbotTextMgr::rollTextChance(std::string name) +{ + if (!botTextChance[name]) + return true; + + return urand(0, 100) < botTextChance[name]; +} + +bool PlayerbotTextMgr::GetBotText(std::string name, std::string &text) +{ + if (!rollTextChance(name)) + return false; + + text = GetBotText(name); + return !text.empty(); +} + +bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map placeholders) +{ + if (!rollTextChance(name)) + return false; + + text = GetBotText(name, placeholders); + return !text.empty(); +} + + +void PlayerbotTextMgr::AddLocalePriority(uint32 locale) +{ + if (!locale) + return; + + botTextLocalePriority[locale]++; +} + +uint32 PlayerbotTextMgr::GetLocalePriority() +{ + uint32 topLocale = 0; + + // if no real players online, reset top locale + if (!sWorld->GetActiveSessionCount()) + { + ResetLocalePriority(); + return 0; + } + + for (uint8 i = 0; i < MAX_LOCALES; ++i) + { + if (botTextLocalePriority[i] > topLocale) + topLocale = i; + } + return topLocale; +} + +void PlayerbotTextMgr::ResetLocalePriority() +{ + for (uint8 i = 0; i < MAX_LOCALES; ++i) + { + botTextLocalePriority[i] = 0; + } } diff --git a/src/PlayerbotTextMgr.h b/src/PlayerbotTextMgr.h index 800fc0a6..fc94b068 100644 --- a/src/PlayerbotTextMgr.h +++ b/src/PlayerbotTextMgr.h @@ -10,10 +10,57 @@ #include #include +#define BOT_TEXT1(name) sPlayerbotTextMgr->GetBotText(name) +#define BOT_TEXT2(name, replace) sPlayerbotTextMgr->GetBotText(name, replace) + +struct BotTextEntry +{ + BotTextEntry(std::string name, std::map text, uint32 say_type, uint32 reply_type) : m_name(name), m_text(text), m_sayType(say_type), m_replyType(reply_type) {} + std::string m_name; + std::map m_text; + uint32 m_sayType; + uint32 m_replyType; +}; + +struct ChatReplyData +{ + ChatReplyData(uint32 guid, uint32 type, std::string chat) : m_guid(guid), m_type(type), m_chat(chat) {} + uint32 m_type, m_guid = 0; + std::string m_chat = ""; +}; + +struct ChatQueuedReply +{ + ChatQueuedReply(uint32 type, uint32 guid1, uint32 guid2, std::string msg, std::string chanName, std::string name, time_t time) : m_type(type), m_guid1(guid1), m_guid2(guid2), m_msg(msg), m_chanName(chanName), m_name(name), m_time(time) {} + uint32 m_type; + uint32 m_guid1; + uint32 m_guid2; + std::string m_msg; + std::string m_chanName; + std::string m_name; + time_t m_time; +}; + +enum ChatReplyType +{ + REPLY_NOT_UNDERSTAND, + REPLY_GRUDGE, + REPLY_VICTIM, + REPLY_ATTACKER, + REPLY_HELLO, + REPLY_NAME, + REPLY_ADMIN_ABUSE +}; + class PlayerbotTextMgr { public: - PlayerbotTextMgr() { }; + PlayerbotTextMgr() { + for (uint8 i = 0; i < MAX_LOCALES; ++i) + { + botTextLocalePriority[i] = 0; + } + }; virtual ~PlayerbotTextMgr() { }; static PlayerbotTextMgr* instance() { @@ -21,12 +68,25 @@ class PlayerbotTextMgr return &instance; } - std::string const Format(std::string const key, std::map placeholders); + std::string GetBotText(std::string name, std::map placeholders); + std::string GetBotText(std::string name); + std::string GetBotText(ChatReplyType replyType, std::map placeholders); + std::string GetBotText(ChatReplyType replyType, std::string name); + bool GetBotText(std::string name, std::string& text); + bool GetBotText(std::string name, std::string& text, std::map placeholders); + void LoadBotTexts(); + void LoadBotTextChance(); + static void replaceAll(std::string& str, const std::string& from, const std::string& to); + bool rollTextChance(std::string text); + + uint32 GetLocalePriority(); + void AddLocalePriority(uint32 locale); + void ResetLocalePriority(); private: - void LoadTemplates(); - - std::map> templates; + std::map> botTexts; + std::map botTextChance; + uint32 botTextLocalePriority[MAX_LOCALES]; }; #define sPlayerbotTextMgr PlayerbotTextMgr::instance() diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index dddb78e0..6a31c4d9 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -22,6 +22,7 @@ #include "PlayerbotCommandServer.h" #include "PlayerbotFactory.h" #include "ServerFacade.h" +#include "ChannelMgr.h" #include #include @@ -1693,11 +1694,24 @@ bool RandomPlayerbotMgr::HandlePlayerbotConsoleCommand(ChatHandler* handler, cha return true; } -void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Player* fromPlayer) +void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Player* fromPlayer, std::string channelName) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { Player* const bot = it->second; + if (!bot) + continue; + + if (!channelName.empty()) + { + if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId())) + { + Channel* chn = cMgr->GetChannel(channelName, bot); + if (!chn) + continue; + } + } + GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); } } diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 3d354f93..e66e5b20 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -51,7 +51,7 @@ class RandomPlayerbotMgr : public PlayerbotHolder void IncreaseLevel(Player* bot); void ScheduleTeleport(uint32 bot, uint32 time = 0); void ScheduleChangeStrategy(uint32 bot, uint32 time = 0); - void HandleCommand(uint32 type, std::string const text, Player* fromPlayer); + void HandleCommand(uint32 type, std::string const text, Player* fromPlayer, std::string channelName = ""); std::string const HandleRemoteCommand(std::string const request); void OnPlayerLogout(Player* player); void OnPlayerLogin(Player* player); diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index ea5c7196..7e89e23b 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -53,7 +53,6 @@ #include "SayAction.h" #include "StayActions.h" #include "SuggestWhatToDoAction.h" -#include "SuggestDungeonAction.h" #include "TravelAction.h" #include "XpGainAction.h" #include "VehicleActions.h" @@ -76,7 +75,6 @@ class ActionContext : public NamedObjectContext creators["choose travel target"] = &ActionContext::choose_travel_target; creators["move to travel target"] = &ActionContext::move_to_travel_target; creators["move out of collision"] = &ActionContext::move_out_of_collision; - creators["move out of collision"] = &ActionContext::move_out_of_collision; creators["move random"] = &ActionContext::move_random; creators["attack"] = &ActionContext::melee; creators["melee"] = &ActionContext::melee; @@ -118,7 +116,6 @@ class ActionContext : public NamedObjectContext creators["emote"] = &ActionContext::emote; creators["talk"] = &ActionContext::talk; creators["suggest what to do"] = &ActionContext::suggest_what_to_do; - creators["suggest dungeon"] = &ActionContext::suggest_dungeon; creators["suggest trade"] = &ActionContext::suggest_trade; creators["return"] = &ActionContext::_return; creators["move to loot"] = &ActionContext::move_to_loot; @@ -138,12 +135,12 @@ class ActionContext : public NamedObjectContext creators["greet"] = &ActionContext::greet; creators["check values"] = &ActionContext::check_values; creators["ra"] = &ActionContext::ra; - creators["give food"] = &ActionContext::give_food; - creators["give water"] = &ActionContext::give_water; creators["apply poison"] = &ActionContext::apply_poison; creators["apply stone"] = &ActionContext::apply_stone; creators["apply oil"] = &ActionContext::apply_oil; creators["try emergency"] = &ActionContext::try_emergency; + creators["give food"] = &ActionContext::give_food; + creators["give water"] = &ActionContext::give_water; creators["mount"] = &ActionContext::mount; creators["war stomp"] = &ActionContext::war_stomp; creators["auto talents"] = &ActionContext::auto_talents; @@ -266,7 +263,6 @@ class ActionContext : public NamedObjectContext static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); } static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); } static Action* suggest_what_to_do(PlayerbotAI* botAI) { return new SuggestWhatToDoAction(botAI); } - static Action* suggest_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(botAI); } static Action* suggest_trade(PlayerbotAI* botAI) { return new SuggestTradeAction(botAI); } static Action* attack_anything(PlayerbotAI* botAI) { return new AttackAnythingAction(botAI); } static Action* attack_least_hp_target(PlayerbotAI* botAI) { return new AttackLeastHpTargetAction(botAI); } diff --git a/src/strategy/actions/EmoteAction.cpp b/src/strategy/actions/EmoteAction.cpp index 9a2f8c9a..c1cb7596 100644 --- a/src/strategy/actions/EmoteAction.cpp +++ b/src/strategy/actions/EmoteAction.cpp @@ -6,6 +6,7 @@ #include "Event.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "PlayerbotTextMgr.h" std::map EmoteActionBase::emotes; std::map EmoteActionBase::textEmotes; @@ -778,7 +779,7 @@ bool EmoteAction::isUseful() return false; time_t lastEmote = AI_VALUE2(time_t, "last emote", qualifier); - return (time(nullptr) - lastEmote) >= sPlayerbotAIConfig->repeatDelay / 1000; + return time(nullptr) >= lastEmote; } bool TalkAction::Execute(Event event) diff --git a/src/strategy/actions/SayAction.cpp b/src/strategy/actions/SayAction.cpp index f44e8d1d..ba601e86 100644 --- a/src/strategy/actions/SayAction.cpp +++ b/src/strategy/actions/SayAction.cpp @@ -5,125 +5,26 @@ #include "SayAction.h" #include "Event.h" #include "Playerbots.h" - -std::map> SayAction::stringTable; -std::map SayAction::probabilityTable; +#include "PlayerbotTextMgr.h" +#include "ChannelMgr.h" +#include "GuildMgr.h" +#include SayAction::SayAction(PlayerbotAI* botAI) : Action(botAI, "say"), Qualified() { } -void replaceAll(std::string& str, std::string const from, std::string const 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()) - { - if (PreparedQueryResult result = PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_SPEECH))) - { - do - { - Field* fields = result->Fetch(); - std::string const name = fields[0].Get(); - std::string text = fields[1].Get(); - std::string const type = fields[2].Get(); - - if (type == "yell") - text = "/y " + text; - - if (!text.empty() && text != "") - stringTable[name].push_back(std::move(text)); - } - while (result->NextRow()); - } - } - - if (probabilityTable.empty()) - { - if (PreparedQueryResult result = PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_SPEECH_PROBABILITY))) - { - do - { - Field* fields = result->Fetch(); - std::string const name = fields[0].Get(); - uint32 probability = fields[1].Get(); - - probabilityTable[name] = probability; - } - while (result->NextRow()); - } - } - - std::vector& strings = stringTable[qualifier]; - if (strings.empty()) - return false; - - time_t lastSaid = AI_VALUE2(time_t, "last said", qualifier); - uint32 nextTime = time(nullptr) + urand(1, 30); - botAI->GetAiObjectContext()->GetValue("last said", qualifier)->Set(nextTime); - - if (Group* group = bot->GetGroup()) - { - std::vector members; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (PlayerbotAI* memberAi = GET_PLAYERBOT_AI(member)) - members.push_back(member); - } - - uint32 count = members.size(); - if (count > 1) - { - for (uint32 i = 0; i < count * 5; i++) - { - uint32 i1 = urand(0, count - 1); - uint32 i2 = urand(0, count - 1); - - Player* item = members[i1]; - members[i1] = members[i2]; - members[i2] = item; - } - } - - uint32 index = 0; - for (Player* player : members) - { - PlayerbotAI* memberAi = GET_PLAYERBOT_AI(player); - memberAi->GetAiObjectContext()->GetValue("last said", qualifier)->Set(nextTime + (20 * ++index) + urand(1, 15)); - } - } - - uint32 probability = probabilityTable[qualifier]; - if (!probability) - probability = 100; - - if (urand(0, 100) >= probability) - return false; - - uint32 idx = urand(0, strings.size() - 1); - std::string text = strings[idx]; - + std::string text = ""; + std::map placeholders; 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"); - + // set replace strings + if (target) placeholders[""] = target->GetName(); + placeholders[""] = IsAlliance(bot->getRace()) ? "Alliance" : "Horde"; if (qualifier == "low ammo" || qualifier == "no ammo") { if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) @@ -131,11 +32,11 @@ bool SayAction::Execute(Event event) switch (pItem->GetTemplate()->SubClass) { case ITEM_SUBCLASS_WEAPON_GUN: - replaceAll(text, "", "bullets"); + placeholders[""] = "bullets"; break; case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW: - replaceAll(text, "", "arrows"); + placeholders[""] = "arrows"; break; } } @@ -144,9 +45,53 @@ bool SayAction::Execute(Event event) if (bot->GetMap()) { if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetAreaId())) - replaceAll(text, "", area->area_name[0]); + placeholders[""] = area->area_name[0]; } + + // set delay before next say + time_t lastSaid = AI_VALUE2(time_t, "last said", qualifier); + uint32 nextTime = time(nullptr) + urand(1, 30); + botAI->GetAiObjectContext()->GetValue("last said", qualifier)->Set(nextTime); + + Group* group = bot->GetGroup(); + if (group) + { + std::vector members; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + PlayerbotAI* memberAi = GET_PLAYERBOT_AI(member); + if (memberAi) members.push_back(member); + } + + uint32 count = members.size(); + if (count > 1) + { + for (uint32 i = 0; i < count * 5; i++) + { + int i1 = urand(0, count - 1); + int i2 = urand(0, count - 1); + + Player* item = members[i1]; + members[i1] = members[i2]; + members[i2] = item; + } + } + + int index = 0; + for (auto & member : members) + { + PlayerbotAI* memberAi = GET_PLAYERBOT_AI(member); + if (memberAi) + memberAi->GetAiObjectContext()->GetValue("last said", qualifier)->Set(nextTime + (20 * ++index) + urand(1, 15)); + } + } + + // load text based on chance + if (!sPlayerbotTextMgr->GetBotText(qualifier, text, placeholders)) + return false; + if (text.find("/y ") == 0) bot->Yell(text.substr(3), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); else @@ -163,3 +108,540 @@ bool SayAction::isUseful() time_t lastSaid = AI_VALUE2(time_t, "last said", qualifier); return (time(nullptr) - lastSaid) > 30; } + +void ChatReplyAction::ChatReplyDo(Player* bot, uint32 type, uint32 guid1, uint32 guid2, std::string msg, std::string chanName, std::string name) +{ + ChatReplyType replyType = REPLY_NOT_UNDERSTAND; // default not understand + std::string respondsText = ""; + + // Chat Logic + int32 verb_pos = -1; + int32 verb_type = -1; + int32 is_quest = 0; + bool found = false; + std::stringstream text(msg); + std::string segment; + std::vector word; + while (std::getline(text, segment, ' ')) + { + word.push_back(segment); + } + + for (uint32 i = 0; i < 15; i++) + { + if (word.size() < i) + word.push_back(""); + } + + if (msg.find("?") != std::string::npos) + is_quest = 1; + if (word[0].find("what") != std::string::npos) + is_quest = 2; + else if (word[0].find("who") != std::string::npos) + is_quest = 3; + else if (word[0] == "when") + is_quest = 4; + else if (word[0] == "where") + is_quest = 5; + else if (word[0] == "why") + is_quest = 6; + + // Responds + for (uint32 i = 0; i < 8; i++) + { +// // blame gm with chat tag +// if (Player* plr = sObjectMgr->GetPlayer(ObjectGuid(HIGHGUID_PLAYER, guid1))) +// { +// if (plr->isGMChat()) +// { +// replyType = REPLY_ADMIN_ABUSE; +// found = true; +// break; +// } +// } +// + if (word[i] == "hi" || word[i] == "hey" || word[i] == "hello" || word[i] == "wazzup") + { + replyType = REPLY_HELLO; + found = true; + break; + } + + if (verb_type < 4) + { + if (word[i] == "am" || word[i] == "are" || word[i] == "is") + { + verb_pos = i; + verb_type = 2; // present + } + else if (word[i] == "will") + { + verb_pos = i; + verb_type = 3; // future + } + else if (word[i] == "was" || word[i] == "were") + { + verb_pos = i; + verb_type = 1; // past + } + else if (word[i] == "shut" || word[i] == "noob") + { + if (msg.find(bot->GetName()) == std::string::npos) + { + continue; // not react + uint32 rnd = urand(0, 2); + std::string msg = ""; + if (rnd == 0) + msg = "sorry %s, ill shut up now"; + if (rnd == 1) + msg = "ok ok %s"; + if (rnd == 2) + msg = "fine, i wont talk to you anymore %s"; + + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + else + { + replyType = REPLY_GRUDGE; + found = true; + break; + } + } + } + } + if (verb_type < 4 && is_quest && !found) + { + switch (is_quest) + { + case 2: + { + uint32 rnd = urand(0, 3); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "i dont know what"; + break; + case 1: + msg = "i dont know %s"; + break; + case 2: + msg = "who cares"; + break; + case 3: + msg = "afraid that was before i was around or paying attention"; + break; + } + + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 3: + { + uint32 rnd = urand(0, 4); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "nobody"; + break; + case 1: + msg = "we all do"; + break; + case 2: + msg = "perhaps its you, %s"; + break; + case 3: + msg = "dunno %s"; + break; + case 4: + msg = "is it me?"; + break; + } + + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 4: + { + uint32 rnd = urand(0, 6); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "soon perhaps %s"; + break; + case 1: + msg = "probably later"; + break; + case 2: + msg = "never"; + break; + case 3: + msg = "what do i look like, a psychic?"; + break; + case 4: + msg = "a few minutes, maybe an hour ... years?"; + break; + case 5: + msg = "when? good question %s"; + break; + case 6: + msg = "dunno %s"; + break; + } + + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 5: + { + uint32 rnd = urand(0, 6); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "really want me to answer that?"; + break; + case 1: + msg = "on the map?"; + break; + case 2: + msg = "who cares"; + break; + case 3: + msg = "afk?"; + break; + case 4: + msg = "none of your buisiness where"; + break; + case 5: + msg = "yeah, where?"; + break; + case 6: + msg = "dunno %s"; + break; + } + + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 6: + { + uint32 rnd = urand(0, 6); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "dunno %s"; + break; + case 1: + msg = "why? just because %s"; + break; + case 2: + msg = "why is the sky blue?"; + break; + case 3: + msg = "dont ask me %s, im just a bot"; + break; + case 4: + msg = "your asking the wrong person"; + break; + case 5: + msg = "who knows?"; + break; + case 6: + msg = "dunno %s"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + default: + { + switch (verb_type) + { + case 1: + { + uint32 rnd = urand(0, 3); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "its true, " + word[verb_pos + 1] + " " + word[verb_pos] + " " + word[verb_pos + 2] + " " + word[verb_pos + 3] + " " + word[verb_pos + 4] + " " + word[verb_pos + 4]; + break; + case 1: + msg = "ya %s but thats in the past"; + break; + case 2: + msg = "nah, but " + word[verb_pos + 1] + " will " + word[verb_pos + 3] + " again though %s"; + break; + case 3: + msg = "afraid that was before i was around or paying attention"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 2: + { + uint32 rnd = urand(0, 6); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "its true, " + word[verb_pos + 1] + " " + word[verb_pos] + " " + word[verb_pos + 2] + " " + word[verb_pos + 3] + " " + word[verb_pos + 4] + " " + word[verb_pos + 5]; + break; + case 1: + msg = "ya %s thats true"; + break; + case 2: + msg = "maybe " + word[verb_pos + 1] + " " + word[verb_pos] + " " + word[verb_pos + 2] + " " + word[verb_pos + 3] + " " + word[verb_pos + 4] + " " + word[verb_pos + 5]; + break; + case 3: + msg = "dunno %s"; + break; + case 4: + msg = "i dont think so %s"; + break; + case 5: + msg = "yes"; + break; + case 6: + msg = "no"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 3: + { + uint32 rnd = urand(0, 8); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "dunno %s"; + break; + case 1: + msg = "beats me %s"; + break; + case 2: + msg = "how should i know %s"; + break; + case 3: + msg = "dont ask me %s, im just a bot"; + break; + case 4: + msg = "your asking the wrong person"; + break; + case 5: + msg = "what do i look like, a psychic?"; + break; + case 6: + msg = "sure %s"; + break; + case 7: + msg = "i dont think so %s"; + break; + case 8: + msg = "maybe"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + } + } + } + } + else if (!found) + { + switch (verb_type) + { + case 1: + { + uint32 rnd = urand(0, 2); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "yeah %s, the key word being " + word[verb_pos] + " " + word[verb_pos + 1]; + break; + case 1: + msg = "ya %s but thats in the past"; + break; + case 2: + msg = word[verb_pos - 1] + " will " + word[verb_pos + 1] + " again though %s"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 2: + { + uint32 rnd = urand(0, 2); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "%s, what do you mean " + word[verb_pos + 1] + "?"; + break; + case 1: + msg = "%s, what is a " + word[verb_pos + 1] + "?"; + break; + case 2: + msg = "yeah i know " + word[verb_pos - 1] + " is a " + word[verb_pos + 1]; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + case 3: + { + uint32 rnd = urand(0, 1); + std::string msg = ""; + + switch (rnd) + { + case 0: + msg = "are you sure thats going to happen %s?"; + break; + case 1: + msg = "%s, what will happen %s?"; + break; + case 2: + msg = "are you saying " + word[verb_pos - 1] + " will " + word[verb_pos + 1] + " " + word[verb_pos + 2] + " %s?"; + break; + } + msg = std::regex_replace(msg, std::regex("%s"), name); + respondsText = msg; + found = true; + break; + } + } + } + + if (!found) + { + // Name Responds + if (msg.find(bot->GetName()) != std::string::npos) + { + replyType = REPLY_NAME; + found = true; + } + else // Does not understand + { + replyType = REPLY_NOT_UNDERSTAND; + found = true; + } + } + + // send responds + // + if (found) + { + // load text if needed + if (respondsText.empty()) + { + respondsText = BOT_TEXT2(replyType, name); + } + const char* c = respondsText.c_str(); + if (strlen(c) > 255) + return; + + if (chanName == "World") + { + if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId())) + { + std::string worldChan = "World"; + if (Channel* chn = cMgr->GetJoinChannel(worldChan.c_str(), 0)) + if (bot->GetTeamId() == TEAM_ALLIANCE) + chn->Say(bot->GetGUID(), c, LANG_COMMON); + else + chn->Say(bot->GetGUID(), c, LANG_ORCISH); + } + } + else + { + if (type == CHAT_MSG_WHISPER) + { + ObjectGuid receiver = sCharacterCache->GetCharacterGuidByName(name); + if (!receiver.IsPlayer()) + { + return; + } + if (bot->GetTeamId() == TEAM_ALLIANCE) + { + bot->Whisper(c, LANG_COMMON, ObjectAccessor::FindPlayer(receiver)); + } + else + { + bot->Whisper(c, LANG_ORCISH, ObjectAccessor::FindPlayer(receiver)); + } + } + + if (type == CHAT_MSG_SAY) + { + if (bot->GetTeamId() == TEAM_ALLIANCE) + bot->Say(respondsText, LANG_COMMON); + else + bot->Say(respondsText, LANG_ORCISH); + } + + if (type == CHAT_MSG_YELL) + { + if (bot->GetTeamId() == TEAM_ALLIANCE) + bot->Yell(respondsText, LANG_COMMON); + else + bot->Yell(respondsText, LANG_ORCISH); + } + + if (type == CHAT_MSG_GUILD) + { + if (!bot->GetGuildId()) + return; + + Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); + if (!guild) + return; + + guild->BroadcastToGuild(bot->GetSession(), false, respondsText, LANG_UNIVERSAL); + } + } + GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(nullptr) + urand(5, 25)); + } +} \ No newline at end of file diff --git a/src/strategy/actions/SayAction.h b/src/strategy/actions/SayAction.h index eb263096..5afe127f 100644 --- a/src/strategy/actions/SayAction.h +++ b/src/strategy/actions/SayAction.h @@ -24,4 +24,13 @@ class SayAction : public Action, public Qualified static std::map probabilityTable; }; + +class ChatReplyAction : public Action +{ +public: + ChatReplyAction(PlayerbotAI* ai) : Action(ai, "chat message") {} + virtual bool Execute(Event event) { return true; } + bool isUseful() { return true; } + static void ChatReplyDo(Player* bot, uint32 type, uint32 guid1, uint32 guid2, std::string msg, std::string chanName, std::string name); +}; #endif diff --git a/src/strategy/actions/SuggestDungeonAction.cpp b/src/strategy/actions/SuggestDungeonAction.cpp deleted file mode 100644 index 817dc545..00000000 --- a/src/strategy/actions/SuggestDungeonAction.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - */ - -#include "SuggestDungeonAction.h" -#include "AiFactory.h" -#include "Player.h" -#include "Playerbots.h" -#include "PlayerbotTextMgr.h" -#include "PlayerbotDungeonSuggestionMgr.h" - -DungeonSuggestions SuggestDungeonAction::m_dungeonSuggestions; - -SuggestDungeonAction::SuggestDungeonAction(PlayerbotAI* botAI) : - SuggestWhatToDoAction(botAI, "suggest instance") -{ - if (m_dungeonSuggestions.empty()) - { - m_dungeonSuggestions = sPlayerbotDungeonSuggestionMgr->GetDungeonSuggestions(); - } -} - -bool SuggestDungeonAction::Execute(Event event) -{ - bool const isRealPlayer = !sRandomPlayerbotMgr->IsRandomBot(bot); - bool const isInGroup = bot->GetGroup(); - bool const isInInstance = bot->GetInstanceId(); - if (isRealPlayer || isInGroup || isInInstance) - { - return false; - } - - DungeonSuggestions const dungeonSuggestions = GetDungeonSuggestionsEligibleFor(bot); - if (dungeonSuggestions.empty()) - { - return false; - } - - uint32 const randomDungeonIndex = urand(0, dungeonSuggestions.size() - 1); - DungeonSuggestion const* dungeonSuggestion = &dungeonSuggestions[randomDungeonIndex]; - PlaceholderMap const placeholders = MapPlaceholders(bot, dungeonSuggestion); - std::string playerbotsTextKey = PlayerbotsTextKeyByMapKey(placeholders); - std::string message = sPlayerbotTextMgr->Format(playerbotsTextKey, placeholders); - bool isRandomlyLowerCase = sPlayerbotAIConfig->suggestDungeonsInLowerCaseRandomly - ? urand(0, 1) - : false; - spam(message, 1, isRandomlyLowerCase); - - return true; -} - -DungeonSuggestions const SuggestDungeonAction::GetDungeonSuggestionsEligibleFor(Player* bot) -{ - DungeonSuggestions dungeonSuggestionsEligibleFor; - for (DungeonSuggestions::const_iterator i = m_dungeonSuggestions.begin(); - i != m_dungeonSuggestions.end(); ++i) - { - uint8 const level = bot->getLevel(); - bool const isEligible = level >= i->min_level && level <= i->max_level; - if (isEligible) - { - dungeonSuggestionsEligibleFor.push_back(*i); - } - } - - return dungeonSuggestionsEligibleFor; -} - -PlaceholderMap SuggestDungeonAction::MapPlaceholders( - Player* bot, - DungeonSuggestion const* dungeonSuggestion -) -{ - PlaceholderMap placeholders; - bool const isRandomlyMappingRole = urand(0, 1); - if (isRandomlyMappingRole) - { - PlaceholderHelper::MapRole(placeholders, bot); - } - PlaceholderHelper::MapDungeon(placeholders, dungeonSuggestion, bot); - - return placeholders; -} - -std::string SuggestDungeonAction::PlayerbotsTextKeyByMapKey(PlaceholderMap const& placeholders) -{ - bool const isRoleMapped = placeholders.find("%role") != placeholders.end(); - std::string playerbotsTextKey = "suggest_dungeon"; - if (isRoleMapped) - { - playerbotsTextKey = "suggest_dungeon_role"; - } - - return playerbotsTextKey; -} diff --git a/src/strategy/actions/SuggestDungeonAction.h b/src/strategy/actions/SuggestDungeonAction.h deleted file mode 100644 index aacaa37c..00000000 --- a/src/strategy/actions/SuggestDungeonAction.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version. - */ - -#ifndef _PLAYERBOT_SUGGESTINSTANCEACTION_H -#define _PLAYERBOT_SUGGESTINSTANCEACTION_H - -#include "SuggestWhatToDoAction.h" -#include "PlayerbotDungeonSuggestionMgr.h" -#include "PlaceholderHelper.h" - -typedef std::vector DungeonSuggestions; - -class SuggestDungeonAction : public SuggestWhatToDoAction -{ - public: - SuggestDungeonAction(PlayerbotAI* botAI); - - bool Execute(Event event) override; - bool isUseful() override { return true; } - - private: - static DungeonSuggestions m_dungeonSuggestions; - - DungeonSuggestions const GetDungeonSuggestionsEligibleFor(Player* bot); - PlaceholderMap MapPlaceholders( - Player* bot, - DungeonSuggestion const* dungeonSuggestion - ); - std::string PlayerbotsTextKeyByMapKey(PlaceholderMap const& placeholders); -}; - -#endif diff --git a/src/strategy/actions/SuggestWhatToDoAction.cpp b/src/strategy/actions/SuggestWhatToDoAction.cpp index 6846e5c6..c2a7acfc 100644 --- a/src/strategy/actions/SuggestWhatToDoAction.cpp +++ b/src/strategy/actions/SuggestWhatToDoAction.cpp @@ -10,7 +10,9 @@ #include "ChatHelper.h" #include "Playerbots.h" #include "PlayerbotTextMgr.h" +#include "GuildMgr.h" +std::map SuggestWhatToDoAction::instances; std::map SuggestWhatToDoAction::factions; SuggestWhatToDoAction::SuggestWhatToDoAction(PlayerbotAI* botAI, std::string const name) : InventoryAction(botAI, name) @@ -35,6 +37,73 @@ bool SuggestWhatToDoAction::Execute(Event event) return true; } +void SuggestWhatToDoAction::instance() +{ + if (instances.empty()) + { + instances["Ragefire Chasm"] = 15; + instances["Deadmines"] = 18; + instances["Wailing Caverns"] = 18; + instances["Shadowfang Keep"] = 25; + instances["Blackfathom Deeps"] = 20; + instances["Stockade"] = 20; + instances["Gnomeregan"] = 35; + instances["Razorfen Kraul"] = 35; + instances["Maraudon"] = 50; + instances["Scarlet Monestery"] = 40; + instances["Uldaman"] = 45; + instances["Dire Maul"] = 58; + instances["Scholomance"] = 59; + instances["Razorfen Downs"] = 40; + instances["Strathholme"] = 59; + instances["Zul'Farrak"] = 45; + instances["Blackrock Depths"] = 55; + instances["Temple of Atal'Hakkar"] = 55; + instances["Lower Blackrock Spire"] = 57; + + instances["Hellfire Citidel"] = 65; + instances["Coilfang Reservoir"] = 65; + instances["Auchindoun"] = 65; + instances["Cavens of Time"] = 68; + instances["Tempest Keep"] = 69; + instances["Magister's Terrace"] = 70; + + instances["Utgarde Keep"] = 75; + instances["The Nexus"] = 75; + instances["Ahn'kahet: The Old Kingdom"] = 75; + instances["Azjol-Nerub"] = 75; + instances["Drak'Tharon Keep"] = 75; + instances["Violet Hold"] = 80; + instances["Gundrak"] = 77; + instances["Halls of Stone"] = 77; + instances["Halls of Lightning"] = 77; + instances["Oculus"] = 77; + instances["Utgarde Pinnacle"] = 77; + instances["Trial of the Champion"] = 80; + instances["Forge of Souls"] = 80; + instances["Pit of Saron"] = 80; + instances["Halls of Reflection"] = 80; + } + + std::vector allowedInstances; + for (auto & instance : instances) + { + if (bot->getLevel() >= instance.second) allowedInstances.push_back(instance.first); + } + + if (allowedInstances.empty()) return; + + std::map placeholders; + placeholders["%role"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot)); + + std::ostringstream itemout; + //itemout << "|c00b000b0" << allowedInstances[urand(0, allowedInstances.size() - 1)] << "|r"; + itemout << allowedInstances[urand(0, allowedInstances.size() - 1)]; + placeholders["%instance"] = itemout.str(); + + spam(BOT_TEXT2("suggest_instance", placeholders), urand(0, 1) ? 0x50 : 0, urand(0, 2), urand(0, 2)); +} + std::vector SuggestWhatToDoAction::GetIncompletedQuests() { std::vector result; @@ -67,7 +136,7 @@ void SuggestWhatToDoAction::specificQuest() placeholders["%role"] = chat->FormatClass(bot, AiFactory::GetPlayerSpecTab(bot)); placeholders["%quest"] = chat->FormatQuest(quest); - spam(sPlayerbotTextMgr->Format("suggest_quest", placeholders)); + spam(BOT_TEXT2("suggest_quest", placeholders), urand(0, 1) ? 0x18 : 0, urand(0, 2), urand(0, 2)); } void SuggestWhatToDoAction::grindReputation() @@ -134,10 +203,11 @@ void SuggestWhatToDoAction::grindReputation() placeholders["%rndK"] = rnd.str(); std::ostringstream itemout; - itemout << "|c004040b0" << allowedFactions[urand(0, allowedFactions.size() - 1)] << "|r"; +// itemout << "|c004040b0" << allowedFactions[urand(0, allowedFactions.size() - 1)] << "|r"; + itemout << allowedFactions[urand(0, allowedFactions.size() - 1)]; placeholders["%faction"] = itemout.str(); - spam(sPlayerbotTextMgr->Format("suggest_faction", placeholders)); + spam(BOT_TEXT2("suggest_faction", placeholders), 0x18, true); } void SuggestWhatToDoAction::something() @@ -150,48 +220,88 @@ void SuggestWhatToDoAction::something() return; std::ostringstream out; - out << "|cffb04040" << entry->area_name[0] << "|r"; +// out << "|cffb04040" << entry->area_name[0] << "|r"; + out << entry->area_name[0]; placeholders["%zone"] = out.str(); - spam(sPlayerbotTextMgr->Format("suggest_something", placeholders)); + spam(BOT_TEXT2("suggest_something", placeholders), urand(0, 1) ? 0x18 : 0, urand(0, 2), urand(0, 2)); } -void SuggestWhatToDoAction::spam( - std::string msg, - uint32 channelId, - bool const isLowerCase -) +void SuggestWhatToDoAction::spam(std::string msg, uint8 flags, bool worldChat, bool guild) { - std::set said; + if (msg.empty()) + return; + + std::vector channelNames; + ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()); + if (!cMgr) + return; + + for (uint32 i = 0; i < sChatChannelsStore.GetNumRows(); ++i) { ChatChannelsEntry const* channel = sChatChannelsStore.LookupEntry(i); - if (!channel || channel->ChannelID != channelId) continue; + if (!channel) continue; - for (AreaTableEntry const* area : sAreaTableStore) + for (AreaTableEntry const* current_zone : sAreaTableStore) { - char channelName[255]; - snprintf(channelName, 255, channel->pattern[0], area->area_name[0]); - if (said.find(channelName) != said.end()) + if (!current_zone) + continue; + + + // combine full channel name + char channelName[100]; + Channel* chn = nullptr; + if ((channel->flags & CHANNEL_DBC_FLAG_LFG) != 0) + { + std::string chanName = channel->pattern[0]; + chn = cMgr->GetChannel(chanName, bot); + } + else + { + snprintf(channelName, 100, channel->pattern[0], current_zone->area_name[0]); + chn = cMgr->GetChannel(channelName, bot); + } + if (!chn) + continue; + // skip world chat here + if (chn->GetName() == "World") continue; - said.insert(channelName); + if (flags != 0 && chn->GetFlags() != flags) + continue; - if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId())) + // skip local defense + //if (chn->GetFlags() == 0x18) + // continue; + + // no filter, pick several options + if (flags == CHANNEL_FLAG_NONE) { - if (Channel* chn = cMgr->GetJoinChannel(channelName, channel->ChannelID)) - { - chn->JoinChannel(bot, ""); - if (isLowerCase) - { - strToLower(msg); - } - if (chn->GetName().length() > 0) - { - chn->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); - } - } + channelNames.push_back(chn->GetName()); } + else + chn->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); + } + + if (!channelNames.empty()) + { + std::string randomName = channelNames[urand(0, channelNames.size() - 1)]; + if (Channel* chn = cMgr->GetChannel(randomName, bot)) + chn->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); + } + + if (worldChat) + { + if (Channel* worldChannel = cMgr->GetChannel("World", bot)) + worldChannel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); + } + + if (guild && bot->GetGuildId()) + { + Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); + if (guild) + guild->BroadcastToGuild(bot->GetSession(), false, msg.c_str(), LANG_UNIVERSAL); } } } @@ -290,7 +400,7 @@ bool SuggestTradeAction::Execute(Event event) placeholders["%item"] = chat->FormatItem(proto, count); placeholders["%gold"] = chat->formatMoney(price); - spam(sPlayerbotTextMgr->Format("suggest_sell", placeholders)); + spam(BOT_TEXT2("suggest_sell", placeholders), urand(0, 1) ? 0x3C : 0, urand(0, 1), urand(0, 5)); return true; } diff --git a/src/strategy/actions/SuggestWhatToDoAction.h b/src/strategy/actions/SuggestWhatToDoAction.h index d5f08b00..4bff50cf 100644 --- a/src/strategy/actions/SuggestWhatToDoAction.h +++ b/src/strategy/actions/SuggestWhatToDoAction.h @@ -20,14 +20,11 @@ class SuggestWhatToDoAction : public InventoryAction protected: typedef void (SuggestWhatToDoAction::*Suggestion)(); std::vector suggestions; + void instance(); void specificQuest(); void grindReputation(); void something(); - void spam( - std::string msg, - uint32 channelId = 1, - bool const isLowerCase = false - ); + void spam(std::string msg, uint8 flags = 0, bool worldChat = false, bool guild = false); std::vector GetIncompletedQuests(); diff --git a/src/strategy/generic/EmoteStrategy.cpp b/src/strategy/generic/EmoteStrategy.cpp index 331ce368..a008f48e 100644 --- a/src/strategy/generic/EmoteStrategy.cpp +++ b/src/strategy/generic/EmoteStrategy.cpp @@ -8,8 +8,8 @@ void EmoteStrategy::InitTriggers(std::vector& triggers) { triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("emote", 1.0f), nullptr))); - triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("suggest what to do", 1.0f), nullptr))); - triggers.push_back(new TriggerNode("seldom", NextAction::array(0, new NextAction("suggest trade", 1.0f), nullptr))); + triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("suggest what to do", 1.0f), nullptr))); + triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("suggest trade", 1.0f), nullptr))); if (sPlayerbotAIConfig->randomBotSuggestDungeons) {