diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 2cf012a7..874429e4 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -458,9 +458,6 @@ AiPlayerbot.SyncLevelWithPlayers = 0 # Mark many quests <= Bot level as complete (slows down bot creation) AiPlayerbot.PreQuests = 0 -# Bots without a master will say their lines -AiPlayerbot.RandomBotSayWithoutMaster = 0 - # Enable LFG for random bots AiPlayerbot.RandomBotJoinLfg = 1 @@ -1263,27 +1260,107 @@ AiPlayerbot.CommandPrefix = "" # Separator for bot chat commands AiPlayerbot.CommandSeparator = "\\\\" -# Enable playerbot to talk in guild -AiPlayerbot.RandomBotGuildTalk = 1 - # Enable playerbot talk (say / yell / general chatting / lfg) AiPlayerbot.RandomBotTalk = 0 - # Enable playerbot emote AiPlayerbot.RandomBotEmote = 0 - # Enable dungeon suggestions for random bots AiPlayerbot.RandomBotSuggestDungeons = 1 - # Bots greet to the players AiPlayerbot.EnableGreet = 0 - +# Chance to reply to toxic links with toxic links (0-100) +AiPlayerbot.ToxicLinksRepliesChance = 30 +# Chance to reply to thunderfury with thunderfury (0-100) +AiPlayerbot.ThunderfuryRepliesChance = 40 +# Bots will chat in guild about certain events (int) (0-100) +AiPlayerbot.GuildRepliesRate = 100 # Reply someone saying something. +# Bots will chat in guild about certain events +AIPlayerbot.GuildFeedback = 1 +# Bots without a master will say their lines +AiPlayerbot.RandomBotSayWithoutMaster = 0 # # # #################################################################################################### +#################################################################################################### +# +# Broadcast rates +# +# 1 - to enable broadcasts globally, 0 - to disable (default 1) +# AiPlayerbot.EnableBroadcasts = 1 +# +# all broadcast chances should be in range 0-30000 +# +# value of 0 will disable this particular broadcast +# setting value to 30000 does not guarantee the broadcast, as there are some internal randoms as well +# +# setting channel broadcast chance to 0, will re-route most broadcasts to other available channels +# setting all channel broadcasts to 0 will disable most broadcasts +# AiPlayerbot.BroadcastToGuildGlobalChance = 30000 +# AiPlayerbot.BroadcastToWorldGlobalChance = 30000 +# AiPlayerbot.BroadcastToGeneralGlobalChance = 30000 +# AiPlayerbot.BroadcastToTradeGlobalChance = 30000 +# AiPlayerbot.BroadcastToLFGGlobalChance = 30000 +# AiPlayerbot.BroadcastToLocalDefenseGlobalChance = 30000 +# AiPlayerbot.BroadcastToWorldDefenseGlobalChance = 30000 +# AiPlayerbot.BroadcastToGuildRecruitmentGlobalChance = 30000 +# +# individual settings +# setting one of these to 0 will disable the particular broadcast +# AiPlayerbot.BroadcastChanceLootingItemPoor = 30 +# AiPlayerbot.BroadcastChanceLootingItemNormal = 150 +# AiPlayerbot.BroadcastChanceLootingItemUncommon = 10000 +# AiPlayerbot.BroadcastChanceLootingItemRare = 20000 +# AiPlayerbot.BroadcastChanceLootingItemEpic = 30000 +# AiPlayerbot.BroadcastChanceLootingItemLegendary = 30000 +# AiPlayerbot.BroadcastChanceLootingItemArtifact = 30000 +# +# AiPlayerbot.BroadcastChanceQuestAccepted = 6000 +# AiPlayerbot.BroadcastChanceQuestUpdateObjectiveCompleted = 300 +# AiPlayerbot.BroadcastChanceQuestUpdateObjectiveProgress = 300 +# AiPlayerbot.BroadcastChanceQuestUpdateFailedTimer = 300 +# AiPlayerbot.BroadcastChanceQuestUpdateComplete = 1000 +# AiPlayerbot.BroadcastChanceQuestTurnedIn = 10000 +# +# AiPlayerbot.BroadcastChanceKillNormal = 30 +# AiPlayerbot.BroadcastChanceKillElite = 300 +# AiPlayerbot.BroadcastChanceKillRareelite = 3000 +# AiPlayerbot.BroadcastChanceKillWorldboss = 20000 +# AiPlayerbot.BroadcastChanceKillRare = 10000 +# AiPlayerbot.BroadcastChanceKillUnknown = 100 +# AiPlayerbot.BroadcastChanceKillPet = 10 +# AiPlayerbot.BroadcastChanceKillPlayer = 30 +# +# AiPlayerbot.BroadcastChanceLevelupGeneric = 20000 +# AiPlayerbot.BroadcastChanceLevelupTenX = 30000 +# AiPlayerbot.BroadcastChanceLevelupMaxLevel = 30000 +# +# AiPlayerbot.BroadcastChanceSuggestInstance = 5000 +# AiPlayerbot.BroadcastChanceSuggestQuest = 10000 +# AiPlayerbot.BroadcastChanceSuggestGrindMaterials = 5000 +# AiPlayerbot.BroadcastChanceSuggestGrindReputation = 5000 +# AiPlayerbot.BroadcastChanceSuggestSell = 300 +# AiPlayerbot.BroadcastChanceSuggestSomething = 30000 +# +# Very rude speeches +# AiPlayerbot.BroadcastChanceSuggestSomethingToxic = 0 +# +# Specifically for " [item link]" +# AiPlayerbot.BroadcastChanceSuggestToxicLinks = 0 +# +# prefix is used as a word in " [item link]" +# AiPlayerbot.ToxicLinksPrefix = gnomes +# +# chance to suggest thunderfury +# AiPlayerbot.BroadcastChanceSuggestThunderfury = 1 +# +# does not depend on global chance +# AiPlayerbot.BroadcastChanceGuildManagement = 30000 +# +#################################################################################################### + #################################################################################################### # LOGS # diff --git a/src/BroadcastHelper.cpp b/src/BroadcastHelper.cpp new file mode 100644 index 00000000..d17bf561 --- /dev/null +++ b/src/BroadcastHelper.cpp @@ -0,0 +1,919 @@ + +#include "Playerbots.h" +#include "BroadcastHelper.h" +#include "ServerFacade.h" +#include "Channel.h" + +BroadcastHelper::BroadcastHelper() {} + +uint8 BroadcastHelper::GetLocale() +{ + uint8 locale = sWorld->GetDefaultDbcLocale(); + // -- In case we're using auto detect on config file^M + if (locale >= MAX_LOCALES) + locale = LocaleConstant::LOCALE_enUS; + return locale; +} + +bool BroadcastHelper::BroadcastTest(PlayerbotAI* ai, Player* bot) +{ + //return something to ignore the logic + return false; + + std::map placeholders; + placeholders["%rand1"] = std::to_string(urand(0, 1)); + placeholders["%rand2"] = std::to_string(urand(0, 1)); + placeholders["%rand3"] = std::to_string(urand(0, 1)); + + int32 rand = urand(0, 1); + + if (rand == 1 && ai->SayToChannel(BOT_TEXT2("Posted to trade, %rand1, %rand2, %rand3", placeholders), ChatChannelId::TRADE)) + return true; + else if (ai->SayToChannel(BOT_TEXT2("Posted to GuildRecruitment, %rand1, %rand2, %rand3", placeholders), ChatChannelId::GUILD_RECRUITMENT)) + return true; + + return ai->SayToChannel(BOT_TEXT2("Posted to trade, %rand1, %rand2, %rand3", placeholders), ChatChannelId::TRADE); + + //int32 rand = urand(1, 8); + if (rand == 1 && ai->SayToGuild(BOT_TEXT2("Posted to guild, %rand1, %rand2, %rand3", placeholders))) + return true; + else if (rand == 2 && ai->SayToWorld(BOT_TEXT2("Posted to world, %rand1, %rand2, %rand3", placeholders))) + return true; + else if (rand == 3 && ai->SayToChannel(BOT_TEXT2("Posted to general, %rand1, %rand2, %rand3", placeholders), ChatChannelId::GENERAL)) + return true; + else if (rand == 4 && ai->SayToChannel(BOT_TEXT2("Posted to trade, %rand1, %rand2, %rand3", placeholders), ChatChannelId::TRADE)) + return true; + else if (rand == 5 && ai->SayToChannel(BOT_TEXT2("Posted to LFG, %rand1, %rand2, %rand3", placeholders), ChatChannelId::LOOKING_FOR_GROUP)) + return true; + else if (rand == 6 && ai->SayToChannel(BOT_TEXT2("Posted to LocalDefense, %rand1, %rand2, %rand3", placeholders), ChatChannelId::LOCAL_DEFENSE)) + return true; + else if (rand == 7 && ai->SayToChannel(BOT_TEXT2("Posted to WorldDefense, %rand1, %rand2, %rand3", placeholders), ChatChannelId::WORLD_DEFENSE)) + return true; + else if (rand == 8 && ai->SayToChannel(BOT_TEXT2("Posted to GuildRecruitment, %rand1, %rand2, %rand3", placeholders), ChatChannelId::GUILD_RECRUITMENT)) + return true; + + return false; +} + +/** +@param toChannels - map of (ToChannel, chance), where chance is in range 0-100 as uint32 (unless global chance is not 100%) + +@return true if said to the channel, false otherwise +*/ +bool BroadcastHelper::BroadcastToChannelWithGlobalChance(PlayerbotAI* ai, std::string message, std::list> toChannels) +{ + if (message.empty()) + { + return false; + } + + for (const auto& pair : toChannels) + { + uint32 roll = urand(1, 100); + uint32 chance = pair.second; + uint32 broadcastRoll = urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue); + + switch (pair.first) + { + case TO_GUILD: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToGuildGlobalChance + && ai->SayToGuild(message)) + { + return true; + } + break; + } + case TO_WORLD: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToWorldGlobalChance + && ai->SayToWorld(message)) + { + return true; + } + break; + } + case TO_GENERAL: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToGeneralGlobalChance + && ai->SayToChannel(message, ChatChannelId::GENERAL)) + { + return true; + } + break; + } + case TO_TRADE: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToTradeGlobalChance + && ai->SayToChannel(message, ChatChannelId::TRADE)) + { + return true; + } + break; + } + case TO_LOOKING_FOR_GROUP: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToLFGGlobalChance + && ai->SayToChannel(message, ChatChannelId::LOOKING_FOR_GROUP)) + { + return true; + } + break; + } + case TO_LOCAL_DEFENSE: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToLocalDefenseGlobalChance + && ai->SayToChannel(message, ChatChannelId::LOCAL_DEFENSE)) + { + return true; + } + break; + } + case TO_WORLD_DEFENSE: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToWorldDefenseGlobalChance + && ai->SayToChannel(message, ChatChannelId::WORLD_DEFENSE)) + { + return true; + } + break; + } + case TO_GUILD_RECRUITMENT: + { + if (roll <= chance + && broadcastRoll <= sPlayerbotAIConfig->broadcastToGuildRecruitmentGlobalChance + && ai->SayToChannel(message, ChatChannelId::GUILD_RECRUITMENT)) + { + return true; + } + break; + } + default: + break; + } + } + + return false; +} + +bool BroadcastHelper::BroadcastLootingItem(PlayerbotAI* ai, Player* bot, const ItemTemplate *proto) +{ + std::map placeholders; + placeholders["%item_link"] = ai->GetChatHelper()->FormatItem(proto); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + switch (proto->Quality) + { + case ITEM_QUALITY_POOR: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemPoor) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_poor", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_NORMAL: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemNormal) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_normal", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_UNCOMMON: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemUncommon) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_uncommon", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_RARE: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemRare) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_rare", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_EPIC: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemEpic) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_epic", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_LEGENDARY: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemLegendary) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_legendary", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case ITEM_QUALITY_ARTIFACT: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLootingItemArtifact) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_looting_item_artifact", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + default: + break; + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestAccepted(PlayerbotAI* ai, Player* bot, const Quest* quest) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestAccepted) + { + std::map placeholders; + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_accepted_generic", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestUpdateAddKill(PlayerbotAI* ai, Player* bot, Quest const* quest, uint32 availableCount, uint32 requiredCount, std::string obectiveName) +{ + std::map placeholders; + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + placeholders["%quest_obj_name"] = obectiveName; + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + placeholders["%quest_obj_available"] = std::to_string(availableCount); + placeholders["%quest_obj_required"] = std::to_string(requiredCount); + placeholders["%quest_obj_missing"] = std::to_string(requiredCount - std::min(availableCount, requiredCount)); + placeholders["%quest_obj_full_formatted"] = ai->GetChatHelper()->FormatQuestObjective(obectiveName, availableCount, requiredCount); + + if (availableCount < requiredCount + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateObjectiveProgress) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_add_kill_objective_progress", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + else if (availableCount == requiredCount + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateObjectiveCompleted) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_add_kill_objective_completed", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestUpdateAddItem(PlayerbotAI* ai, Player* bot, Quest const* quest, uint32 availableCount, uint32 requiredCount, const ItemTemplate* proto) +{ + std::map placeholders; + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + std::string itemLinkFormatted = ai->GetChatHelper()->FormatItem(proto); + placeholders["%item_link"] = itemLinkFormatted; + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + placeholders["%quest_obj_available"] = std::to_string(availableCount); + placeholders["%quest_obj_required"] = std::to_string(requiredCount); + placeholders["%quest_obj_missing"] = std::to_string(requiredCount - std::min(availableCount, requiredCount)); + placeholders["%quest_obj_full_formatted"] = ai->GetChatHelper()->FormatQuestObjective(itemLinkFormatted, availableCount, requiredCount); + + if (availableCount < requiredCount + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateObjectiveProgress) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_add_item_objective_progress", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + else if (availableCount == requiredCount + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateObjectiveCompleted) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_add_item_objective_completed", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestUpdateFailedTimer(PlayerbotAI* ai, Player* bot, Quest const* quest) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateFailedTimer) + { + std::map placeholders; + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_failed_timer", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestUpdateComplete(PlayerbotAI* ai, Player* bot, Quest const* quest) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateComplete) + { + std::map placeholders; + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_update_complete", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastQuestTurnedIn(PlayerbotAI* ai, Player* bot, Quest const* quest) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestTurnedIn) + { + std::map placeholders; + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_quest_turned_in", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastKill(PlayerbotAI* ai, Player* bot, Creature *creature) +{ + std::map placeholders; + placeholders["%victim_name"] = creature->GetName(); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%victim_level"] = creature->GetLevel(); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + //if ((creature->IsElite() && !creature->GetMap()->IsDungeon()) + //if creature->IsWorldBoss() + //if creature->GetLevel() > DEFAULT_MAX_LEVEL + 1 + //if creature->GetLevel() > bot->GetLevel() + 4 + + if (creature->IsPet()) + { + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillPet) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_pet", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + } + else if (creature->IsPlayer()) + { + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillPlayer) + { + placeholders["%victim_class"] = ai->GetChatHelper()->FormatClass(creature->getClass()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_player", placeholders), + { {TO_WORLD_DEFENSE, 50}, {TO_LOCAL_DEFENSE, 50}, {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + } + else + { + switch (creature->GetCreatureTemplate()->rank) + { + case CREATURE_ELITE_NORMAL: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillNormal) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_normal", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case CREATURE_ELITE_ELITE: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillElite) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_elite", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case CREATURE_ELITE_RAREELITE: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillRareelite) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_rareelite", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case CREATURE_ELITE_WORLDBOSS: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillWorldboss) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_worldboss", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case CREATURE_ELITE_RARE: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillRare) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_rare", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + case CREATURE_UNKNOWN: + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceKillUnknown) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_killed_unknown", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + break; + default: + break; + } + } + + return false; +} + +bool BroadcastHelper::BroadcastLevelup(PlayerbotAI* ai, Player* bot) +{ + uint32 level = bot->GetLevel(); + + std::map placeholders; + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(level); + + if (level == sPlayerbotAIConfig->randomBotMaxLevel + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLevelupMaxLevel) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_levelup_max_level", placeholders), + { {TO_GUILD, 30}, {TO_WORLD, 90}, {TO_GENERAL, 100} } + ); + } + // It's divisible by 10 + else if (level % 10 == 0 + && urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLevelupTenX) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_levelup_10x", placeholders), + { {TO_GUILD, 50}, {TO_WORLD, 90}, {TO_GENERAL, 100} } + ); + } + else if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceLevelupGeneric) + { + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("broadcast_levelup_generic", placeholders), + { {TO_GUILD, 90}, {TO_WORLD, 90}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* bot, Player* player) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceGuildManagement) + { + std::map placeholders; + placeholders["%other_name"] = player->GetName(); + placeholders["%other_class"] = ai->GetChatHelper()->FormatClass(player->getClass()); + placeholders["%other_race"] = ai->GetChatHelper()->FormatRace(player->getRace()); + placeholders["%other_level"] = std::to_string(player->GetLevel()); + + return ai->SayToGuild(BOT_TEXT2("broadcast_guild_promotion", placeholders)); + } + + return false; +} + +bool BroadcastHelper::BroadcastGuildMemberDemotion(PlayerbotAI* ai, Player* bot, Player* player) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceGuildManagement) + { + std::map placeholders; + placeholders["%other_name"] = player->GetName(); + placeholders["%other_class"] = ai->GetChatHelper()->FormatClass(player->getClass()); + placeholders["%other_race"] = ai->GetChatHelper()->FormatRace(player->getRace()); + placeholders["%other_level"] = std::to_string(player->GetLevel()); + + return ai->SayToGuild(BOT_TEXT2("broadcast_guild_demotion", placeholders)); + } + + return false; +} + +bool BroadcastHelper::BroadcastGuildGroupOrRaidInvite(PlayerbotAI* ai, Player* bot, Player* player, Group* group) +{ + std::map placeholders; + placeholders["%name"] = player->GetName(); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + + //TODO move texts to sql! + if (group && group->isRaidGroup()) + { + if (urand(0, 3)) + { + return ai->SayToGuild(BOT_TEXT2("Hey anyone want to raid in %zone_name", placeholders)); + } + else + { + return ai->SayToGuild(BOT_TEXT2("Hey %name I'm raiding in %zone_name do you wan to join me?", placeholders)); + } + } + else + { + //(bot->GetTeam() == ALLIANCE ? LANG_COMMON : LANG_ORCISH) + if (urand(0, 3)) + { + return ai->SayToGuild(BOT_TEXT2("Hey anyone wanna group up in %zone_name?", placeholders)); + } + else + { + return ai->SayToGuild(BOT_TEXT2("Hey %name do you want join my group? I'm heading for %zone_name", placeholders)); + } + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestInstance(PlayerbotAI* ai, std::vector& allowedInstances, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestInstance) + { + std::map placeholders; + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + + std::ostringstream itemout; + //itemout << "|c00b000b0" << allowedInstances[urand(0, allowedInstances.size() - 1)] << "|r"; + itemout << allowedInstances[urand(0, allowedInstances.size() - 1)]; + placeholders["%instance_name"] = itemout.str(); + + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_instance", placeholders), + { {TO_LOOKING_FOR_GROUP, 50}, {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestQuest(PlayerbotAI* ai, std::vector& quests, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestQuest) + { + + int index = rand() % quests.size(); + + Quest const* quest = sObjectMgr->GetQuestTemplate(quests[index]); + + std::map placeholders; + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + placeholders["%quest_link"] = ai->GetChatHelper()->FormatQuest(quest); + placeholders["%quest_level"] = std::to_string(quest->GetQuestLevel()); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_quest", placeholders), + { {TO_LOOKING_FOR_GROUP, 50}, {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestGrindMaterials(PlayerbotAI* ai, std::string item, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestGrindMaterials) + { + + std::map placeholders; + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + placeholders["%category"] = item; + + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_trade", placeholders), + { {TO_TRADE, 50}, {TO_LOOKING_FOR_GROUP, 50}, {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestGrindReputation(PlayerbotAI* ai, std::vector levels, std::vector allowedFactions, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestGrindReputation) + { + + std::map placeholders; + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + placeholders["%rep_level"] = levels[urand(0, 2)]; + std::ostringstream rnd; rnd << urand(1, 5) << "K"; + placeholders["%rndK"] = rnd.str(); + + std::ostringstream itemout; + //itemout << "|c004040b0" << allowedFactions[urand(0, allowedFactions.size() - 1)] << "|r"; + itemout << allowedFactions[urand(0, allowedFactions.size() - 1)]; + placeholders["%faction"] = itemout.str(); + + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_faction", placeholders), + { {TO_LOOKING_FOR_GROUP, 50}, {TO_GUILD, 50}, {TO_WORLD, 50}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestSell(PlayerbotAI* ai, const ItemTemplate* proto, uint32 count, uint32 price, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSell) + { + + std::map placeholders; + placeholders["%item_link"] = ai->GetChatHelper()->FormatItem(proto, 0); + placeholders["%item_formatted_link"] = ai->GetChatHelper()->FormatItem(proto, count); + placeholders["%item_count"] = std::to_string(count); + placeholders["%cost_gold"] = ai->GetChatHelper()->formatMoney(price); + + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_sell", placeholders), + { {TO_TRADE, 90}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestSomething(PlayerbotAI* ai, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSomething) + { + std::map placeholders; + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_something", placeholders), + { {TO_GUILD, 10}, {TO_WORLD, 70}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestSomethingToxic(PlayerbotAI* ai, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSomethingToxic) + { + //items + std::vector botItems = ai->GetInventoryAndEquippedItems(); + + std::map placeholders; + + placeholders["%random_inventory_item_link"] = botItems.size() > 0 ? ai->GetChatHelper()->FormatItem(botItems[rand() % botItems.size()]->GetTemplate()) : BOT_TEXT1("string_empty_link"); + + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_something_toxic", placeholders), + { {TO_GUILD, 10}, {TO_WORLD, 70}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestToxicLinks(PlayerbotAI* ai, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestToxicLinks) + { + //quests + std::vector incompleteQuests; + 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) + incompleteQuests.push_back(questId); + } + + //items + std::vector botItems = ai->GetInventoryAndEquippedItems(); + + //spells + //? + + std::map placeholders; + + placeholders["%random_inventory_item_link"] = botItems.size() > 0 ? ai->GetChatHelper()->FormatItem(botItems[rand() % botItems.size()]->GetTemplate()) : BOT_TEXT1("string_empty_link"); + placeholders["%prefix"] = sPlayerbotAIConfig->toxicLinksPrefix; + + if (incompleteQuests.size() > 0) + { + Quest const* quest = sObjectMgr->GetQuestTemplate(incompleteQuests[rand() % incompleteQuests.size()]); + placeholders["%random_taken_quest_or_item_link"] = ai->GetChatHelper()->FormatQuest(quest); + } + else + { + placeholders["%random_taken_quest_or_item_link"] = placeholders["%random_inventory_item_link"]; + } + + placeholders["%my_role"] = ai->GetChatHelper()->FormatClass(bot->GetSpec()); + AreaTableEntry const* current_area = ai->GetCurrentArea(); + AreaTableEntry const* current_zone = ai->GetCurrentZone(); + placeholders["%area_name"] = current_area ? ai->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? ai->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = ai->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = ai->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("suggest_toxic_links", placeholders), + { {TO_GUILD, 10}, {TO_WORLD, 70}, {TO_GENERAL, 100} } + ); + } + + return false; +} + +bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot) +{ + if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestThunderfury) + { + std::map placeholders; + ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019); + placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); + + return BroadcastToChannelWithGlobalChance( + ai, + BOT_TEXT2("thunderfury_spam", placeholders), + { {TO_WORLD, 70}, {TO_GENERAL, 100} } + ); + } + + return false; +} \ No newline at end of file diff --git a/src/BroadcastHelper.h b/src/BroadcastHelper.h new file mode 100644 index 00000000..099d6bf1 --- /dev/null +++ b/src/BroadcastHelper.h @@ -0,0 +1,148 @@ +#pragma once + +class PlayerbotAI; +class Player; +class ItemTemplate; +class Quest; +class Creature; +class Group; + +class BroadcastHelper +{ +public: + BroadcastHelper(); + +public: + enum ToChannel + { + TO_GUILD = 1, + TO_WORLD = 2, + TO_GENERAL = 3, + TO_TRADE = 4, + TO_LOOKING_FOR_GROUP = 5, + TO_LOCAL_DEFENSE = 6, + TO_WORLD_DEFENSE = 7, + TO_GUILD_RECRUITMENT = 8 + }; + + static uint8_t GetLocale(); + static bool BroadcastTest( + PlayerbotAI* ai, + Player* bot + ); + static bool BroadcastToChannelWithGlobalChance( + PlayerbotAI* ai, + std::string message, + std::list> toChannels + ); + static bool BroadcastLootingItem( + PlayerbotAI* ai, + Player* bot, + const ItemTemplate* proto + ); + static bool BroadcastQuestAccepted( + PlayerbotAI* ai, + Player* bot, + const Quest* quest + ); + static bool BroadcastQuestUpdateAddKill( + PlayerbotAI* ai, + Player* bot, + Quest const* quest, + uint32_t availableCount, + uint32_t requiredCount, + std::string obectiveName + ); + static bool BroadcastQuestUpdateAddItem( + PlayerbotAI* ai, + Player* bot, + Quest const* quest, + uint32_t availableCount, + uint32_t requiredCount, + const ItemTemplate* proto + ); + static bool BroadcastQuestUpdateFailedTimer( + PlayerbotAI* ai, + Player* bot, + Quest const* quest + ); + static bool BroadcastQuestUpdateComplete( + PlayerbotAI* ai, + Player* bot, + Quest const* quest + ); + static bool BroadcastQuestTurnedIn( + PlayerbotAI* ai, + Player* bot, + Quest const* quest + ); + static bool BroadcastKill( + PlayerbotAI* ai, + Player* bot, + Creature* creature + ); + static bool BroadcastLevelup( + PlayerbotAI* ai, + Player* bot + ); + static bool BroadcastGuildMemberPromotion( + PlayerbotAI* ai, + Player* bot, + Player* player + ); + static bool BroadcastGuildMemberDemotion( + PlayerbotAI* ai, + Player* bot, + Player* player + ); + static bool BroadcastGuildGroupOrRaidInvite( + PlayerbotAI* ai, + Player* bot, + Player* player, + Group* group + ); + static bool BroadcastSuggestInstance( + PlayerbotAI* ai, + std::vector& allowedInstances, + Player* bot + ); + static bool BroadcastSuggestQuest( + PlayerbotAI* ai, + std::vector& quests, + Player* bot + ); + static bool BroadcastSuggestGrindMaterials( + PlayerbotAI* ai, + std::string item, + Player* bot + ); + static bool BroadcastSuggestGrindReputation( + PlayerbotAI* ai, + std::vector levels, + std::vector allowedFactions, + Player* bot + ); + static bool BroadcastSuggestSell( + PlayerbotAI* ai, + const ItemTemplate* proto, + uint32_t count, + uint32_t price, + Player* bot + ); + static bool BroadcastSuggestSomething( + PlayerbotAI* ai, + Player* bot + ); + static bool BroadcastSuggestSomethingToxic( + PlayerbotAI* ai, + Player* bot + ); + static bool BroadcastSuggestToxicLinks( + PlayerbotAI* ai, + Player* bot + ); + static bool BroadcastSuggestThunderfury( + PlayerbotAI* ai, + Player* bot + ); +}; \ No newline at end of file diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 3c2d7e20..83971ab5 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -344,11 +344,11 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal std::list delayedResponses; while (!chatReplies.empty()) { - ChatQueuedReply holder = chatReplies.front(); + ChatQueuedReply& holder = chatReplies.front(); time_t checkTime = holder.m_time; if (checkTime && time(0) < checkTime) { - delayedResponses.push_back(holder); + delayedResponses.push_back(std::move(holder)); chatReplies.pop(); continue; } @@ -420,11 +420,11 @@ void PlayerbotAI::HandleCommands() std::list delayed; while (!chatCommands.empty()) { - ChatCommandHolder holder = chatCommands.front(); + ChatCommandHolder& holder = chatCommands.front(); time_t checkTime = holder.GetTime(); if (checkTime && time(0) < checkTime) { - delayed.push_back(holder); + delayed.push_back(std::move(holder)); chatCommands.pop(); continue; } @@ -940,6 +940,11 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) p >> guid1 >> unused; if (guid1.IsEmpty() || p.size() > p.DEFAULT_SIZE) return; + if (p.GetOpcode() == SMSG_GM_MESSAGECHAT) + { + p >> textLen; + p >> name; + } switch (msgtype) { @@ -965,6 +970,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) bool isPaused = time(0) < lastChat; bool shouldReply = false; bool isFromFreeBot = false; + if (!sCharacterCache->GetCharacterNameByGuid(guid1, name)) {/*sould return or ?*/} uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid1); isFromFreeBot = sPlayerbotAIConfig->IsInRandomAccountList(accountId); bool isMentioned = message.find(bot->GetName()) != std::string::npos; @@ -984,34 +990,43 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet) if (lang == LANG_ADDON) return; - // several talk config option later - /* - - TODO: - toxic link - legendary grind / thunderfury etc - - */ - if (isFromFreeBot && urand(0, 20)) - return; - - //if (msgtype == CHAT_MSG_GUILD && (!sPlayerbotAIConfig->guildRepliesRate || urand(1, 100) >= sPlayerbotAIConfig->guildRepliesRate)) - //return; - - if (!isFromFreeBot) + if (message.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) + && (GetChatHelper()->ExtractAllItemIds(message).size() > 0 || GetChatHelper()->ExtractAllQuestIds(message).size() > 0) + && sPlayerbotAIConfig->toxicLinksRepliesChance) { - if (!isMentioned && urand(0, 4)) + if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig->toxicLinksRepliesChance) + { return; + } + } + else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) && sPlayerbotAIConfig->thunderfuryRepliesChance)) + { + if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig->thunderfuryRepliesChance) + { + return; + } } else { - if (urand(0, 20 + 10 * isMentioned)) + if (isFromFreeBot && urand(0, 20)) return; - } + //if (msgtype == CHAT_MSG_GUILD && (!sPlayerbotAIConfig->guildRepliesRate || urand(1, 100) >= sPlayerbotAIConfig->guildRepliesRate)) + //return; + + if (!isFromFreeBot) + { + if (!isMentioned && urand(0, 4)) + return; + } + else + { + if (urand(0, 20 + 10 * isMentioned)) + return; + } + } QueueChatResponse(msgtype, guid1, ObjectGuid(), message, chanName, name); GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 25)); - return; } } @@ -4653,6 +4668,209 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const return oil; } +std::vector PlayerbotAI::GetInventoryAndEquippedItems() +{ + std::vector items; + + 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)) + { + items.push_back(pItem); + } + } + } + } + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + items.push_back(pItem); + } + } + + for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + items.push_back(pItem); + } + } + + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + items.push_back(pItem); + } + } + + return items; +} + +std::vector PlayerbotAI::GetInventoryItems() +{ + std::vector items; + + 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)) + { + items.push_back(pItem); + } + } + } + } + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + items.push_back(pItem); + } + } + + for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + items.push_back(pItem); + } + } + + return items; +} + +uint32 PlayerbotAI::GetInventoryItemsCountWithId(uint32 itemId) +{ + uint32 count = 0; + + 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 (pItem->GetTemplate()->ItemId == itemId) + { + count += pItem->GetCount(); + } + } + } + } + } + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetTemplate()->ItemId == itemId) + { + count += pItem->GetCount(); + } + } + } + + for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetTemplate()->ItemId == itemId) + { + count += pItem->GetCount(); + } + } + } + + return count; +} + +bool PlayerbotAI::HasItemInInventory(uint32 itemId) +{ + + 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 (pItem->GetTemplate()->ItemId == itemId) + { + return true; + } + } + } + } + } + + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetTemplate()->ItemId == itemId) + { + return true; + } + } + } + + for (int i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (pItem->GetTemplate()->ItemId == itemId) + { + return true; + } + } + } + + return false; +} + +std::vector> PlayerbotAI::GetCurrentQuestsRequiringItemId(uint32 itemId) +{ + std::vector> result; + + if (!itemId) + { + return result; + } + + for (uint16 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) + { + uint32 questId = bot->GetQuestSlotQuestId(slot); + if (!questId) + continue; + + QuestStatus status = bot->GetQuestStatus(questId); + const Quest* quest = sObjectMgr->GetQuestTemplate(questId); + for (uint8 i = 0; i < std::size(quest->RequiredItemId); ++i) + { + if (quest->RequiredItemId[i] == itemId) + { + result.push_back(std::pair(quest, quest->RequiredItemId[i])); + break; + } + } + } + + return result; +} + // on self void PlayerbotAI::ImbueItem(Item* item) { @@ -4807,9 +5025,9 @@ bool PlayerbotAI::IsInRealGuild() return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount)); } -void PlayerbotAI::QueueChatResponse(uint8 msgtype, ObjectGuid guid1, ObjectGuid guid2, std::string message, std::string chanName, std::string name) +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(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15))); + chatReplies.push({ msgtype, guid1.GetCounter(), guid2.GetCounter(), message, chanName, name, time(nullptr) + urand(inCombat ? 10 : 5, inCombat ? 25 : 15) }); } bool PlayerbotAI::EqualLowercaseName(std::string s1, std::string s2) diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 00380f26..7ebb520b 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -364,7 +364,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 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); @@ -525,6 +525,11 @@ class PlayerbotAI : public PlayerbotAIBase bool EqualLowercaseName(std::string s1, std::string s2); InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const; uint8 FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const; + std::vector GetInventoryAndEquippedItems(); + std::vector GetInventoryItems(); + uint32 GetInventoryItemsCountWithId(uint32 itemId); + bool HasItemInInventory(uint32 itemId); + std::vector> GetCurrentQuestsRequiringItemId(uint32 itemId); std::vector GetAllCurrentQuests(); std::vector GetCurrentIncompleteQuests(); diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index e90d847f..0f7d0272 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -140,11 +140,85 @@ bool PlayerbotAIConfig::Initialize() minRandomBotsPriceChangeInterval = sConfigMgr->GetOption("AiPlayerbot.MinRandomBotsPriceChangeInterval", 2 * HOUR); maxRandomBotsPriceChangeInterval = sConfigMgr->GetOption("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * HOUR); randomBotJoinLfg = sConfigMgr->GetOption("AiPlayerbot.RandomBotJoinLfg", true); + + //////////////////////////// CHAT + enableBroadcasts = sConfigMgr->GetOption("AiPlayerbot.EnableBroadcasts", true); randomBotTalk = sConfigMgr->GetOption("AiPlayerbot.RandomBotTalk", false); randomBotEmote = sConfigMgr->GetOption("AiPlayerbot.RandomBotEmote", false); randomBotSuggestDungeons = sConfigMgr->GetOption("AiPlayerbot.RandomBotSuggestDungeons", true); - randomBotGuildTalk = sConfigMgr->GetOption("AiPlayerbot.RandomBotGuildTalk", false); + randomBotSayWithoutMaster = sConfigMgr->GetOption("AiPlayerbot.RandomBotSayWithoutMaster", false); + + //broadcastChanceMaxValue is used in urand(1, broadcastChanceMaxValue) for broadcasts, + //lowering it will increase the chance, setting it to 0 will disable broadcasts + //for internal use, not intended to be change by the user + broadcastChanceMaxValue = enableBroadcasts ? 30000 : 0; + + //all broadcast chances should be in range 1-broadcastChanceMaxValue, value of 0 will disable this particular broadcast + //setting value to max does not guarantee the broadcast, as there are some internal randoms as well + broadcastToGuildGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToGuildGlobalChance", 30000); + broadcastToWorldGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToWorldGlobalChance", 30000); + broadcastToGeneralGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToGeneralGlobalChance", 30000); + broadcastToTradeGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToTradeGlobalChance", 30000); + broadcastToLFGGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToLFGGlobalChance", 30000); + broadcastToLocalDefenseGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToLocalDefenseGlobalChance", 30000); + broadcastToWorldDefenseGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToWorldDefenseGlobalChance", 30000); + broadcastToGuildRecruitmentGlobalChance = sConfigMgr->GetOption("AiPlayerbot.BroadcastToGuildRecruitmentGlobalChance", 30000); + + broadcastChanceLootingItemPoor = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemPoor", 30); + broadcastChanceLootingItemNormal = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemNormal", 300); + broadcastChanceLootingItemUncommon = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemUncommon", 10000); + broadcastChanceLootingItemRare = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemRare", 20000); + broadcastChanceLootingItemEpic = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemEpic", 30000); + broadcastChanceLootingItemLegendary = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemLegendary", 30000); + broadcastChanceLootingItemArtifact = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLootingItemArtifact", 30000); + + broadcastChanceQuestAccepted = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestAccepted", 6000); + broadcastChanceQuestUpdateObjectiveCompleted = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestUpdateObjectiveCompleted", 300); + broadcastChanceQuestUpdateObjectiveProgress = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestUpdateObjectiveProgress", 300); + broadcastChanceQuestUpdateFailedTimer = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestUpdateFailedTimer", 300); + broadcastChanceQuestUpdateComplete = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestUpdateComplete", 1000); + broadcastChanceQuestTurnedIn = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceQuestTurnedIn", 10000); + + broadcastChanceKillNormal = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillNormal", 30); + broadcastChanceKillElite = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillElite", 300); + broadcastChanceKillRareelite = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillRareelite", 3000); + broadcastChanceKillWorldboss = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillWorldboss", 20000); + broadcastChanceKillRare = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillRare", 10000); + broadcastChanceKillUnknown = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillUnknown", 100); + broadcastChanceKillPet = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillPet", 10); + broadcastChanceKillPlayer = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceKillPlayer", 30); + + broadcastChanceLevelupGeneric = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLevelupGeneric", 20000); + broadcastChanceLevelupTenX = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLevelupTenX", 30000); + broadcastChanceLevelupMaxLevel = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceLevelupMaxLevel", 30000); + + broadcastChanceSuggestInstance = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestInstance", 5000); + broadcastChanceSuggestQuest = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestQuest", 10000); + broadcastChanceSuggestGrindMaterials = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestGrindMaterials", 5000); + broadcastChanceSuggestGrindReputation = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestGrindReputation", 5000); + broadcastChanceSuggestSell = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestSell", 300); + broadcastChanceSuggestSomething = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestSomething", 30000); + + broadcastChanceSuggestSomethingToxic = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestSomethingToxic", 0); + + broadcastChanceSuggestToxicLinks = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestToxicLinks", 0); + toxicLinksPrefix = sConfigMgr->GetOption("AiPlayerbot.ToxicLinksPrefix", "gnomes"); + + broadcastChanceSuggestThunderfury = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceSuggestThunderfury", 1); + + //does not depend on global chance + broadcastChanceGuildManagement = sConfigMgr->GetOption("AiPlayerbot.BroadcastChanceGuildManagement", 30000); + //////////////////////////// + + toxicLinksRepliesChance = sConfigMgr->GetOption("AiPlayerbot.ToxicLinksRepliesChance", 30); //0-100 + thunderfuryRepliesChance = sConfigMgr->GetOption("AiPlayerbot.ThunderfuryRepliesChance", 40); //0-100 + guildRepliesRate = sConfigMgr->GetOption("AiPlayerbot.GuildRepliesRate", 100); //0-100 suggestDungeonsInLowerCaseRandomly = sConfigMgr->GetOption("AiPlayerbot.SuggestDungeonsInLowerCaseRandomly", false); + + + ////////////////////////// !CHAT + + randomBotJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotJoinBG", true); randomBotAutoJoinBG = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinBG", false); randomBotAutoJoinWarsongBracket = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutoJoinWarsongBracket", 14); @@ -337,8 +411,6 @@ bool PlayerbotAIConfig::Initialize() autoDoQuests = sConfigMgr->GetOption("AiPlayerbot.AutoDoQuests", false); syncLevelWithPlayers = sConfigMgr->GetOption("AiPlayerbot.SyncLevelWithPlayers", false); freeFood = sConfigMgr->GetOption("AiPlayerbot.FreeFood", true); - randomBotSayWithoutMaster = sConfigMgr->GetOption("AiPlayerbot.RandomBotSayWithoutMaster", false); - sayWhenCollectingItems = sConfigMgr->GetOption("AiPlayerbot.SayWhenCollectingItems", true); randomBotGroupNearby = sConfigMgr->GetOption("AiPlayerbot.RandomBotGroupNearby", true); // arena diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 3971d819..fe9596a3 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -95,11 +95,78 @@ class PlayerbotAIConfig uint32 randomBotsPerInterval; uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval; bool randomBotJoinLfg; + + // chat bool randomBotTalk; bool randomBotEmote; bool randomBotSuggestDungeons; - bool randomBotGuildTalk; + bool enableBroadcasts; + bool enableGreet; + bool randomBotSayWithoutMaster; + + uint32 broadcastChanceMaxValue; + + uint32 broadcastToGuildGlobalChance; + uint32 broadcastToWorldGlobalChance; + uint32 broadcastToGeneralGlobalChance; + uint32 broadcastToTradeGlobalChance; + uint32 broadcastToLFGGlobalChance; + uint32 broadcastToLocalDefenseGlobalChance; + uint32 broadcastToWorldDefenseGlobalChance; + uint32 broadcastToGuildRecruitmentGlobalChance; + + uint32 broadcastChanceLootingItemPoor; + uint32 broadcastChanceLootingItemNormal; + uint32 broadcastChanceLootingItemUncommon; + uint32 broadcastChanceLootingItemRare; + uint32 broadcastChanceLootingItemEpic; + uint32 broadcastChanceLootingItemLegendary; + uint32 broadcastChanceLootingItemArtifact; + + uint32 broadcastChanceQuestAccepted; + uint32 broadcastChanceQuestUpdateObjectiveCompleted; + uint32 broadcastChanceQuestUpdateObjectiveProgress; + uint32 broadcastChanceQuestUpdateFailedTimer; + uint32 broadcastChanceQuestUpdateComplete; + uint32 broadcastChanceQuestTurnedIn; + + uint32 broadcastChanceKillNormal; + uint32 broadcastChanceKillElite; + uint32 broadcastChanceKillRareelite; + uint32 broadcastChanceKillWorldboss; + uint32 broadcastChanceKillRare; + uint32 broadcastChanceKillUnknown; + uint32 broadcastChanceKillPet; + uint32 broadcastChanceKillPlayer; + + uint32 broadcastChanceLevelupGeneric; + uint32 broadcastChanceLevelupTenX; + uint32 broadcastChanceLevelupMaxLevel; + + uint32 broadcastChanceSuggestInstance; + uint32 broadcastChanceSuggestQuest; + uint32 broadcastChanceSuggestGrindMaterials; + uint32 broadcastChanceSuggestGrindReputation; + uint32 broadcastChanceSuggestSell; + uint32 broadcastChanceSuggestSomething; + + uint32 broadcastChanceSuggestSomethingToxic; + + uint32 broadcastChanceSuggestToxicLinks; + std::string toxicLinksPrefix; + uint32 toxicLinksRepliesChance; + + uint32 broadcastChanceSuggestThunderfury; + uint32 thunderfuryRepliesChance; + + uint32 broadcastChanceGuildManagement; + + uint32 guildRepliesRate; + bool suggestDungeonsInLowerCaseRandomly; + + // -- + bool randomBotJoinBG; bool randomBotAutoJoinBG; uint32 randomBotAutoJoinWarsongBracket; @@ -181,8 +248,6 @@ class PlayerbotAIConfig uint32 commandServerPort; bool perfMonEnabled; - - bool enableGreet; bool summonWhenGroup; bool randomBotShowHelmet; bool randomBotShowCloak; @@ -212,8 +277,6 @@ class PlayerbotAIConfig bool freeFood; bool autoLearnQuestSpells; bool autoTeleportForLevel; - bool randomBotSayWithoutMaster; - bool sayWhenCollectingItems; bool randomBotGroupNearby; uint32 tweakValue; //Debugging config diff --git a/src/strategy/actions/AutoLearnSpellAction.cpp b/src/strategy/actions/AutoLearnSpellAction.cpp index 25d8db43..9944d4ea 100644 --- a/src/strategy/actions/AutoLearnSpellAction.cpp +++ b/src/strategy/actions/AutoLearnSpellAction.cpp @@ -6,7 +6,7 @@ #include "Event.h" #include "PlayerbotFactory.h" #include "Playerbots.h" -#include "GuildMgr.h" +#include "BroadcastHelper.h" bool AutoLearnSpellAction::Execute(Event event) { @@ -30,18 +30,12 @@ bool AutoLearnSpellAction::Execute(Event event) void AutoLearnSpellAction::LearnSpells(std::ostringstream* out) { + BroadcastHelper::BroadcastLevelup(botAI, bot); if (sPlayerbotAIConfig->autoLearnTrainerSpells) LearnTrainerSpells(out); if (sPlayerbotAIConfig->autoLearnQuestSpells) LearnQuestSpells(out); - - if (sPlayerbotAIConfig->randomBotGuildTalk) - { - std::map args; - args["%my_level"] = std::to_string(bot->GetLevel()); - botAI->SayToGuild(BOT_TEXT2("broadcast_levelup_generic", args)); - } } void AutoLearnSpellAction::LearnTrainerSpells(std::ostringstream* out) diff --git a/src/strategy/actions/GuildManagementActions.cpp b/src/strategy/actions/GuildManagementActions.cpp index 18138145..b7316f27 100644 --- a/src/strategy/actions/GuildManagementActions.cpp +++ b/src/strategy/actions/GuildManagementActions.cpp @@ -7,6 +7,7 @@ #include "GuildPackets.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "BroadcastHelper.h" Player* GuidManageAction::GetPlayer(Event event) { @@ -167,6 +168,7 @@ bool GuildManageNearbyAction::Execute(Event event) { if (!urand(0, 10)) { + BroadcastHelper::BroadcastGuildMemberPromotion(botAI, bot, player); botAI->DoSpecificAction("guild promote", Event("guild management", guid), true); continue; diff --git a/src/strategy/actions/InviteToGroupAction.cpp b/src/strategy/actions/InviteToGroupAction.cpp index 0967a823..443999cf 100644 --- a/src/strategy/actions/InviteToGroupAction.cpp +++ b/src/strategy/actions/InviteToGroupAction.cpp @@ -7,6 +7,7 @@ #include "GuildMgr.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "BroadcastHelper.h" bool InviteToGroupAction::Execute(Event event) { @@ -76,6 +77,26 @@ bool InviteNearbyToGroupAction::Execute(Event event) if (sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) continue; + Group* group = bot->GetGroup(); + Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); + if (!botAI->HasActivePlayerMaster()) + { + if (guild && bot->GetGuildId() == player->GetGuildId()) + { + BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); + } + else + { + std::map placeholders; + placeholders["%player"] = player->GetName(); + + if (group && group->isRaidGroup()) + bot->Say(BOT_TEXT2("join_raid", placeholders), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); + else + bot->Say(BOT_TEXT2("join_group", placeholders), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); + } + } + return Invite(player); } @@ -125,6 +146,8 @@ std::vector InviteGuildToGroupAction::getGuildMembers() bool InviteGuildToGroupAction::Execute(Event event) { + Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); + for (auto& member : getGuildMembers()) { Player* player = member; @@ -165,6 +188,8 @@ bool InviteGuildToGroupAction::Execute(Event event) if (!botAI && sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) continue; + Group* group = bot->GetGroup(); + BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); return Invite(player); } diff --git a/src/strategy/actions/LootAction.cpp b/src/strategy/actions/LootAction.cpp index 587528c8..b82610c0 100644 --- a/src/strategy/actions/LootAction.cpp +++ b/src/strategy/actions/LootAction.cpp @@ -13,6 +13,7 @@ #include "Playerbots.h" #include "ServerFacade.h" #include "GuildMgr.h" +#include "BroadcastHelper.h" bool LootAction::Execute(Event event) { @@ -417,19 +418,7 @@ bool StoreLootAction::Execute(Event event) if (proto->Quality >= ITEM_QUALITY_RARE && !urand(0, 1) && botAI->HasStrategy("emote", BOT_STATE_NON_COMBAT)) botAI->PlayEmote(TEXT_EMOTE_CHEER); - if (sPlayerbotAIConfig->randomBotGuildTalk && urand(0, 10) && proto->Quality >= ITEM_QUALITY_RARE) - { - std::map args; - args["%item"] = chat->FormatItem(proto); - botAI->SayToGuild(BOT_TEXT2("loot_command", args)); - } - - // std::ostringstream out; - // out << "Looting " << chat->FormatItem(proto); - // botAI->TellMasterNoFacing(out.str()); - - //ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", proto->ItemId); - //LOG_ERROR("playerbots", "Bot {} is looting {} {} for usage {}.", bot->GetName().c_str(), itemcount, proto->Name1.c_str(), usage); + BroadcastHelper::BroadcastLootingItem(botAI, bot, proto); } AI_VALUE(LootObjectStack*, "available loot")->Remove(guid); diff --git a/src/strategy/actions/QueryItemUsageAction.cpp b/src/strategy/actions/QueryItemUsageAction.cpp index 2a894ced..3e3e5aa3 100644 --- a/src/strategy/actions/QueryItemUsageAction.cpp +++ b/src/strategy/actions/QueryItemUsageAction.cpp @@ -10,64 +10,6 @@ bool QueryItemUsageAction::Execute(Event event) { - if (!GetMaster() && !sPlayerbotAIConfig->randomBotSayWithoutMaster) - return false; - - if (!sPlayerbotAIConfig->sayWhenCollectingItems) - return false; - - WorldPacket& data = event.getPacket(); - if (!data.empty()) - { - data.rpos(0); - - ObjectGuid guid; - data >> guid; - if (guid != bot->GetGUID()) - return false; - - uint32 received; - uint32 created; - uint32 isShowChatMessage; - uint32 notUsed; - uint32 itemId; - uint32 suffixFactor; - uint32 itemRandomPropertyId; - uint32 count; - uint32 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 - - ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId); - if (!item) - return false; - - bot->Say(QueryItem(item, count, GetCount(item)), LANG_UNIVERSAL); - return true; - } - - std::string const text = event.getParam(); - ItemIds items = chat->parseItems(text); - for (uint32 itemId : items) - { - ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId); - if (!item) - continue; - - botAI->TellMaster(QueryItem(item, 0, GetCount(item))); - } - return true; } diff --git a/src/strategy/actions/QuestAction.cpp b/src/strategy/actions/QuestAction.cpp index 260ebc27..30879ace 100644 --- a/src/strategy/actions/QuestAction.cpp +++ b/src/strategy/actions/QuestAction.cpp @@ -8,6 +8,7 @@ #include "Playerbots.h" #include "ReputationMgr.h" #include "ServerFacade.h" +#include "BroadcastHelper.h" bool QuestAction::Execute(Event event) { @@ -226,6 +227,7 @@ bool QuestAction::AcceptQuest(Quest const* quest, ObjectGuid questGiver) if (bot->GetQuestStatus(questId) != QUEST_STATUS_NONE && bot->GetQuestStatus(questId) != QUEST_STATUS_REWARDED) { + BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest); out << "Accepted " << chat->FormatQuest(quest); botAI->TellMaster(out); return true; @@ -253,13 +255,17 @@ bool QuestUpdateCompleteAction::Execute(Event event) Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); if (qInfo) { - const std::string text_quest = ChatHelper::FormatQuest(qInfo); + std::map placeholders; + const auto format = ChatHelper::FormatQuest(qInfo); + placeholders["%quest_link"] = format; + if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) { LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle()); - bot->Say("Quest [ " + text_quest + " ] completed", LANG_UNIVERSAL); + bot->Say("Quest [ " + format + " ] completed", LANG_UNIVERSAL); } - botAI->TellMasterNoFacing("Quest completed " + text_quest); + botAI->TellMasterNoFacing("Quest completed " + format); + BroadcastHelper::BroadcastQuestUpdateComplete(botAI, bot, qInfo); } return true; @@ -276,9 +282,38 @@ bool QuestUpdateAddKillAction::Execute(Event event) uint32 entry, questId, available, required; p >> questId >> entry >> available >> required; - std::stringstream ss; - ss << "Update progression kill questid {" << std::to_string(questId) << "} {" << std::to_string(available) << "} / {" << std::to_string(required) << "}"; - botAI->TellMasterNoFacing(ss.str()); + const Quest* qInfo = sObjectMgr->GetQuestTemplate(questId); + if (qInfo && (entry & 0x80000000)) + { + entry &= 0x7FFFFFFF; + const GameObjectTemplate* info = sObjectMgr->GetGameObjectTemplate(entry); + if (info) + BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->name); + } + else if (qInfo) + { + CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(entry); + if (info) + { + BroadcastHelper::BroadcastQuestUpdateAddKill(botAI, bot, qInfo, available, required, info->Name); + } + } + else + { + std::map placeholders; + placeholders["%quest_id"] = questId; + placeholders["%available"] = available; + placeholders["%required"] = required; + + if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT)) + { + LOG_INFO("playerbots", "{} => {}", bot->GetName(), BOT_TEXT2("%available/%required for questId: %quest_id", placeholders)); + botAI->Say(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders)); + } + + botAI->TellMasterNoFacing(BOT_TEXT2("%available/%required for questId: %quest_id", placeholders)); + } + return false; } @@ -292,11 +327,29 @@ bool QuestUpdateAddItemAction::Execute(Event event) Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); auto const* itemPrototype = sObjectMgr->GetItemTemplate(itemId); + if (itemPrototype) + { + std::map placeholders; + placeholders["%item_link"] = botAI->GetChatHelper()->FormatItem(itemPrototype); + uint32 availableItemsCount = botAI->GetInventoryItemsCountWithId(itemId); + placeholders["%quest_obj_available"] = std::to_string(availableItemsCount); - std::stringstream ss; - ss << "Update progression itemid {" << std::to_string(itemId) << "} count: {" << std::to_string(count) << "}"; - botAI->TellMasterNoFacing(ss.str()); + for (const auto& pair : botAI->GetCurrentQuestsRequiringItemId(itemId)) + { + placeholders["%quest_link"] = chat->FormatQuest(pair.first); + uint32 requiredItemsCount = pair.second; + placeholders["%quest_obj_required"] = std::to_string(requiredItemsCount); + if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_COMBAT) || botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT)) + { + const auto text = BOT_TEXT2("%quest_link - %item_link %quest_obj_available/%quest_obj_required", placeholders); + botAI->Say(text); + LOG_INFO("playerbots", "{} => {}", bot->GetName(), text); + } + BroadcastHelper::BroadcastQuestUpdateAddItem(botAI, bot, pair.first, availableItemsCount, requiredItemsCount, itemPrototype); + } + } + return false; } @@ -320,7 +373,10 @@ bool QuestUpdateFailedTimerAction::Execute(Event event) if (qInfo) { - botAI->TellMaster("Failed timer for " + botAI->GetChatHelper()->FormatQuest(qInfo) +", abandoning"); + std::map placeholders; + placeholders["%quest_link"] = botAI->GetChatHelper()->FormatQuest(qInfo); + botAI->TellMaster(BOT_TEXT2("Failed timer for %quest_link, abandoning", placeholders)); + BroadcastHelper::BroadcastQuestUpdateFailedTimer(botAI, bot, qInfo); } else { diff --git a/src/strategy/actions/SayAction.cpp b/src/strategy/actions/SayAction.cpp index 58789c0f..6f731000 100644 --- a/src/strategy/actions/SayAction.cpp +++ b/src/strategy/actions/SayAction.cpp @@ -122,7 +122,7 @@ bool SayAction::isUseful() 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) +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 = ""; @@ -165,7 +165,7 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32 type, uint32 guid1, uint32 } //toxic links - if (msg.starts_with("gnomes") + if (msg.starts_with(sPlayerbotAIConfig->toxicLinksPrefix) && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0)) { HandleToxicLinksReply(bot, chatChannelSource, msg, name); @@ -179,10 +179,11 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32 type, uint32 guid1, uint32 return; } - SendGeneralResponse(bot, chatChannelSource, GenerateReplyMessage(bot, msg, guid1, name), name); + auto messageRepy = GenerateReplyMessage(bot, msg, guid1, name); + SendGeneralResponse(bot, chatChannelSource, messageRepy, name); } -bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name) +bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { std::map placeholders; const auto thunderfury = sObjectMgr->GetItemTemplate(19019); @@ -208,17 +209,164 @@ bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chat return true; } -bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name) +bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { + //quests + std::vector incompleteQuests; + 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) + incompleteQuests.push_back(questId); + } + + //items + std::vector botItems = GET_PLAYERBOT_AI(bot)->GetInventoryAndEquippedItems(); + + std::map placeholders; + placeholders["%random_inventory_item_link"] = botItems.size() > 0 ? GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(botItems[rand() % botItems.size()]->GetTemplate()) : BOT_TEXT1("string_empty_link"); + placeholders["%prefix"] = sPlayerbotAIConfig->toxicLinksPrefix; + + if (incompleteQuests.size() > 0) + { + Quest const* quest = sObjectMgr->GetQuestTemplate(incompleteQuests[rand() % incompleteQuests.size()]); + placeholders["%random_taken_quest_or_item_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatQuest(quest); + } + else + { + placeholders["%random_taken_quest_or_item_link"] = placeholders["%random_inventory_item_link"]; + } + + placeholders["%my_role"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatClass(bot->getClass()); + AreaTableEntry const* current_area = GET_PLAYERBOT_AI(bot)->GetCurrentArea(); + AreaTableEntry const* current_zone = GET_PLAYERBOT_AI(bot)->GetCurrentZone(); + placeholders["%area_name"] = current_area ? GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + + switch (chatChannelSource) + { + case ChatChannelSource::SRC_WORLD: + { + GET_PLAYERBOT_AI(bot)->SayToWorld(BOT_TEXT2("suggest_toxic_links", placeholders)); + break; + } + case ChatChannelSource::SRC_GENERAL: + { + GET_PLAYERBOT_AI(bot)->SayToChannel(BOT_TEXT2("suggest_toxic_links", placeholders), ChatChannelId::GENERAL); + break; + } + case ChatChannelSource::SRC_GUILD: + { + GET_PLAYERBOT_AI(bot)->SayToGuild(BOT_TEXT2("suggest_toxic_links", placeholders)); + break; + } + } + GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 60)); + return true; } -bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name) +bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { - GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 60)); + auto messageItemIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg); + + if (messageItemIds.empty()) + { + return false; + } + + std::set matchingItemIds; + + for (auto messageItemId : messageItemIds) + { + if (GET_PLAYERBOT_AI(bot)->HasItemInInventory(messageItemId)) + { + matchingItemIds.insert(messageItemId); + } + } + + if (!matchingItemIds.empty()) + { + std::map placeholders; + placeholders["%other_name"] = name; + AreaTableEntry const* current_area = GET_PLAYERBOT_AI(bot)->GetCurrentArea(); + AreaTableEntry const* current_zone = GET_PLAYERBOT_AI(bot)->GetCurrentZone(); + placeholders["%area_name"] = current_area ? GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(current_area) : BOT_TEXT1("string_unknown_area"); + placeholders["%zone_name"] = current_zone ? GET_PLAYERBOT_AI(bot)->GetLocalizedAreaName(current_zone) : BOT_TEXT1("string_unknown_area"); + placeholders["%my_class"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatClass(bot->getClass()); + placeholders["%my_race"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatRace(bot->getRace()); + placeholders["%my_level"] = std::to_string(bot->GetLevel()); + placeholders["%my_role"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatClass(bot->GetSpec()); + placeholders["%formatted_item_links"] = ""; + + for (auto matchingItemId : matchingItemIds) + { + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(matchingItemId); + placeholders["%formatted_item_links"] += GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(proto, GET_PLAYERBOT_AI(bot)->GetInventoryItemsCountWithId(matchingItemId)); + placeholders["%formatted_item_links"] += " "; + } + + switch (chatChannelSource) + { + case ChatChannelSource::SRC_WORLD: + { + //may reply to the same channel or whisper + if (urand(0, 1)) + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_channel", placeholders); + GET_PLAYERBOT_AI(bot)->SayToWorld(responseMessage); + } + else + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_whisper", placeholders); + GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); + } + break; + } + case ChatChannelSource::SRC_GENERAL: + { + //may reply to the same channel or whisper + if (urand(0, 1)) + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_channel", placeholders); + GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL); + } + else + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_whisper", placeholders); + GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); + } + break; + } + case ChatChannelSource::SRC_TRADE: + { + //may reply to the same channel or whisper + if (urand(0, 1)) + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_channel", placeholders); + GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::TRADE); + } + else + { + std::string responseMessage = BOT_TEXT2("response_wtb_items_whisper", placeholders); + GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name); + } + break; + } + } + GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue("last said", "chat")->Set(time(0) + urand(5, 60)); + } + return true; } -bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name) +bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) { auto messageQuestIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg); @@ -303,7 +451,7 @@ bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatCh return true; } -bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string responseMessage, std::string name) +bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name) { // send responds switch (chatChannelSource) @@ -341,13 +489,11 @@ bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatCha case ChatChannelSource::SRC_LOOKING_FOR_GROUP: { //do not reply to the chat - //may whisper break; } case ChatChannelSource::SRC_GUILD_RECRUITMENT: { //do not reply to the chat - //may whisper break; } case ChatChannelSource::SRC_WHISPER: @@ -378,7 +524,7 @@ bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatCha return true; } -std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string incomingMessage, uint32 guid1, std::string name) +std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string& incomingMessage, uint32& guid1, std::string& name) { ChatReplyType replyType = REPLY_NOT_UNDERSTAND; // default not understand diff --git a/src/strategy/actions/SayAction.h b/src/strategy/actions/SayAction.h index 2ba4a9b1..eddf383e 100644 --- a/src/strategy/actions/SayAction.h +++ b/src/strategy/actions/SayAction.h @@ -32,12 +32,12 @@ public: 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); - static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name); - static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name); - static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name); - static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string msg, std::string name); - static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string responseMessage, std::string name); - static std::string GenerateReplyMessage(Player* bot, std::string incomingMessage, uint32 guid1, std::string name); + static void ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name); + static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); + static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); + static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); + static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); + static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name); + static std::string GenerateReplyMessage(Player* bot, std::string& incomingMessage, uint32& guid1, std::string& name); }; #endif diff --git a/src/strategy/actions/SuggestWhatToDoAction.cpp b/src/strategy/actions/SuggestWhatToDoAction.cpp index dcaef0a6..699cf858 100644 --- a/src/strategy/actions/SuggestWhatToDoAction.cpp +++ b/src/strategy/actions/SuggestWhatToDoAction.cpp @@ -11,8 +11,8 @@ #include "ChatHelper.h" #include "Playerbots.h" #include "PlayerbotTextMgr.h" -#include "GuildMgr.h" #include "Config.h" +#include "BroadcastHelper.h" #include @@ -36,6 +36,8 @@ SuggestWhatToDoAction::SuggestWhatToDoAction(PlayerbotAI* botAI, std::string con suggestions.push_back(std::bind(&SuggestWhatToDoAction::grindReputation, this)); suggestions.push_back(std::bind(&SuggestWhatToDoAction::something, this)); suggestions.push_back(std::bind(&SuggestWhatToDoAction::grindMaterials, this)); + suggestions.push_back(std::bind(&SuggestWhatToDoAction::somethingToxic, this)); + suggestions.push_back(std::bind(&SuggestWhatToDoAction::toxicLinks, this)); } bool SuggestWhatToDoAction::isUseful() @@ -85,15 +87,7 @@ void SuggestWhatToDoAction::specificQuest() if (quests.empty()) return; - uint32 index = rand() % quests.size(); - - Quest const* quest = sObjectMgr->GetQuestTemplate(quests[index]); - - std::map placeholders; - placeholders["%role"] = chat->FormatClass(bot, AiFactory::GetPlayerSpecTab(bot)); - placeholders["%quest"] = chat->FormatQuest(quest); - - spam(BOT_TEXT2("suggest_quest", placeholders), urand(0, 1) ? eTalkType::General : 0, !urand(0, 2), !urand(0, 3)); + BroadcastHelper::BroadcastSuggestQuest(botAI, quests, bot); } void SuggestWhatToDoAction::grindMaterials() @@ -187,123 +181,34 @@ void SuggestWhatToDoAction::grindReputation() levels.push_back("exalted"); std::vector allowedFactions; - for (const auto& i : factions) + for (auto it : factions) { - if (bot->GetLevel() >= i.second) - allowedFactions.push_back(i.first); + if ((int)bot->GetLevel() >= it.second) allowedFactions.push_back(it.first); } - if (allowedFactions.empty()) - return; - std::map placeholders; - placeholders["%role"] = chat->FormatClass(bot, AiFactory::GetPlayerSpecTab(bot)); - placeholders["%level"] = levels[urand(0, 2)]; + if (allowedFactions.empty()) return; - std::ostringstream rnd; - rnd << urand(1, 5) << "K"; - placeholders["%rndK"] = rnd.str(); - - std::ostringstream itemout; -// itemout << "|c004040b0" << allowedFactions[urand(0, allowedFactions.size() - 1)] << "|r"; - itemout << allowedFactions[urand(0, allowedFactions.size() - 1)]; - placeholders["%faction"] = itemout.str(); - - spam(BOT_TEXT2("suggest_faction", placeholders), eTalkType::General, true); + BroadcastHelper::BroadcastSuggestGrindReputation(botAI, levels, allowedFactions, bot); } void SuggestWhatToDoAction::something() { - std::map placeholders; - placeholders["%role"] = chat->FormatClass(bot, AiFactory::GetPlayerSpecTab(bot)); - - AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetMap()->GetZoneId(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())); - if (!zone) return; - - std::ostringstream out; -// out << "|cffb04040" << entry->area_name[0] << "|r"; - out << zone->area_name[_dbc_locale]; - placeholders["%zone"] = out.str(); - - spam(BOT_TEXT2("suggest_something", placeholders), urand(0, 1) ? eTalkType::General : 0, !urand(0, 2), !urand(0, 3)); + BroadcastHelper::BroadcastSuggestSomething(botAI, bot); } -void SuggestWhatToDoAction::spam(std::string msg, uint8 flags, bool worldChat, bool guild) +void SuggestWhatToDoAction::somethingToxic() { - if (msg.empty()) - return; + BroadcastHelper::BroadcastSuggestSomethingToxic(botAI, bot); +} - std::vector channelNames; - ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()); - if (!cMgr) - return; +void SuggestWhatToDoAction::toxicLinks() +{ + BroadcastHelper::BroadcastSuggestToxicLinks(botAI, bot); +} - AreaTableEntry const* zone = sAreaTableStore.LookupEntry(bot->GetMap()->GetZoneId(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())); - if (!zone) return; - /*AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetMap()->GetAreaId(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())); - if (!area) return;*/ - - for (uint32 i = 0; i < sChatChannelsStore.GetNumRows(); ++i) - { - ChatChannelsEntry const* channel = sChatChannelsStore.LookupEntry(i); - if (!channel) continue; - - // combine full channel name - char channelName[100]; - Channel* chn = nullptr; - if (channel->ChannelID == 24 || (channel->flags & CHANNEL_DBC_FLAG_LFG) != 0) - { - std::string chanName = channel->pattern[_dbc_locale]; - chn = cMgr->GetChannel(chanName, bot); - } - else - { - snprintf(channelName, 100, channel->pattern[_dbc_locale], zone->area_name[_dbc_locale]); - chn = cMgr->GetChannel(channelName, bot); - } - if (!chn) - continue; - - // skip world chat here - if (chn->GetName() == "World") - continue; - - if (flags != 0 && chn->GetFlags() != flags) - continue; - - // skip local defense and universal defense - if (chn->GetChannelId() == 22 || chn->GetChannelId() == 23) - continue; - - // no filter, pick several options - if (flags == CHANNEL_FLAG_NONE) - { - 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 (sPlayerbotAIConfig->randomBotGuildTalk) - { - botAI->SayToGuild(msg); - } +void SuggestWhatToDoAction::thunderfury() +{ + BroadcastHelper::BroadcastSuggestThunderfury(botAI, bot); } class FindTradeItemsVisitor : public IterateItemsVisitor @@ -394,22 +299,14 @@ bool SuggestDungeonAction::Execute(Event event) } std::vector allowedInstances; - for (const auto& instance : instances) + for (auto it : instances) { - if (bot->GetLevel() >= instance.second) - allowedInstances.push_back(instance.first); + if (bot->GetLevel() >= it.second) allowedInstances.push_back(it.first); } + if (allowedInstances.empty()) return false; - 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) ? eTalkType::LookingForGroup : 0, !urand(0, 2), !urand(0, 3)); + BroadcastHelper::BroadcastSuggestInstance(botAI, allowedInstances, bot); return true; } @@ -469,11 +366,6 @@ bool SuggestTradeAction::Execute(Event event) if (!price) return false; - std::map placeholders; - placeholders["%item"] = chat->FormatItem(proto, count); - placeholders["%gold"] = chat->formatMoney(price); - - - spam(BOT_TEXT2("suggest_sell", placeholders), urand(0, 1) ? eTalkType::Trade : 0, !urand(0, 2), urand(0, 5)); + BroadcastHelper::BroadcastSuggestSell(botAI, proto, count, price, bot); return true; } diff --git a/src/strategy/actions/SuggestWhatToDoAction.h b/src/strategy/actions/SuggestWhatToDoAction.h index 46391ff2..9548737c 100644 --- a/src/strategy/actions/SuggestWhatToDoAction.h +++ b/src/strategy/actions/SuggestWhatToDoAction.h @@ -24,8 +24,10 @@ class SuggestWhatToDoAction : public InventoryAction void grindReputation(); void grindMaterials(); void something(); - void spam(std::string msg, uint8 flags = 0, bool worldChat = false, bool guild = false); - + void toxicLinks(); + void somethingToxic(); + void thunderfury(); + std::vector GetIncompletedQuests(); private: diff --git a/src/strategy/actions/TalkToQuestGiverAction.cpp b/src/strategy/actions/TalkToQuestGiverAction.cpp index 329c0f9b..624d6cec 100644 --- a/src/strategy/actions/TalkToQuestGiverAction.cpp +++ b/src/strategy/actions/TalkToQuestGiverAction.cpp @@ -10,6 +10,7 @@ #include "Playerbots.h" #include "QuestDef.h" #include "WorldPacket.h" +#include "BroadcastHelper.h" bool TalkToQuestGiverAction::ProcessQuest(Quest const* quest, Object* questGiver) { @@ -96,14 +97,19 @@ bool TalkToQuestGiverAction::TurnInQuest(Quest const* quest, Object* questGiver, void TalkToQuestGiverAction::RewardNoItem(Quest const* quest, Object* questGiver, std::ostringstream& out) { + std::map args; + args["%quest"] = chat->FormatQuest(quest); + if (bot->CanRewardQuest(quest, false)) { + out << BOT_TEXT2("quest_status_completed", args); + BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest); + bot->RewardQuest(quest, 0, questGiver, false); - out << "Completed"; } else { - out << "|cffff0000Unable to turn in|r"; + out << BOT_TEXT2("quest_status_unable_to_complete", args); } } @@ -111,15 +117,19 @@ void TalkToQuestGiverAction::RewardSingleItem(Quest const* quest, Object* questG { int index = 0; ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[index]); + std::map args; + args["%quest"] = chat->FormatQuest(quest); + args["%item"] = chat->FormatItem(item); + if (bot->CanRewardQuest(quest, index, false)) { + out << BOT_TEXT2("quest_status_complete_single_reward", args); + BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest); bot->RewardQuest(quest, index, questGiver, true); - - out << "Rewarded " << chat->FormatItem(item); } else { - out << "|cffff0000Unable to turn in:|r, reward: " << chat->FormatItem(item); + out << BOT_TEXT2("quest_status_unable_to_complete", args); } } diff --git a/src/strategy/actions/XpGainAction.cpp b/src/strategy/actions/XpGainAction.cpp index 471b0125..47528768 100644 --- a/src/strategy/actions/XpGainAction.cpp +++ b/src/strategy/actions/XpGainAction.cpp @@ -6,7 +6,7 @@ #include "Event.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" -#include "GuildMgr.h" +#include "BroadcastHelper.h" bool XpGainAction::Execute(Event event) { @@ -33,19 +33,10 @@ bool XpGainAction::Execute(Event event) p >> groupBonus; // 8 group bonus } - if (sPlayerbotAIConfig->randomBotGuildTalk && bot->GetGuildId() && urand(0, 10)) + Creature* creature = botAI->GetCreature(guid); + if (creature && !creature->GetMap()->IsDungeon()) { - Creature* creature = botAI->GetCreature(guid); - if (creature && (creature->isElite() || creature->isWorldBoss() || creature->GetLevel() > 61 || creature->GetLevel() > bot->GetLevel() + 4)) - { - Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); - if (guild) - { - std::map args; - args["%victim_name"] = creature->GetName(); - botAI->SayToGuild(BOT_TEXT2("broadcast_killed_rare", args)); - } - } + BroadcastHelper::BroadcastKill(botAI, bot, creature); } Unit* victim = nullptr;