Directory reorganization

This commit is contained in:
Yunfan Li
2025-05-07 00:06:31 +08:00
parent 0d170f5370
commit 6003dbc1b5
948 changed files with 28 additions and 28 deletions

View File

@@ -0,0 +1,960 @@
#include "Playerbots.h"
#include "BroadcastHelper.h"
#include "ServerFacade.h"
#include "Channel.h"
#include "AiFactory.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<std::string, std::string> 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<std::pair<ToChannel, uint32>> toChannels)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestAccepted)
{
std::map<std::string, std::string> 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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
std::map<std::string, std::string> 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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateFailedTimer)
{
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestUpdateComplete)
{
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceQuestTurnedIn)
{
std::map<std::string, std::string> 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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
std::map<std::string, std::string> 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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
uint32 level = bot->GetLevel();
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceGuildManagement)
{
std::map<std::string, std::string> 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<std::string, std::string> 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)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
std::map<std::string, std::string> 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<std::string>& allowedInstances, Player* bot)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestInstance)
{
std::map<std::string, std::string> placeholders;
placeholders["%my_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_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<uint32>& quests, Player* bot)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestQuest)
{
int index = rand() % quests.size();
Quest const* quest = sObjectMgr->GetQuestTemplate(quests[index]);
std::map<std::string, std::string> placeholders;
placeholders["%my_role"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestGrindMaterials)
{
std::map<std::string, std::string> placeholders;
placeholders["%my_role"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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<std::string> levels, std::vector<std::string> allowedFactions, Player* bot)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestGrindReputation)
{
std::map<std::string, std::string> placeholders;
placeholders["%my_role"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSell)
{
std::map<std::string, std::string> 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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSomething)
{
std::map<std::string, std::string> placeholders;
placeholders["%my_role"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestSomethingToxic)
{
//items
std::vector<Item*> botItems = ai->GetInventoryAndEquippedItems();
std::map<std::string, std::string> 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"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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 (!sPlayerbotAIConfig->enableBroadcasts)
return false;
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceSuggestToxicLinks)
{
//quests
std::vector<uint32> 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<Item*> botItems = ai->GetInventoryAndEquippedItems();
//spells
//?
std::map<std::string, std::string> 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"] = ChatHelper::FormatClass(bot, AiFactory::GetPlayerSpecTab(bot));
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<std::string, std::string> 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;
}

View File

@@ -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<std::pair<ToChannel, uint32_t>> 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<std::string>& allowedInstances,
Player* bot
);
static bool BroadcastSuggestQuest(
PlayerbotAI* ai,
std::vector<uint32>& quests,
Player* bot
);
static bool BroadcastSuggestGrindMaterials(
PlayerbotAI* ai,
std::string item,
Player* bot
);
static bool BroadcastSuggestGrindReputation(
PlayerbotAI* ai,
std::vector<std::string> levels,
std::vector<std::string> 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
);
};

299
src/helper/ChatFilter.cpp Normal file
View File

@@ -0,0 +1,299 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "ChatFilter.h"
#include "Group.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
std::string const ChatFilter::Filter(std::string& message)
{
if (message.find("@") == std::string::npos)
return message;
return message.substr(message.find(" ") + 1);
}
class StrategyChatFilter : public ChatFilter
{
public:
StrategyChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
bool tank = message.find("@tank") == 0;
if (tank && !botAI->IsTank(bot))
return "";
bool dps = message.find("@dps") == 0;
if (dps && (botAI->IsTank(bot) || botAI->IsHeal(bot)))
return "";
bool heal = message.find("@heal") == 0;
if (heal && !botAI->IsHeal(bot))
return "";
bool ranged = message.find("@ranged") == 0;
if (ranged && !botAI->IsRanged(bot))
return "";
bool melee = message.find("@melee") == 0;
if (melee && botAI->IsRanged(bot))
return "";
if (tank || dps || heal || ranged || melee)
return ChatFilter::Filter(message);
return message;
}
};
class LevelChatFilter : public ChatFilter
{
public:
LevelChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
if (message[0] != '@')
return message;
if (message.find("-") != std::string::npos)
{
uint32 fromLevel = atoi(message.substr(message.find("@") + 1, message.find("-")).c_str());
uint32 toLevel = atoi(message.substr(message.find("-") + 1, message.find(" ")).c_str());
if (bot->GetLevel() >= fromLevel && bot->GetLevel() <= toLevel)
return ChatFilter::Filter(message);
return message;
}
uint32 level = atoi(message.substr(message.find("@") + 1, message.find(" ")).c_str());
if (bot->GetLevel() == level)
return ChatFilter::Filter(message);
return message;
}
};
class CombatTypeChatFilter : public ChatFilter
{
public:
CombatTypeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
bool melee = message.find("@melee") == 0;
bool ranged = message.find("@ranged") == 0;
if (!melee && !ranged)
return message;
switch (bot->getClass())
{
case CLASS_WARRIOR:
case CLASS_PALADIN:
case CLASS_ROGUE:
case CLASS_DEATH_KNIGHT:
if (ranged)
return "";
break;
case CLASS_HUNTER:
case CLASS_PRIEST:
case CLASS_MAGE:
case CLASS_WARLOCK:
if (melee)
return "";
break;
case CLASS_DRUID:
if (ranged && botAI->IsTank(bot))
return "";
if (melee && !botAI->IsTank(bot))
return "";
break;
case CLASS_SHAMAN:
if (melee && botAI->IsHeal(bot))
return "";
if (ranged && !botAI->IsHeal(bot))
return "";
break;
}
return ChatFilter::Filter(message);
}
};
class RtiChatFilter : public ChatFilter
{
public:
RtiChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{
rtis.push_back("@star");
rtis.push_back("@circle");
rtis.push_back("@diamond");
rtis.push_back("@triangle");
rtis.push_back("@moon");
rtis.push_back("@square");
rtis.push_back("@cross");
rtis.push_back("@skull");
}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
Group* group = bot->GetGroup();
if (!group)
return message;
bool found = false;
//bool isRti = false; //not used, shadowed by the next declaration, line marked for removal.
for (std::vector<std::string>::iterator i = rtis.begin(); i != rtis.end(); i++)
{
std::string const rti = *i;
bool isRti = message.find(rti) == 0;
if (!isRti)
continue;
ObjectGuid rtiTarget = group->GetTargetIcon(RtiTargetValue::GetRtiIndex(rti.substr(1)));
if (bot->GetGUID() == rtiTarget)
return ChatFilter::Filter(message);
Unit* target = *botAI->GetAiObjectContext()->GetValue<Unit*>("current target");
if (!target)
return "";
if (target->GetGUID() != rtiTarget)
return "";
found |= isRti;
if (found)
break;
}
if (found)
return ChatFilter::Filter(message);
return message;
}
private:
std::vector<std::string> rtis;
};
class ClassChatFilter : public ChatFilter
{
public:
ClassChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{
classNames["@death_knight"] = CLASS_DEATH_KNIGHT;
classNames["@druid"] = CLASS_DRUID;
classNames["@hunter"] = CLASS_HUNTER;
classNames["@mage"] = CLASS_MAGE;
classNames["@paladin"] = CLASS_PALADIN;
classNames["@priest"] = CLASS_PRIEST;
classNames["@rogue"] = CLASS_ROGUE;
classNames["@shaman"] = CLASS_SHAMAN;
classNames["@warlock"] = CLASS_WARLOCK;
classNames["@warrior"] = CLASS_WARRIOR;
}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
bool found = false;
//bool isClass = false; //not used, shadowed by the next declaration, line marked for removal.
for (std::map<std::string, uint8>::iterator i = classNames.begin(); i != classNames.end(); i++)
{
bool isClass = message.find(i->first) == 0;
if (isClass && bot->getClass() != i->second)
return "";
found |= isClass;
if (found)
break;
}
if (found)
return ChatFilter::Filter(message);
return message;
}
private:
std::map<std::string, uint8> classNames;
};
class SubGroupChatFilter : public ChatFilter
{
public:
SubGroupChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
std::string const Filter(std::string& message) override
{
Player* bot = botAI->GetBot();
if (message.find("@group") == 0)
{
std::string const pnum = message.substr(6, message.find(" "));
uint32 from = atoi(pnum.c_str());
uint32 to = from;
if (pnum.find("-") != std::string::npos)
{
from = atoi(pnum.substr(pnum.find("@") + 1, pnum.find("-")).c_str());
to = atoi(pnum.substr(pnum.find("-") + 1, pnum.find(" ")).c_str());
}
if (!bot->GetGroup())
return message;
uint32 sg = bot->GetSubGroup() + 1;
if (sg >= from && sg <= to)
return ChatFilter::Filter(message);
}
return message;
}
};
CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{
filters.push_back(new StrategyChatFilter(botAI));
filters.push_back(new ClassChatFilter(botAI));
filters.push_back(new RtiChatFilter(botAI));
filters.push_back(new CombatTypeChatFilter(botAI));
filters.push_back(new LevelChatFilter(botAI));
filters.push_back(new SubGroupChatFilter(botAI));
}
CompositeChatFilter::~CompositeChatFilter()
{
for (std::vector<ChatFilter*>::iterator i = filters.begin(); i != filters.end(); i++)
delete (*i);
}
std::string const CompositeChatFilter::Filter(std::string& message)
{
for (uint32 j = 0; j < filters.size(); ++j)
{
for (std::vector<ChatFilter*>::iterator i = filters.begin(); i != filters.end(); i++)
{
message = (*i)->Filter(message);
if (message.empty())
break;
}
}
return message;
}

37
src/helper/ChatFilter.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CHATFILTER_H
#define _PLAYERBOT_CHATFILTER_H
#include <vector>
#include "Common.h"
#include "PlayerbotAIAware.h"
class PlayerbotAI;
class ChatFilter : public PlayerbotAIAware
{
public:
ChatFilter(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) {}
virtual ~ChatFilter() {}
virtual std::string const Filter(std::string& message);
};
class CompositeChatFilter : public ChatFilter
{
public:
CompositeChatFilter(PlayerbotAI* botAI);
virtual ~CompositeChatFilter();
std::string const Filter(std::string& message) override;
private:
std::vector<ChatFilter*> filters;
};
#endif

658
src/helper/ChatHelper.cpp Normal file
View File

@@ -0,0 +1,658 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "ChatHelper.h"
#include "AiFactory.h"
#include "Common.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "Playerbots.h"
#include "SpellInfo.h"
#include <regex>
std::map<std::string, uint32> ChatHelper::consumableSubClasses;
std::map<std::string, uint32> ChatHelper::tradeSubClasses;
std::map<std::string, uint32> ChatHelper::itemQualities;
std::map<std::string, uint32> ChatHelper::projectileSubClasses;
std::map<std::string, uint32> ChatHelper::slots;
std::map<std::string, uint32> ChatHelper::skills;
std::map<std::string, ChatMsg> ChatHelper::chats;
std::map<uint8, std::string> ChatHelper::classes;
std::map<uint8, std::string> ChatHelper::races;
std::map<uint8, std::map<uint8, std::string> > ChatHelper::specs;
template <class T>
static bool substrContainsInMap(std::string const searchTerm, std::map<std::string, T> searchIn)
{
for (typename std::map<std::string, T>::iterator i = searchIn.begin(); i != searchIn.end(); ++i)
{
std::string const term = i->first;
if (term.size() > 1 && searchTerm.find(term) != std::string::npos)
return true;
}
return false;
}
ChatHelper::ChatHelper(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
{
itemQualities["poor"] = ITEM_QUALITY_POOR;
itemQualities["gray"] = ITEM_QUALITY_POOR;
itemQualities["normal"] = ITEM_QUALITY_NORMAL;
itemQualities["white"] = ITEM_QUALITY_NORMAL;
itemQualities["uncommon"] = ITEM_QUALITY_UNCOMMON;
itemQualities["green"] = ITEM_QUALITY_UNCOMMON;
itemQualities["rare"] = ITEM_QUALITY_RARE;
itemQualities["blue"] = ITEM_QUALITY_RARE;
itemQualities["epic"] = ITEM_QUALITY_EPIC;
itemQualities["violet"] = ITEM_QUALITY_EPIC;
itemQualities["legendary"] = ITEM_QUALITY_LEGENDARY;
itemQualities["yellow"] = ITEM_QUALITY_LEGENDARY;
consumableSubClasses["potion"] = ITEM_SUBCLASS_POTION;
consumableSubClasses["elixir"] = ITEM_SUBCLASS_ELIXIR;
consumableSubClasses["flask"] = ITEM_SUBCLASS_FLASK;
consumableSubClasses["scroll"] = ITEM_SUBCLASS_SCROLL;
consumableSubClasses["food"] = ITEM_SUBCLASS_FOOD;
consumableSubClasses["bandage"] = ITEM_SUBCLASS_BANDAGE;
consumableSubClasses["enchant"] = ITEM_SUBCLASS_CONSUMABLE_OTHER;
projectileSubClasses["arrows"] = ITEM_SUBCLASS_ARROW;
projectileSubClasses["bullets"] = ITEM_SUBCLASS_BULLET;
// tradeSubClasses["cloth"] = ITEM_SUBCLASS_CLOTH;
// tradeSubClasses["leather"] = ITEM_SUBCLASS_LEATHER;
// tradeSubClasses["metal"] = ITEM_SUBCLASS_METAL_STONE;
// tradeSubClasses["stone"] = ITEM_SUBCLASS_METAL_STONE;
// tradeSubClasses["ore"] = ITEM_SUBCLASS_METAL_STONE;
// tradeSubClasses["meat"] = ITEM_SUBCLASS_MEAT;
// tradeSubClasses["herb"] = ITEM_SUBCLASS_HERB;
// tradeSubClasses["elemental"] = ITEM_SUBCLASS_ELEMENTAL;
// tradeSubClasses["disenchants"] = ITEM_SUBCLASS_ENCHANTING;
// tradeSubClasses["enchanting"] = ITEM_SUBCLASS_ENCHANTING;
// tradeSubClasses["gems"] = ITEM_SUBCLASS_JEWELCRAFTING;
// tradeSubClasses["jewels"] = ITEM_SUBCLASS_JEWELCRAFTING;
// tradeSubClasses["jewelcrafting"] = ITEM_SUBCLASS_JEWELCRAFTING;
slots["head"] = EQUIPMENT_SLOT_HEAD;
slots["neck"] = EQUIPMENT_SLOT_NECK;
slots["shoulder"] = EQUIPMENT_SLOT_SHOULDERS;
slots["shirt"] = EQUIPMENT_SLOT_BODY;
slots["chest"] = EQUIPMENT_SLOT_CHEST;
slots["waist"] = EQUIPMENT_SLOT_WAIST;
slots["legs"] = EQUIPMENT_SLOT_LEGS;
slots["feet"] = EQUIPMENT_SLOT_FEET;
slots["wrist"] = EQUIPMENT_SLOT_WRISTS;
slots["hands"] = EQUIPMENT_SLOT_HANDS;
slots["finger 1"] = EQUIPMENT_SLOT_FINGER1;
slots["finger 2"] = EQUIPMENT_SLOT_FINGER2;
slots["trinket 1"] = EQUIPMENT_SLOT_TRINKET1;
slots["trinket 2"] = EQUIPMENT_SLOT_TRINKET2;
slots["back"] = EQUIPMENT_SLOT_BACK;
slots["main hand"] = EQUIPMENT_SLOT_MAINHAND;
slots["off hand"] = EQUIPMENT_SLOT_OFFHAND;
slots["ranged"] = EQUIPMENT_SLOT_RANGED;
slots["tabard"] = EQUIPMENT_SLOT_TABARD;
skills["first aid"] = SKILL_FIRST_AID;
skills["fishing"] = SKILL_FISHING;
skills["cooking"] = SKILL_COOKING;
skills["alchemy"] = SKILL_ALCHEMY;
skills["enchanting"] = SKILL_ENCHANTING;
skills["engineering"] = SKILL_ENGINEERING;
skills["leatherworking"] = SKILL_LEATHERWORKING;
skills["blacksmithing"] = SKILL_BLACKSMITHING;
skills["tailoring"] = SKILL_TAILORING;
skills["herbalism"] = SKILL_HERBALISM;
skills["mining"] = SKILL_MINING;
skills["skinning"] = SKILL_SKINNING;
skills["jewelcrafting"] = SKILL_JEWELCRAFTING;
chats["party"] = CHAT_MSG_PARTY;
chats["p"] = CHAT_MSG_PARTY;
chats["guild"] = CHAT_MSG_GUILD;
chats["g"] = CHAT_MSG_GUILD;
chats["raid"] = CHAT_MSG_RAID;
chats["r"] = CHAT_MSG_RAID;
chats["whisper"] = CHAT_MSG_WHISPER;
chats["w"] = CHAT_MSG_WHISPER;
classes[CLASS_DRUID] = "druid";
specs[CLASS_DRUID][0] = "balance";
specs[CLASS_DRUID][1] = "feral combat";
specs[CLASS_DRUID][2] = "restoration";
classes[CLASS_HUNTER] = "hunter";
specs[CLASS_HUNTER][0] = "beast mastery";
specs[CLASS_HUNTER][1] = "marksmanship";
specs[CLASS_HUNTER][2] = "survival";
classes[CLASS_MAGE] = "mage";
specs[CLASS_MAGE][0] = "arcane";
specs[CLASS_MAGE][1] = "fire";
specs[CLASS_MAGE][2] = "frost";
classes[CLASS_PALADIN] = "paladin";
specs[CLASS_PALADIN][0] = "holy";
specs[CLASS_PALADIN][1] = "protection";
specs[CLASS_PALADIN][2] = "retribution";
classes[CLASS_PRIEST] = "priest";
specs[CLASS_PRIEST][0] = "discipline";
specs[CLASS_PRIEST][1] = "holy";
specs[CLASS_PRIEST][2] = "shadow";
classes[CLASS_ROGUE] = "rogue";
specs[CLASS_ROGUE][0] = "assasination";
specs[CLASS_ROGUE][1] = "combat";
specs[CLASS_ROGUE][2] = "subtlety";
classes[CLASS_SHAMAN] = "shaman";
specs[CLASS_SHAMAN][0] = "elemental";
specs[CLASS_SHAMAN][1] = "enhancement";
specs[CLASS_SHAMAN][2] = "restoration";
classes[CLASS_WARLOCK] = "warlock";
specs[CLASS_WARLOCK][0] = "affliction";
specs[CLASS_WARLOCK][1] = "demonology";
specs[CLASS_WARLOCK][2] = "destruction";
classes[CLASS_WARRIOR] = "warrior";
specs[CLASS_WARRIOR][0] = "arms";
specs[CLASS_WARRIOR][1] = "fury";
specs[CLASS_WARRIOR][2] = "protection";
classes[CLASS_DEATH_KNIGHT] = "dk";
specs[CLASS_DEATH_KNIGHT][0] = "blood";
specs[CLASS_DEATH_KNIGHT][1] = "frost";
specs[CLASS_DEATH_KNIGHT][2] = "unholy";
races[RACE_DWARF] = "Dwarf";
races[RACE_GNOME] = "Gnome";
races[RACE_HUMAN] = "Human";
races[RACE_NIGHTELF] = "Night Elf";
races[RACE_ORC] = "Orc";
races[RACE_TAUREN] = "Tauren";
races[RACE_TROLL] = "Troll";
races[RACE_UNDEAD_PLAYER] = "Undead";
races[RACE_BLOODELF] = "Blood Elf";
races[RACE_DRAENEI] = "Draenei";
}
std::string const ChatHelper::formatMoney(uint32 copper)
{
std::ostringstream out;
if (!copper)
{
out << "0";
return out.str();
}
uint32 gold = uint32(copper / 10000);
copper -= (gold * 10000);
uint32 silver = uint32(copper / 100);
copper -= (silver * 100);
bool space = false;
if (gold > 0)
{
out << gold << "g";
space = true;
}
if (silver > 0 && gold < 50)
{
if (space)
out << " ";
out << silver << "s";
space = true;
}
if (copper > 0 && gold < 10)
{
if (space)
out << " ";
out << copper << "c";
}
return out.str();
}
std::string ChatHelper::parseValue(const std::string& type, const std::string& text)
{
std::string retString;
std::string pattern = "Hvalue:" + type + ":";
int pos = text.find(pattern, 0);
if (pos == -1)
return retString;
pos += pattern.size();
int endPos = text.find('|', pos);
if (endPos == -1)
return retString;
retString = text.substr(pos, endPos - pos);
return retString;
}
uint32 ChatHelper::parseMoney(std::string const text)
{
// if user specified money in ##g##s##c format
std::string acum = "";
uint32 copper = 0;
for (uint8 i = 0; i < text.length(); i++)
{
if (text[i] == 'g')
{
copper += (atol(acum.c_str()) * 100 * 100);
acum = "";
}
else if (text[i] == 'c')
{
copper += atol(acum.c_str());
acum = "";
}
else if (text[i] == 's')
{
copper += (atol(acum.c_str()) * 100);
acum = "";
}
else if (text[i] == ' ')
break;
else if (text[i] >= 48 && text[i] <= 57)
acum += text[i];
else
{
copper = 0;
break;
}
}
return copper;
}
ItemIds ChatHelper::parseItems(std::string const text)
{
ItemIds itemIds;
uint8 pos = 0;
while (true)
{
auto i = text.find("Hitem:", pos);
if (i == std::string::npos)
break;
pos = i + 6;
auto endPos = text.find(':', pos);
if (endPos == std::string::npos)
break;
std::string const idC = text.substr(pos, endPos - pos);
auto id = atol(idC.c_str());
pos = endPos;
if (id)
itemIds.insert(id);
}
return itemIds;
}
std::string const ChatHelper::FormatQuest(Quest const* quest)
{
if (!quest)
{
return "Invalid quest";
}
std::ostringstream out;
QuestLocale const* locale = sObjectMgr->GetQuestLocale(quest->GetQuestId());
std::string questTitle;
if (locale && locale->Title.size() > sWorld->GetDefaultDbcLocale())
questTitle = locale->Title[sWorld->GetDefaultDbcLocale()];
if (questTitle.empty())
questTitle = quest->GetTitle();
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << questTitle << "]|h|r";
return out.str();
}
std::string const ChatHelper::FormatGameobject(GameObject* go)
{
std::ostringstream out;
out << "|cFFFFFF00|Hfound:" << go->GetGUID().GetRawValue() << ":" << go->GetEntry() << ":"
<< "|h[" << go->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()) << "]|h|r";
return out.str();
}
std::string const ChatHelper::FormatWorldobject(WorldObject* wo)
{
std::ostringstream out;
out << "|cFFFFFF00|Hfound:" << wo->GetGUID().GetRawValue() << ":" << wo->GetEntry() << ":"
<< "|h[";
out << (wo->ToGameObject() ? ((GameObject*)wo)->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale())
: wo->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()))
<< "]|h|r";
return out.str();
}
std::string const ChatHelper::FormatWorldEntry(int32 entry)
{
CreatureTemplate const* cInfo = nullptr;
GameObjectTemplate const* gInfo = nullptr;
if (entry > 0)
cInfo = sObjectMgr->GetCreatureTemplate(entry);
else
gInfo = sObjectMgr->GetGameObjectTemplate(entry * -1);
std::ostringstream out;
out << "|cFFFFFF00|Hentry:" << abs(entry) << ":"
<< "|h[";
if (entry < 0 && gInfo)
out << gInfo->name;
else if (entry > 0 && cInfo)
out << cInfo->Name;
else
out << "unknown";
out << "]|h|r";
return out.str();
}
std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo)
{
std::ostringstream out;
std::string spellName = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] ?
spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] : spellInfo->SpellName[LOCALE_enUS];
out << "|cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellName << "]|h|r";
return out.str();
}
std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count, uint32 total)
{
char color[32];
sprintf(color, "%x", ItemQualityColors[proto->Quality]);
std::string itemName;
const ItemLocale* locale = sObjectMgr->GetItemLocale(proto->ItemId);
if (locale && locale->Name.size() > sWorld->GetDefaultDbcLocale())
itemName = locale->Name[sWorld->GetDefaultDbcLocale()];
if (itemName.empty())
itemName = proto->Name1;
std::ostringstream out;
out << "|c" << color << "|Hitem:" << proto->ItemId << ":0:0:0:0:0:0:0"
<< "|h[" << itemName << "]|h|r";
if (count > 1)
out << "x" << count;
if (total > 0)
out << " (" << total << ")";
return out.str();
}
std::string const ChatHelper::FormatQItem(uint32 itemId)
{
char color[32];
sprintf(color, "%x", ItemQualityColors[0]);
std::ostringstream out;
out << "|c" << color << "|Hitem:" << itemId << ":0:0:0:0:0:0:0"
<< "|h[item"
<< "]|h|r";
return out.str();
}
ChatMsg ChatHelper::parseChat(std::string const text)
{
if (chats.find(text) != chats.end())
return chats[text];
return CHAT_MSG_SYSTEM;
}
std::string const ChatHelper::FormatChat(ChatMsg chat)
{
switch (chat)
{
case CHAT_MSG_GUILD:
return "guild";
case CHAT_MSG_PARTY:
return "party";
case CHAT_MSG_WHISPER:
return "whisper";
case CHAT_MSG_RAID:
return "raid";
default:
break;
}
return "unknown";
}
uint32 ChatHelper::parseSpell(std::string const text)
{
PlayerbotChatHandler handler(botAI->GetBot());
return handler.extractSpellId(text);
}
GuidVector ChatHelper::parseGameobjects(std::string const text)
{
GuidVector gos;
// Link format
// |cFFFFFF00|Hfound:" << guid << ':' << entry << ':' << "|h[" << gInfo->name << "]|h|r";
// |cFFFFFF00|Hfound:9582:1731|h[Copper Vein]|h|r
uint8 pos = 0;
while (true)
{
// extract GO guid
auto i = text.find("Hfound:", pos); // base H = 11
if (i == std::string::npos) // break if error
break;
pos = i + 7; // start of window in text 11 + 7 = 18
auto endPos = text.find(':', pos); // end of window in text 22
if (endPos == std::string::npos) // break if error
break;
std::istringstream stream(text.substr(pos, endPos - pos));
uint64 guid;
stream >> guid;
// extract GO entry
pos = endPos + 1;
endPos = text.find(':', pos); // end of window in text
if (endPos == std::string::npos) // break if error
break;
std::string const entryC = text.substr(pos, endPos - pos); // get std::string const within window i.e entry
//uint32 entry = atol(entryC.c_str()); // convert ascii to float
ObjectGuid lootCurrent = ObjectGuid(guid);
if (guid)
gos.push_back(lootCurrent);
}
return gos;
}
std::string const ChatHelper::FormatQuestObjective(std::string const name, uint32 available, uint32 required)
{
std::ostringstream out;
out << "|cFFFFFFFF" << name << (available >= required ? "|c0000FF00: " : "|c00FF0000: ") << available << "/"
<< required << "|r";
return out.str();
}
uint32 ChatHelper::parseItemQuality(std::string const text)
{
if (itemQualities.find(text) == itemQualities.end())
return MAX_ITEM_QUALITY;
return itemQualities[text];
}
bool ChatHelper::parseItemClass(std::string const text, uint32* itemClass, uint32* itemSubClass)
{
if (text == "questitem")
{
*itemClass = ITEM_CLASS_QUEST;
*itemSubClass = ITEM_SUBCLASS_QUEST;
return true;
}
if (consumableSubClasses.find(text) != consumableSubClasses.end())
{
*itemClass = ITEM_CLASS_CONSUMABLE;
*itemSubClass = consumableSubClasses[text];
return true;
}
if (tradeSubClasses.find(text) != tradeSubClasses.end())
{
*itemClass = ITEM_CLASS_TRADE_GOODS;
*itemSubClass = tradeSubClasses[text];
return true;
}
if (projectileSubClasses.find(text) != projectileSubClasses.end())
{
*itemClass = ITEM_CLASS_PROJECTILE;
*itemSubClass = projectileSubClasses[text];
return true;
}
return false;
}
uint32 ChatHelper::parseSlot(std::string const text)
{
if (slots.find(text) != slots.end())
return slots[text];
return EQUIPMENT_SLOT_END;
}
bool ChatHelper::parseable(std::string const text)
{
return text.find("|H") != std::string::npos || text == "questitem" || text == "ammo" ||
substrContainsInMap<uint32>(text, consumableSubClasses) ||
substrContainsInMap<uint32>(text, tradeSubClasses) || substrContainsInMap<uint32>(text, itemQualities) ||
substrContainsInMap<uint32>(text, slots) || substrContainsInMap<ChatMsg>(text, chats) ||
substrContainsInMap<uint32>(text, skills) || parseMoney(text) > 0;
}
std::string const ChatHelper::FormatClass(Player* player, int8 spec)
{
uint8 cls = player->getClass();
std::ostringstream out;
out << specs[cls][spec] << " (";
std::map<uint8, uint32> tabs = AiFactory::GetPlayerSpecTabs(player);
uint32 c0 = tabs[0];
uint32 c1 = tabs[1];
uint32 c2 = tabs[2];
out << (c0 ? "|h|cff00ff00" : "") << c0 << "|h|cffffffff/";
out << (c1 ? "|h|cff00ff00" : "") << c1 << "|h|cffffffff/";
out << (c2 ? "|h|cff00ff00" : "") << c2 << "|h|cffffffff";
out << ")|r " << classes[cls];
return out.str();
}
std::string const ChatHelper::FormatClass(uint8 cls) { return classes[cls]; }
std::string const ChatHelper::FormatRace(uint8 race) { return races[race]; }
uint32 ChatHelper::parseSkill(std::string const text)
{
if (skills.find(text) != skills.end())
return skills[text];
return SKILL_NONE;
}
std::string const ChatHelper::FormatSkill(uint32 skill)
{
for (std::map<std::string, uint32>::iterator i = skills.begin(); i != skills.end(); ++i)
{
if (i->second == skill)
return i->first;
}
return "";
}
std::string const ChatHelper::FormatBoolean(bool flag) { return flag ? "|cff00ff00ON|r" : "|cffffff00OFF|r"; }
void ChatHelper::eraseAllSubStr(std::string& mainStr, std::string const toErase)
{
size_t pos = std::string::npos;
// Search for the substring in std::string const in a loop untill nothing is found
while ((pos = mainStr.find(toErase)) != std::string::npos)
{
// If found then erase it from std::string
mainStr.erase(pos, toErase.length());
}
}
std::set<uint32> extractGeneric(std::string_view text, std::string_view prefix)
{
std::set<uint32_t> ids;
std::string_view text_view = text;
size_t pos = 0;
while ((pos = text_view.find(prefix, pos)) != std::string::npos)
{
// skip "Hquest:/Hitem:"
pos += prefix.size();
// extract everything after "Hquest:/Hitem:"
size_t end_pos = text_view.find_first_not_of("0123456789", pos);
std::string_view number_str = text_view.substr(pos, end_pos - pos);
uint32 number = 0;
auto [ptr, ec] = std::from_chars(number_str.data(), number_str.data() + number_str.size(), number);
if (ec == std::errc())
{
ids.insert(number);
}
pos = end_pos;
}
return ids;
}
std::set<uint32> ChatHelper::ExtractAllQuestIds(const std::string& text)
{
return extractGeneric(text, "Hquest:");
}
std::set<uint32> ChatHelper::ExtractAllItemIds(const std::string& text)
{
return extractGeneric(text, "Hitem:");
}

83
src/helper/ChatHelper.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CHATHELPER_H
#define _PLAYERBOT_CHATHELPER_H
#include <map>
#include "Common.h"
#include "ObjectGuid.h"
#include "PlayerbotAIAware.h"
#include "SharedDefines.h"
class GameObject;
class Quest;
class Player;
class PlayerbotAI;
class SpellInfo;
class WorldObject;
struct ItemTemplate;
typedef std::set<uint32> ItemIds;
typedef std::set<uint32> SpellIds;
class ChatHelper : public PlayerbotAIAware
{
public:
ChatHelper(PlayerbotAI* botAI);
static std::string const formatMoney(uint32 copper);
static uint32 parseMoney(std::string const text);
static ItemIds parseItems(std::string const text);
uint32 parseSpell(std::string const text);
static std::string parseValue(const std::string& type, const std::string& text);
static std::string const FormatQuest(Quest const* quest);
static std::string const FormatItem(ItemTemplate const* proto, uint32 count = 0, uint32 total = 0);
static std::string const FormatQItem(uint32 itemId);
static std::string const FormatSpell(SpellInfo const* spellInfo);
static std::string const FormatGameobject(GameObject* go);
static std::string const FormatWorldobject(WorldObject* wo);
static std::string const FormatWorldEntry(int32 entry);
static std::string const FormatQuestObjective(std::string const name, uint32 available, uint32 required);
static GuidVector parseGameobjects(std::string const text);
static ChatMsg parseChat(std::string const text);
static std::string const FormatChat(ChatMsg chat);
static std::string const FormatClass(Player* player, int8 spec);
static std::string const FormatClass(uint8 cls);
static std::string const FormatRace(uint8 race);
static std::string const FormatSkill(uint32 skill);
static std::string const FormatBoolean(bool flag);
static uint32 parseItemQuality(std::string const text);
static bool parseItemClass(std::string const text, uint32* itemClass, uint32* itemSubClass);
static uint32 parseSlot(std::string const text);
uint32 parseSkill(std::string const text);
static bool parseable(std::string const text);
void eraseAllSubStr(std::string& mainStr, std::string const toErase);
static std::set<uint32> ExtractAllQuestIds(const std::string& text);
static std::set<uint32> ExtractAllItemIds(const std::string& text);
private:
static std::map<std::string, uint32> consumableSubClasses;
static std::map<std::string, uint32> tradeSubClasses;
static std::map<std::string, uint32> itemQualities;
static std::map<std::string, uint32> projectileSubClasses;
static std::map<std::string, uint32> slots;
static std::map<std::string, uint32> skills;
static std::map<std::string, ChatMsg> chats;
static std::map<uint8, std::string> classes;
static std::map<uint8, std::string> races;
static std::map<uint8, std::map<uint8, std::string>> specs;
};
#endif

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "ExternalEventHelper.h"
#include "ChatHelper.h"
#include "Playerbots.h"
#include "Trigger.h"
bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* owner)
{
if (HandleCommand(command, "", owner))
return true;
size_t i = std::string::npos;
while (true)
{
size_t found = command.rfind(" ", i);
if (found == std::string::npos || !found)
break;
std::string const name = command.substr(0, found);
std::string const param = command.substr(found + 1);
i = found - 1;
if (HandleCommand(name, param, owner))
return true;
}
if (!ChatHelper::parseable(command))
return false;
HandleCommand("c", command, owner);
HandleCommand("t", command, owner);
return true;
}
void ExternalEventHelper::HandlePacket(std::map<uint16, std::string>& handlers, WorldPacket const& packet,
Player* owner)
{
uint16 opcode = packet.GetOpcode();
std::string const name = handlers[opcode];
if (name.empty())
return;
Trigger* trigger = aiObjectContext->GetTrigger(name);
if (!trigger)
return;
WorldPacket p(packet);
trigger->ExternalEvent(p, owner);
}
bool ExternalEventHelper::HandleCommand(std::string const name, std::string const param, Player* owner)
{
Trigger* trigger = aiObjectContext->GetTrigger(name);
if (!trigger)
return false;
trigger->ExternalEvent(param, owner);
return true;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_EXTERNALEVENTHELPER_H
#define _PLAYERBOT_EXTERNALEVENTHELPER_H
#include <map>
#include "Common.h"
class AiObjectContext;
class Player;
class WorldPacket;
class ExternalEventHelper
{
public:
ExternalEventHelper(AiObjectContext* aiObjectContext) : aiObjectContext(aiObjectContext) {}
bool ParseChatCommand(std::string const command, Player* owner = nullptr);
void HandlePacket(std::map<uint16, std::string>& handlers, WorldPacket const& packet, Player* owner = nullptr);
bool HandleCommand(std::string const name, std::string const param, Player* owner = nullptr);
private:
AiObjectContext* aiObjectContext;
};
#endif

197
src/helper/FleeManager.cpp Normal file
View File

@@ -0,0 +1,197 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "FleeManager.h"
#include "Playerbots.h"
#include "ServerFacade.h"
FleeManager::FleeManager(Player* bot, float maxAllowedDistance, float followAngle, bool forceMaxDistance,
WorldPosition startPosition)
: bot(bot),
maxAllowedDistance(maxAllowedDistance),
followAngle(followAngle),
forceMaxDistance(forceMaxDistance),
startPosition(startPosition ? startPosition : WorldPosition(bot))
{
}
void FleeManager::calculateDistanceToCreatures(FleePoint* point)
{
point->minDistance = -1.0f;
point->sumDistance = 0.0f;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
float d = sServerFacade->GetDistance2d(unit, point->x, point->y);
point->sumDistance += d;
if (point->minDistance < 0 || point->minDistance > d)
point->minDistance = d;
}
}
bool intersectsOri(float angle, std::vector<float>& angles, float angleIncrement)
{
for (std::vector<float>::iterator i = angles.begin(); i != angles.end(); ++i)
{
float ori = *i;
if (abs(angle - ori) < angleIncrement)
return true;
}
return false;
}
void FleeManager::calculatePossibleDestinations(std::vector<FleePoint*>& points)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
Unit* target = *botAI->GetAiObjectContext()->GetValue<Unit*>("current target");
float botPosX = startPosition.getX();
float botPosY = startPosition.getY();
float botPosZ = startPosition.getZ();
FleePoint start(botAI, botPosX, botPosY, botPosZ);
calculateDistanceToCreatures(&start);
std::vector<float> enemyOri;
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
float ori = bot->GetAngle(unit);
enemyOri.push_back(ori);
}
float distIncrement = std::max(sPlayerbotAIConfig->followDistance,
(maxAllowedDistance - sPlayerbotAIConfig->tooCloseDistance) / 10.0f);
for (float dist = maxAllowedDistance; dist >= sPlayerbotAIConfig->tooCloseDistance; dist -= distIncrement)
{
float angleIncrement = std::max(M_PI / 20, M_PI / 4 / (1.0 + dist - sPlayerbotAIConfig->tooCloseDistance));
for (float add = 0.0f; add < M_PI / 4 + angleIncrement; add += angleIncrement)
{
for (float angle = add; angle < add + 2 * static_cast<float>(M_PI) + angleIncrement;
angle += static_cast<float>(M_PI) / 4)
{
if (intersectsOri(angle, enemyOri, angleIncrement))
continue;
float x = botPosX + cos(angle) * maxAllowedDistance, y = botPosY + sin(angle) * maxAllowedDistance,
z = botPosZ + CONTACT_DISTANCE;
if (forceMaxDistance &&
sServerFacade->IsDistanceLessThan(sServerFacade->GetDistance2d(bot, x, y),
maxAllowedDistance - sPlayerbotAIConfig->tooCloseDistance))
continue;
bot->UpdateAllowedPositionZ(x, y, z);
Map* map = startPosition.getMap();
if (map && map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
continue;
if (!bot->IsWithinLOS(x, y, z) || (target && !target->IsWithinLOS(x, y, z)))
continue;
FleePoint* point = new FleePoint(botAI, x, y, z);
calculateDistanceToCreatures(point);
if (sServerFacade->IsDistanceGreaterOrEqualThan(point->minDistance - start.minDistance,
sPlayerbotAIConfig->followDistance))
points.push_back(point);
else
delete point;
}
}
}
}
void FleeManager::cleanup(std::vector<FleePoint*>& points)
{
for (std::vector<FleePoint*>::iterator i = points.begin(); i != points.end(); i++)
{
delete *i;
}
points.clear();
}
bool FleeManager::isBetterThan(FleePoint* point, FleePoint* other)
{
return point->sumDistance - other->sumDistance > 0;
}
FleePoint* FleeManager::selectOptimalDestination(std::vector<FleePoint*>& points)
{
FleePoint* best = nullptr;
for (std::vector<FleePoint*>::iterator i = points.begin(); i != points.end(); i++)
{
FleePoint* point = *i;
if (!best || isBetterThan(point, best))
best = point;
}
return best;
}
bool FleeManager::CalculateDestination(float* rx, float* ry, float* rz)
{
std::vector<FleePoint*> points;
calculatePossibleDestinations(points);
FleePoint* point = selectOptimalDestination(points);
if (!point)
{
cleanup(points);
return false;
}
*rx = point->x;
*ry = point->y;
*rz = point->z;
cleanup(points);
return true;
}
bool FleeManager::isUseful()
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return false;
}
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los");
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Creature* creature = botAI->GetCreature(*i);
if (!creature)
continue;
if (startPosition.sqDistance(WorldPosition(creature)) <
creature->GetAttackDistance(bot) * creature->GetAttackDistance(bot))
return true;
// float d = sServerFacade->GetDistance2d(unit, bot);
// if (sServerFacade->IsDistanceLessThan(d, sPlayerbotAIConfig->aggroDistance)) return true;
}
return false;
}

59
src/helper/FleeManager.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_FLEEMANAGER_H
#define _PLAYERBOT_FLEEMANAGER_H
#include <vector>
#include "Common.h"
#include "TravelMgr.h"
class Player;
class PlayerbotAI;
class FleePoint
{
public:
FleePoint(PlayerbotAI* botAI, float x, float y, float z)
: x(x), y(y), z(z), sumDistance(0.0f), minDistance(0.0f), botAI(botAI)
{
}
float x;
float y;
float z;
float sumDistance;
float minDistance;
private:
PlayerbotAI* botAI;
};
class FleeManager
{
public:
FleeManager(Player* bot, float maxAllowedDistance, float followAngle, bool forceMaxDistance = false,
WorldPosition startPosition = WorldPosition());
bool CalculateDestination(float* rx, float* ry, float* rz);
bool isUseful();
private:
void calculatePossibleDestinations(std::vector<FleePoint*>& points);
void calculateDistanceToCreatures(FleePoint* point);
void cleanup(std::vector<FleePoint*>& points);
FleePoint* selectOptimalDestination(std::vector<FleePoint*>& points);
bool isBetterThan(FleePoint* point, FleePoint* other);
Player* bot;
float maxAllowedDistance;
[[maybe_unused]] float followAngle; // unused - whipowill
bool forceMaxDistance;
WorldPosition startPosition;
};
#endif

1243
src/helper/GuildTaskMgr.cpp Normal file

File diff suppressed because it is too large Load Diff

62
src/helper/GuildTaskMgr.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_GUILDTASKMGR_H
#define _PLAYERBOT_GUILDTASKMGR_H
#include <map>
#include "Common.h"
#include "Transaction.h"
class ChatHandler;
class Player;
class Unit;
class GuildTaskMgr
{
public:
GuildTaskMgr(){};
virtual ~GuildTaskMgr(){};
static GuildTaskMgr* instance()
{
static GuildTaskMgr instance;
return &instance;
}
void Update(Player* owner, Player* guildMaster);
static bool HandleConsoleCommand(ChatHandler* handler, char const* args);
bool IsGuildTaskItem(uint32 itemId, uint32 guildId);
bool CheckItemTask(uint32 itemId, uint32 obtained, Player* owner, Player* bot, bool byMail = false);
void CheckKillTask(Player* owner, Unit* victim);
void CheckKillTaskInternal(Player* owner, Unit* victim);
bool CheckTaskTransfer(std::string const text, Player* owner, Player* bot);
private:
std::map<uint32, uint32> GetTaskValues(uint32 owner, std::string const type, uint32* validIn = nullptr);
uint32 GetTaskValue(uint32 owner, uint32 guildId, std::string const type, uint32* validIn = nullptr);
uint32 SetTaskValue(uint32 owner, uint32 guildId, std::string const type, uint32 value, uint32 validIn);
uint32 CreateTask(Player* owner, uint32 guildId);
bool SendAdvertisement(CharacterDatabaseTransaction& trans, uint32 owner, uint32 guildId);
bool SendItemAdvertisement(CharacterDatabaseTransaction& trans, uint32 itemId, uint32 owner, uint32 guildId,
uint32 validIn);
bool SendKillAdvertisement(CharacterDatabaseTransaction& trans, uint32 creatureId, uint32 owner, uint32 guildId,
uint32 validIn);
bool SendThanks(CharacterDatabaseTransaction& trans, uint32 owner, uint32 guildId, uint32 payment);
bool Reward(CharacterDatabaseTransaction& trans, uint32 owner, uint32 guildId);
bool CreateItemTask(Player* owner, uint32 guildId);
bool CreateKillTask(Player* owner, uint32 guildId);
uint32 GetMaxItemTaskCount(uint32 itemId);
void CleanupAdverts();
void RemoveDuplicatedAdverts();
void DeleteMail(std::vector<uint32> buffer);
void SendCompletionMessage(Player* player, std::string const verb);
};
#define sGuildTaskMgr GuildTaskMgr::instance()
#endif

50
src/helper/Helpers.cpp Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "Helpers.h"
char* strstri(char const* haystack, char const* needle)
{
if (!*needle)
{
return (char*)haystack;
}
for (; *haystack; ++haystack)
{
if (tolower(*haystack) == tolower(*needle))
{
char const *h = haystack, *n = needle;
for (; *h && *n; ++h, ++n)
{
if (tolower(*h) != tolower(*n))
{
break;
}
}
if (!*n)
{
return (char*)haystack;
}
}
}
return 0;
}
std::string& ltrim(std::string& s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c) { return !std::isspace(c); }));
return s;
}
std::string& rtrim(std::string& s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) { return !std::isspace(c); }).base(), s.end());
return s;
}
std::string& trim(std::string& s) { return ltrim(rtrim(s)); }

55
src/helper/Helpers.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_HELPERS_H
#define _PLAYERBOT_HELPERS_H
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <cctype>
#include <functional>
#include <locale>
#include <map>
#include <sstream>
#include <vector>
#include "Common.h"
void split(std::vector<std::string>& dest, std::string const str, char const* delim)
{
char* pTempStr = strdup(str.c_str());
char* pWord = strtok(pTempStr, delim);
while (pWord != nullptr)
{
dest.push_back(pWord);
pWord = strtok(nullptr, delim);
}
free(pTempStr);
}
std::vector<std::string>& split(std::string const s, char delim, std::vector<std::string>& elems)
{
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim))
{
elems.push_back(item);
}
return elems;
}
std::vector<std::string> split(std::string const s, char delim)
{
std::vector<std::string> elems;
return split(s, delim, elems);
}
#endif

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "ItemVisitors.h"
#include "Playerbots.h"
bool FindUsableItemVisitor::Visit(Item* item)
{
if (bot->CanUseItem(item->GetTemplate()) == EQUIP_ERR_OK)
return FindItemVisitor::Visit(item);
return true;
}
bool FindPotionVisitor::Accept(ItemTemplate const* proto)
{
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->SubClass == ITEM_SUBCLASS_POTION || proto->SubClass == ITEM_SUBCLASS_FLASK))
{
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId);
if (!spellInfo)
return false;
for (uint8 i = 0; i < 3; i++)
{
if (spellInfo->Effects[i].Effect == effectId)
return true;
}
}
}
return false;
}
bool FindMountVisitor::Accept(ItemTemplate const* proto)
{
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId);
if (!spellInfo)
return false;
for (uint8 i = 0; i < 3; i++)
{
if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOUNTED)
return true;
}
}
return false;
}
bool FindPetVisitor::Accept(ItemTemplate const* proto)
{
if (proto->Class == ITEM_CLASS_MISC)
{
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId);
if (!spellInfo)
return false;
for (uint8 i = 0; i < 3; i++)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SUMMON_PET)
return true;
}
}
}
return false;
}
FindItemUsageVisitor::FindItemUsageVisitor(Player* bot, ItemUsage usage) : FindUsableItemVisitor(bot), usage(usage)
{
context = GET_PLAYERBOT_AI(bot)->GetAiObjectContext();
};
bool FindItemUsageVisitor::Accept(ItemTemplate const* proto)
{
if (AI_VALUE2(ItemUsage, "item usage", proto->ItemId) == usage)
return true;
return false;
}
bool FindUsableNamedItemVisitor::Accept(ItemTemplate const* proto)
{
return proto && !proto->Name1.empty() && strstri(proto->Name1.c_str(), name.c_str());
}

418
src/helper/ItemVisitors.h Normal file
View File

@@ -0,0 +1,418 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ITEMVISITORS_H
#define _PLAYERBOT_ITEMVISITORS_H
#include "ChatHelper.h"
#include "Common.h"
#include "Item.h"
#include "ItemUsageValue.h"
class AiObjectContext;
class Player;
char* strstri(char const* str1, char const* str2);
enum IterateItemsMask : uint32
{
ITERATE_ITEMS_IN_BAGS = 1,
ITERATE_ITEMS_IN_EQUIP = 2,
ITERATE_ITEMS_IN_BANK = 4,
ITERATE_ALL_ITEMS = 255
};
class IterateItemsVisitor
{
public:
IterateItemsVisitor() {}
virtual bool Visit(Item* item) = 0;
};
class FindItemVisitor : public IterateItemsVisitor
{
public:
FindItemVisitor() : IterateItemsVisitor(), result() {}
bool Visit(Item* item) override
{
if (!Accept(item->GetTemplate()))
return true;
result.push_back(item);
return true;
}
std::vector<Item*>& GetResult() { return result; }
protected:
virtual bool Accept(ItemTemplate const* proto) = 0;
private:
std::vector<Item*> result;
};
class FindUsableItemVisitor : public FindItemVisitor
{
public:
FindUsableItemVisitor(Player* bot) : FindItemVisitor(), bot(bot) {}
bool Visit(Item* item) override;
private:
Player* bot;
};
class FindItemsByQualityVisitor : public IterateItemsVisitor
{
public:
FindItemsByQualityVisitor(uint32 quality, uint32 count) : IterateItemsVisitor(), quality(quality), count(count) {}
bool Visit(Item* item) override
{
if (item->GetTemplate()->Quality != quality)
return true;
if (result.size() >= (size_t)count)
return false;
result.push_back(item);
return true;
}
std::vector<Item*>& GetResult() { return result; }
private:
uint32 quality;
uint32 count;
std::vector<Item*> result;
};
class FindItemsToTradeByQualityVisitor : public FindItemsByQualityVisitor
{
public:
FindItemsToTradeByQualityVisitor(uint32 quality, uint32 count) : FindItemsByQualityVisitor(quality, count) {}
bool Visit(Item* item) override
{
if (item->IsSoulBound())
return true;
return FindItemsByQualityVisitor::Visit(item);
}
};
class FindItemsToTradeByClassVisitor : public IterateItemsVisitor
{
public:
FindItemsToTradeByClassVisitor(uint32 itemClass, uint32 itemSubClass, uint32 count)
: IterateItemsVisitor(), itemClass(itemClass), itemSubClass(itemSubClass), count(count)
{
} // reorder args - whipowill
bool Visit(Item* item) override
{
if (item->IsSoulBound())
return true;
if (item->GetTemplate()->Class != itemClass || item->GetTemplate()->SubClass != itemSubClass)
return true;
if (result.size() >= (size_t)count)
return false;
result.push_back(item);
return true;
}
std::vector<Item*>& GetResult() { return result; }
private:
uint32 itemClass;
uint32 itemSubClass;
uint32 count;
std::vector<Item*> result;
};
class QueryItemCountVisitor : public IterateItemsVisitor
{
public:
QueryItemCountVisitor(uint32 itemId) : count(0), itemId(itemId) {}
bool Visit(Item* item) override
{
if (item->GetTemplate()->ItemId == itemId)
count += item->GetCount();
return true;
}
uint32 GetCount() { return count; }
protected:
uint32 count;
uint32 itemId;
};
class QueryNamedItemCountVisitor : public QueryItemCountVisitor
{
public:
QueryNamedItemCountVisitor(std::string const name) : QueryItemCountVisitor(0), name(name) {}
bool Visit(Item* item) override
{
ItemTemplate const* proto = item->GetTemplate();
if (proto && proto->Name1.c_str() && strstri(proto->Name1.c_str(), name.c_str()))
count += item->GetCount();
return true;
}
private:
std::string const name;
};
class FindNamedItemVisitor : public FindItemVisitor
{
public:
FindNamedItemVisitor([[maybe_unused]] Player* bot, std::string const name) : FindItemVisitor(), name(name) {}
bool Accept(ItemTemplate const* proto) override
{
return proto && proto->Name1.c_str() && strstri(proto->Name1.c_str(), name.c_str());
}
private:
std::string const name;
};
class FindItemByIdVisitor : public FindItemVisitor
{
public:
FindItemByIdVisitor(uint32 id) : FindItemVisitor(), id(id) {}
bool Accept(ItemTemplate const* proto) override { return proto->ItemId == id; }
private:
uint32 id;
};
class FindItemByIdsVisitor : public FindItemVisitor
{
public:
FindItemByIdsVisitor(ItemIds ids) : FindItemVisitor(), ids(ids) {}
bool Accept(ItemTemplate const* proto) override { return ids.find(proto->ItemId) != ids.end(); }
private:
ItemIds ids;
};
class ListItemsVisitor : public IterateItemsVisitor
{
public:
ListItemsVisitor() : IterateItemsVisitor() {}
std::map<uint32, uint32> items;
std::map<uint32, bool> soulbound;
bool Visit(Item* item) override
{
uint32 id = item->GetTemplate()->ItemId;
if (items.find(id) == items.end())
items[id] = 0;
items[id] += item->GetCount();
soulbound[id] = item->IsSoulBound();
return true;
}
};
class ItemCountByQuality : public IterateItemsVisitor
{
public:
ItemCountByQuality() : IterateItemsVisitor()
{
for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i)
count[i] = 0;
}
bool Visit(Item* item) override
{
++count[item->GetTemplate()->Quality];
return true;
}
public:
std::map<uint32, uint32> count;
};
class FindPotionVisitor : public FindUsableItemVisitor
{
public:
FindPotionVisitor(Player* bot, uint32 effectId) : FindUsableItemVisitor(bot), effectId(effectId) {}
bool Accept(ItemTemplate const* proto) override;
private:
uint32 effectId;
};
class FindFoodVisitor : public FindUsableItemVisitor
{
public:
FindFoodVisitor(Player* bot, uint32 spellCategory, bool conjured = false)
: FindUsableItemVisitor(bot), spellCategory(spellCategory), conjured(conjured)
{
}
bool Accept(ItemTemplate const* proto) override
{
return proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->SubClass == ITEM_SUBCLASS_CONSUMABLE || proto->SubClass == ITEM_SUBCLASS_FOOD) &&
proto->Spells[0].SpellCategory == spellCategory && (!conjured || proto->IsConjuredConsumable());
}
private:
uint32 spellCategory;
bool conjured;
};
class FindMountVisitor : public FindUsableItemVisitor
{
public:
FindMountVisitor(Player* bot) : FindUsableItemVisitor(bot) {}
bool Accept(ItemTemplate const* proto) override;
private:
uint32 effectId;
};
class FindPetVisitor : public FindUsableItemVisitor
{
public:
FindPetVisitor(Player* bot) : FindUsableItemVisitor(bot) {}
bool Accept(ItemTemplate const* proto) override;
};
class FindAmmoVisitor : public FindUsableItemVisitor
{
public:
FindAmmoVisitor(Player* bot, uint32 weaponType) : FindUsableItemVisitor(bot), weaponType(weaponType) {}
bool Accept(ItemTemplate const* proto) override
{
if (proto->Class == ITEM_CLASS_PROJECTILE)
{
uint32 subClass = 0;
switch (weaponType)
{
case ITEM_SUBCLASS_WEAPON_GUN:
subClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
subClass = ITEM_SUBCLASS_ARROW;
break;
}
if (!subClass)
return false;
if (proto->SubClass == subClass)
return true;
}
return false;
}
private:
uint32 weaponType;
};
class FindQuestItemVisitor : public FindUsableItemVisitor
{
public:
FindQuestItemVisitor(Player* bot) : FindUsableItemVisitor(bot) {}
bool Accept(ItemTemplate const* proto) override
{
if (proto->Class == ITEM_CLASS_QUEST)
{
return true;
}
return false;
}
};
class FindRecipeVisitor : public FindUsableItemVisitor
{
public:
FindRecipeVisitor(Player* bot, SkillType skill = SKILL_NONE) : FindUsableItemVisitor(bot), skill(skill){};
bool Accept(ItemTemplate const* proto) override
{
if (proto->Class == ITEM_CLASS_RECIPE)
{
if (skill == SKILL_NONE)
return true;
switch (proto->SubClass)
{
case ITEM_SUBCLASS_LEATHERWORKING_PATTERN:
return skill == SKILL_LEATHERWORKING;
case ITEM_SUBCLASS_TAILORING_PATTERN:
return skill == SKILL_TAILORING;
case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC:
return skill == SKILL_ENGINEERING;
case ITEM_SUBCLASS_BLACKSMITHING:
return skill == SKILL_BLACKSMITHING;
case ITEM_SUBCLASS_COOKING_RECIPE:
return skill == SKILL_COOKING;
case ITEM_SUBCLASS_ALCHEMY_RECIPE:
return skill == SKILL_ALCHEMY;
case ITEM_SUBCLASS_FIRST_AID_MANUAL:
return skill == SKILL_FIRST_AID;
case ITEM_SUBCLASS_ENCHANTING_FORMULA:
return skill == SKILL_ENCHANTING;
case ITEM_SUBCLASS_FISHING_MANUAL:
return skill == SKILL_FISHING;
}
}
return false;
}
private:
SkillType skill;
};
class FindItemUsageVisitor : public FindUsableItemVisitor
{
public:
FindItemUsageVisitor(Player* bot, ItemUsage usage = ITEM_USAGE_NONE);
bool Accept(ItemTemplate const* proto) override;
private:
AiObjectContext* context;
ItemUsage usage;
};
class FindUsableNamedItemVisitor : public FindUsableItemVisitor
{
public:
FindUsableNamedItemVisitor(Player* bot, std::string name) : FindUsableItemVisitor(bot), name(name) {}
bool Accept(ItemTemplate const* proto) override;
private:
std::string name;
};
#endif

View File

@@ -0,0 +1,412 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "LootObjectStack.h"
#include "LootMgr.h"
#include "Playerbots.h"
#include "Unit.h"
#define MAX_LOOT_OBJECT_COUNT 200
LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {}
LootTarget::LootTarget(LootTarget const& other)
{
guid = other.guid;
asOfTime = other.asOfTime;
}
LootTarget& LootTarget::operator=(LootTarget const& other)
{
if ((void*)this == (void*)&other)
return *this;
guid = other.guid;
asOfTime = other.asOfTime;
return *this;
}
bool LootTarget::operator<(LootTarget const& other) const { return guid < other.guid; }
void LootTargetList::shrink(time_t fromTime)
{
for (std::set<LootTarget>::iterator i = begin(); i != end();)
{
if (i->asOfTime <= fromTime)
erase(i++);
else
++i;
}
}
LootObject::LootObject(Player* bot, ObjectGuid guid) : guid(), skillId(SKILL_NONE), reqSkillValue(0), reqItem(0)
{
Refresh(bot, guid);
}
void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
{
skillId = SKILL_NONE;
reqSkillValue = 0;
reqItem = 0;
guid.Clear();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return;
}
Creature* creature = botAI->GetCreature(lootGUID);
if (creature && creature->getDeathState() == DeathState::Corpse)
{
if (creature->HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE))
guid = lootGUID;
if (creature->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE))
{
skillId = creature->GetCreatureTemplate()->GetRequiredLootSkill();
uint32 targetLevel = creature->GetLevel();
reqSkillValue = targetLevel < 10 ? 1 : targetLevel < 20 ? (targetLevel - 10) * 10 : targetLevel * 5;
if (botAI->HasSkill((SkillType)skillId) && bot->GetSkillValue(skillId) >= reqSkillValue)
guid = lootGUID;
}
return;
}
GameObject* go = botAI->GetGameObject(lootGUID);
if (go && go->isSpawned() && go->GetGoState() == GO_STATE_READY)
{
bool onlyHasQuestItems = true;
bool hasAnyQuestItems = false;
GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry());
for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
{
if (!items || i >= items->size())
break;
uint32 itemId = uint32((*items)[i]);
if (!itemId)
continue;
hasAnyQuestItems = true;
if (IsNeededForQuest(bot, itemId))
{
this->guid = lootGUID;
return;
}
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
}
}
// Retrieve the correct loot table entry
uint32 lootEntry = go->GetGOInfo()->GetLootId();
if (lootEntry == 0)
return;
// Check the main loot template
if (const LootTemplate* lootTemplate = LootTemplates_Gameobject.GetLootFor(lootEntry))
{
Loot loot;
lootTemplate->Process(loot, LootTemplates_Gameobject, 1, bot);
for (const LootItem& item : loot.items)
{
uint32 itemId = item.itemid;
if (!itemId)
continue;
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
break;
}
// If this item references another loot table, process it
if (const LootTemplate* refLootTemplate = LootTemplates_Reference.GetLootFor(itemId))
{
Loot refLoot;
refLootTemplate->Process(refLoot, LootTemplates_Reference, 1, bot);
for (const LootItem& refItem : refLoot.items)
{
uint32 refItemId = refItem.itemid;
if (!refItemId)
continue;
const ItemTemplate* refProto = sObjectMgr->GetItemTemplate(refItemId);
if (!refProto)
continue;
if (refProto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
break;
}
}
}
}
}
// If gameobject has only quest items that bot doesnt need, skip it.
if (hasAnyQuestItems && onlyHasQuestItems)
return;
// Otherwise, loot it.
guid = lootGUID;
uint32 goId = go->GetEntry();
uint32 lockId = go->GetGOInfo()->GetLockId();
LockEntry const* lockInfo = sLockStore.LookupEntry(lockId);
if (!lockInfo)
return;
for (uint8 i = 0; i < 8; ++i)
{
switch (lockInfo->Type[i])
{
case LOCK_KEY_ITEM:
if (lockInfo->Index[i] > 0)
{
reqItem = lockInfo->Index[i];
guid = lootGUID;
}
break;
case LOCK_KEY_SKILL:
if (goId == 13891 || goId == 19535) // Serpentbloom
{
this->guid = lootGUID;
}
else if (SkillByLockType(LockType(lockInfo->Index[i])) > 0)
{
skillId = SkillByLockType(LockType(lockInfo->Index[i]));
reqSkillValue = std::max((uint32)1, lockInfo->Skill[i]);
guid = lootGUID;
}
break;
case LOCK_KEY_NONE:
guid = lootGUID;
break;
}
}
}
}
bool LootObject::IsNeededForQuest(Player* bot, uint32 itemId)
{
for (int qs = 0; qs < MAX_QUEST_LOG_SIZE; ++qs)
{
uint32 questId = bot->GetQuestSlotQuestId(qs);
if (questId == 0)
continue;
QuestStatusData& qData = bot->getQuestStatusMap()[questId];
if (qData.Status != QUEST_STATUS_INCOMPLETE)
continue;
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
if (!qInfo)
continue;
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; ++i)
{
if (!qInfo->RequiredItemCount[i] || (qInfo->RequiredItemCount[i] - qData.ItemCount[i]) <= 0)
continue;
if (qInfo->RequiredItemId[i] != itemId)
continue;
return true;
}
}
return false;
}
WorldObject* LootObject::GetWorldObject(Player* bot)
{
Refresh(bot, guid);
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return nullptr;
}
Creature* creature = botAI->GetCreature(guid);
if (creature && creature->getDeathState() == DeathState::Corpse && creature->IsInWorld())
return creature;
GameObject* go = botAI->GetGameObject(guid);
if (go && go->isSpawned() && go->IsInWorld())
return go;
return nullptr;
}
LootObject::LootObject(LootObject const& other)
{
guid = other.guid;
skillId = other.skillId;
reqSkillValue = other.reqSkillValue;
reqItem = other.reqItem;
}
bool LootObject::IsLootPossible(Player* bot)
{
if (IsEmpty() || !bot)
return false;
WorldObject* worldObj = GetWorldObject(bot); // Store result to avoid multiple calls
if (!worldObj)
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
return false;
}
if (reqItem && !bot->HasItemCount(reqItem, 1))
return false;
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f)
return false;
Creature* creature = botAI->GetCreature(guid);
if (creature && creature->getDeathState() == DeathState::Corpse)
{
if (!bot->isAllowedToLoot(creature) && skillId != SKILL_SKINNING)
return false;
}
if (skillId == SKILL_NONE)
return true;
if (skillId == SKILL_FISHING)
return false;
if (!botAI->HasSkill((SkillType)skillId))
return false;
if (!reqSkillValue)
return true;
uint32 skillValue = uint32(bot->GetSkillValue(skillId));
if (reqSkillValue > skillValue)
return false;
if (skillId == SKILL_MINING &&
!bot->HasItemCount(756, 1) &&
!bot->HasItemCount(778, 1) &&
!bot->HasItemCount(1819, 1) &&
!bot->HasItemCount(1893, 1) &&
!bot->HasItemCount(1959, 1) &&
!bot->HasItemCount(2901, 1) &&
!bot->HasItemCount(9465, 1) &&
!bot->HasItemCount(20723, 1) &&
!bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40892, 1) &&
!bot->HasItemCount(40893, 1))
{
return false; // Bot is missing a mining pick
}
if (skillId == SKILL_SKINNING &&
!bot->HasItemCount(7005, 1) &&
!bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40893, 1) &&
!bot->HasItemCount(12709, 1) &&
!bot->HasItemCount(19901, 1))
{
return false; // Bot is missing a skinning knife
}
return true;
}
bool LootObjectStack::Add(ObjectGuid guid)
{
if (availableLoot.size() >= MAX_LOOT_OBJECT_COUNT)
{
availableLoot.shrink(time(nullptr) - 30);
}
if (availableLoot.size() >= MAX_LOOT_OBJECT_COUNT)
{
availableLoot.clear();
}
if (!availableLoot.insert(guid).second)
return false;
return true;
}
void LootObjectStack::Remove(ObjectGuid guid)
{
LootTargetList::iterator i = availableLoot.find(guid);
if (i != availableLoot.end())
availableLoot.erase(i);
}
void LootObjectStack::Clear() { availableLoot.clear(); }
bool LootObjectStack::CanLoot(float maxDistance)
{
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return !ordered.empty();
}
LootObject LootObjectStack::GetLoot(float maxDistance)
{
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return ordered.empty() ? LootObject() : *ordered.begin();
}
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
{
availableLoot.shrink(time(nullptr) - 30);
std::map<float, LootObject> sortedMap;
LootTargetList safeCopy(availableLoot);
for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++)
{
ObjectGuid guid = i->guid;
LootObject lootObject(bot, guid);
if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid
continue;
WorldObject* worldObj = lootObject.GetWorldObject(bot);
if (!worldObj) // Prevent null pointer dereference
{
continue;
}
float distance = bot->GetDistance(worldObj);
if (!maxDistance || distance <= maxDistance)
sortedMap[distance] = lootObject;
}
std::vector<LootObject> result;
for (auto& [_, lootObject] : sortedMap)
result.push_back(lootObject);
return result;
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LOOTOBJECTSTACK_H
#define _PLAYERBOT_LOOTOBJECTSTACK_H
#include "ObjectGuid.h"
class AiObjectContext;
class Player;
class WorldObject;
struct ItemTemplate;
class LootStrategy
{
public:
LootStrategy() {}
virtual ~LootStrategy(){};
virtual bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) = 0;
virtual std::string const GetName() = 0;
};
class LootObject
{
public:
LootObject() : skillId(0), reqSkillValue(0), reqItem(0) {}
LootObject(Player* bot, ObjectGuid guid);
LootObject(LootObject const& other);
bool IsEmpty() { return !guid; }
bool IsLootPossible(Player* bot);
void Refresh(Player* bot, ObjectGuid guid);
WorldObject* GetWorldObject(Player* bot);
ObjectGuid guid;
uint32 skillId;
uint32 reqSkillValue;
uint32 reqItem;
private:
static bool IsNeededForQuest(Player* bot, uint32 itemId);
};
class LootTarget
{
public:
LootTarget(ObjectGuid guid);
LootTarget(LootTarget const& other);
public:
LootTarget& operator=(LootTarget const& other);
bool operator<(LootTarget const& other) const;
public:
ObjectGuid guid;
time_t asOfTime;
};
class LootTargetList : public std::set<LootTarget>
{
public:
void shrink(time_t fromTime);
};
class LootObjectStack
{
public:
LootObjectStack(Player* bot) : bot(bot) {}
bool Add(ObjectGuid guid);
void Remove(ObjectGuid guid);
void Clear();
bool CanLoot(float maxDistance);
LootObject GetLoot(float maxDistance = 0);
private:
std::vector<LootObject> OrderByDistance(float maxDistance = 0);
Player* bot;
LootTargetList availableLoot;
};
#endif

View File

@@ -0,0 +1,303 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "PerformanceMonitor.h"
#include "Playerbots.h"
PerformanceMonitorOperation* PerformanceMonitor::start(PerformanceMetric metric, std::string const name,
PerformanceStack* stack)
{
if (!sPlayerbotAIConfig->perfMonEnabled)
return nullptr;
std::string stackName = name;
if (stack)
{
if (!stack->empty())
{
std::ostringstream out;
out << stackName << " [";
for (std::vector<std::string>::reverse_iterator i = stack->rbegin(); i != stack->rend(); ++i)
out << *i << (std::next(i) == stack->rend() ? "" : "|");
out << "]";
stackName = out.str().c_str();
}
stack->push_back(name);
}
std::lock_guard<std::mutex> guard(lock);
PerformanceData* pd = data[metric][stackName];
if (!pd)
{
pd = new PerformanceData();
pd->minTime = 0;
pd->maxTime = 0;
pd->totalTime = 0;
pd->count = 0;
data[metric][stackName] = pd;
}
return new PerformanceMonitorOperation(pd, name, stack);
}
void PerformanceMonitor::PrintStats(bool perTick, bool fullStack)
{
if (data.empty())
return;
if (!perTick)
{
float updateAITotalTime = 0;
for (auto& map : data[PERF_MON_TOTAL])
if (map.first.find("PlayerbotAI::UpdateAIInternal") != std::string::npos)
updateAITotalTime += map.second->totalTime;
LOG_INFO(
"playerbots",
"--------------------------------------[TOTAL BOT]------------------------------------------------------");
LOG_INFO("playerbots",
"percentage time | min .. max ( avg of count) - type : name");
LOG_INFO(
"playerbots",
"-------------------------------------------------------------------------------------------------------");
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin();
i != data.end(); ++i)
{
std::map<std::string, PerformanceData*> pdMap = i->second;
std::string key;
switch (i->first)
{
case PERF_MON_TRIGGER:
key = "Trigger";
break;
case PERF_MON_VALUE:
key = "Value";
break;
case PERF_MON_ACTION:
key = "Action";
break;
case PERF_MON_RNDBOT:
key = "RndBot";
break;
case PERF_MON_TOTAL:
key = "Total";
break;
default:
key = "?";
break;
}
std::vector<std::string> names;
for (std::map<std::string, PerformanceData*>::iterator j = pdMap.begin(); j != pdMap.end(); ++j)
{
if (key == "Total" && j->first.find("PlayerbotAI::UpdateAIInternal") == std::string::npos)
continue;
names.push_back(j->first);
}
std::sort(names.begin(), names.end(),
[pdMap](std::string const i, std::string const j)
{ return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime; });
uint64 typeTotalTime = 0;
uint64 typeMinTime = 0xffffffffu;
uint64 typeMaxTime = 0;
uint32 typeCount = 0;
for (auto& name : names)
{
PerformanceData* pd = pdMap[name];
typeTotalTime += pd->totalTime;
typeCount += pd->count;
if (typeMinTime > pd->minTime)
typeMinTime = pd->minTime;
if (typeMaxTime < pd->maxTime)
typeMaxTime = pd->maxTime;
float perc = (float)pd->totalTime / updateAITotalTime * 100.0f;
float time = (float)pd->totalTime / 1000000.0f;
float minTime = (float)pd->minTime / 1000.0f;
float maxTime = (float)pd->maxTime / 1000.0f;
float avg = (float)pd->totalTime / (float)pd->count / 1000.0f;
std::string disName = name;
if (!fullStack && disName.find("|") != std::string::npos)
disName = disName.substr(0, disName.find("|")) + "]";
if (perc >= 0.1f || avg >= 0.25f || pd->maxTime > 1000)
{
LOG_INFO("playerbots",
"{:7.3f}% {:10.3f}s | {:7.1f} .. {:7.1f} ({:10.3f} of {:10d}) - {:6} : {}", perc, time,
minTime, maxTime, avg, pd->count, key.c_str(), disName.c_str());
}
}
float tPerc = (float)typeTotalTime / (float)updateAITotalTime * 100.0f;
float tTime = (float)typeTotalTime / 1000000.0f;
float tMinTime = (float)typeMinTime / 1000.0f;
float tMaxTime = (float)typeMaxTime / 1000.0f;
float tAvg = (float)typeTotalTime / (float)typeCount / 1000.0f;
LOG_INFO("playerbots", "{:7.3f}% {:10.3f}s | {:7.1f} .. {:7.1f} ({:10.3f} of {:10d}) - {:6} : {}", tPerc,
tTime, tMinTime, tMaxTime, tAvg, typeCount, key.c_str(), "Total");
LOG_INFO("playerbots", " ");
}
}
else
{
float fullTickCount = data[PERF_MON_TOTAL]["PlayerbotAIBase::FullTick"]->count;
float fullTickTotalTime = data[PERF_MON_TOTAL]["PlayerbotAIBase::FullTick"]->totalTime;
LOG_INFO(
"playerbots",
"---------------------------------------[PER TICK]------------------------------------------------------");
LOG_INFO("playerbots",
"percentage time | min .. max ( avg of count) - type : name");
LOG_INFO(
"playerbots",
"-------------------------------------------------------------------------------------------------------");
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin();
i != data.end(); ++i)
{
std::map<std::string, PerformanceData*> pdMap = i->second;
std::string key;
switch (i->first)
{
case PERF_MON_TRIGGER:
key = "Trigger";
break;
case PERF_MON_VALUE:
key = "Value";
break;
case PERF_MON_ACTION:
key = "Action";
break;
case PERF_MON_RNDBOT:
key = "RndBot";
break;
case PERF_MON_TOTAL:
key = "Total";
break;
default:
key = "?";
}
std::vector<std::string> names;
for (std::map<std::string, PerformanceData*>::iterator j = pdMap.begin(); j != pdMap.end(); ++j)
{
names.push_back(j->first);
}
std::sort(names.begin(), names.end(),
[pdMap](std::string const i, std::string const j)
{ return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime; });
uint64 typeTotalTime = 0;
uint64 typeMinTime = 0xffffffffu;
uint64 typeMaxTime = 0;
uint32 typeCount = 0;
for (auto& name : names)
{
PerformanceData* pd = pdMap[name];
typeTotalTime += pd->totalTime;
typeCount += pd->count;
if (typeMinTime > pd->minTime)
typeMinTime = pd->minTime;
if (typeMaxTime < pd->maxTime)
typeMaxTime = pd->maxTime;
float perc = (float)pd->totalTime / fullTickTotalTime * 100.0f;
float time = (float)pd->totalTime / fullTickCount / 1000.0f;
float minTime = (float)pd->minTime / 1000.0f;
float maxTime = (float)pd->maxTime / 1000.0f;
float avg = (float)pd->totalTime / (float)pd->count / 1000.0f;
float amount = (float)pd->count / fullTickCount;
std::string disName = name;
if (!fullStack && disName.find("|") != std::string::npos)
disName = disName.substr(0, disName.find("|")) + "]";
if (perc >= 0.1f || avg >= 0.25f || pd->maxTime > 1000)
{
LOG_INFO("playerbots",
"{:7.3f}% {:9.3f}ms | {:7.1f} .. {:7.1f} ({:10.3f} of {:10.2f}) - {:6} : {}", perc,
time, minTime, maxTime, avg, amount, key.c_str(), disName.c_str());
}
}
if (i->first != PERF_MON_TOTAL)
{
float tPerc = (float)typeTotalTime / (float)fullTickTotalTime * 100.0f;
float tTime = (float)typeTotalTime / fullTickCount / 1000.0f;
float tMinTime = (float)typeMinTime / 1000.0f;
float tMaxTime = (float)typeMaxTime / 1000.0f;
float tAvg = (float)typeTotalTime / (float)typeCount / 1000.0f;
float tAmount = (float)typeCount / fullTickCount;
LOG_INFO("playerbots", "{:7.3f}% {:9.3f}ms | {:7.1f} .. {:7.1f} ({:10.3f} of {:10.2f}) - {:6} : {}",
tPerc, tTime, tMinTime, tMaxTime, tAvg, tAmount, key.c_str(), "Total");
}
LOG_INFO("playerbots", " ");
}
}
}
void PerformanceMonitor::Reset()
{
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin();
i != data.end(); ++i)
{
std::map<std::string, PerformanceData*> pdMap = i->second;
for (std::map<std::string, PerformanceData*>::iterator j = pdMap.begin(); j != pdMap.end(); ++j)
{
PerformanceData* pd = j->second;
std::lock_guard<std::mutex> guard(pd->lock);
pd->minTime = 0;
pd->maxTime = 0;
pd->totalTime = 0;
pd->count = 0;
}
}
}
PerformanceMonitorOperation::PerformanceMonitorOperation(PerformanceData* data, std::string const name,
PerformanceStack* stack)
: data(data), name(name), stack(stack)
{
started = (std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now()))
.time_since_epoch();
}
void PerformanceMonitorOperation::finish()
{
std::chrono::microseconds finished =
(std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now()))
.time_since_epoch();
uint64 elapsed = (finished - started).count();
std::lock_guard<std::mutex> guard(data->lock);
if (elapsed > 0)
{
if (!data->minTime || data->minTime > elapsed)
data->minTime = elapsed;
if (!data->maxTime || data->maxTime < elapsed)
data->maxTime = elapsed;
data->totalTime += elapsed;
}
++data->count;
if (stack)
{
stack->erase(std::remove(stack->begin(), stack->end(), name), stack->end());
}
delete this;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PERFORMANCEMONITOR_H
#define _PLAYERBOT_PERFORMANCEMONITOR_H
#include <chrono>
#include <ctime>
#include <map>
#include <mutex>
#include <vector>
#include "Common.h"
typedef std::vector<std::string> PerformanceStack;
struct PerformanceData
{
uint64 minTime;
uint64 maxTime;
uint64 totalTime;
uint32 count;
std::mutex lock;
};
enum PerformanceMetric
{
PERF_MON_TRIGGER,
PERF_MON_VALUE,
PERF_MON_ACTION,
PERF_MON_RNDBOT,
PERF_MON_TOTAL
};
class PerformanceMonitorOperation
{
public:
PerformanceMonitorOperation(PerformanceData* data, std::string const name, PerformanceStack* stack);
void finish();
private:
PerformanceData* data;
std::string const name;
PerformanceStack* stack;
std::chrono::microseconds started;
};
class PerformanceMonitor
{
public:
PerformanceMonitor(){};
virtual ~PerformanceMonitor(){};
static PerformanceMonitor* instance()
{
static PerformanceMonitor instance;
return &instance;
}
public:
PerformanceMonitorOperation* start(PerformanceMetric metric, std::string const name,
PerformanceStack* stack = nullptr);
void PrintStats(bool perTick = false, bool fullStack = false);
void Reset();
private:
std::map<PerformanceMetric, std::map<std::string, PerformanceData*> > data;
std::mutex lock;
};
#define sPerformanceMonitor PerformanceMonitor::instance()
#endif

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "PlaceholderHelper.h"
#include "AiFactory.h"
#include "PlayerbotTextMgr.h"
#include "Playerbots.h"
#include "Util.h"
void PlaceholderHelper::MapDungeon(PlaceholderMap& placeholders, DungeonSuggestion const* dungeonSuggestion,
Player* bot)
{
std::ostringstream out;
Insertion insertion = {out, dungeonSuggestion};
InsertDungeonName(insertion);
InsertDungeonStrategy(insertion);
InsertDifficulty(insertion, bot);
placeholders["%dungeon"] = out.str();
}
void PlaceholderHelper::MapRole(PlaceholderMap& placeholders, Player* bot)
{
BotRoles const role = AiFactory::GetPlayerRoles(bot);
std::string roleText;
switch (role)
{
case BOT_ROLE_TANK:
roleText = "Tank";
break;
case BOT_ROLE_HEALER:
roleText = "Healer";
break;
case BOT_ROLE_DPS:
roleText = "DPS";
break;
case BOT_ROLE_NONE:
default:
return;
}
bool const hasRole = !roleText.empty();
if (hasRole)
{
placeholders["%role"] = roleText;
}
}
void PlaceholderHelper::InsertDungeonName(Insertion& insertion)
{
std::string name = insertion.dungeonSuggestion->name;
bool const hasAbbrevation = !insertion.dungeonSuggestion->abbrevation.empty();
if (hasAbbrevation)
{
name = insertion.dungeonSuggestion->abbrevation;
}
insertion.out << "|c00b000b0" << name << "|r";
}
void PlaceholderHelper::InsertDungeonStrategy(Insertion& insertion)
{
bool const hasStrategy = !insertion.dungeonSuggestion->strategy.empty();
bool const isRandomlyUsingStrategy = urand(0, 1);
if (hasStrategy && isRandomlyUsingStrategy)
{
std::string strategy = insertion.dungeonSuggestion->strategy;
insertion.out << " " + strategy;
}
}
void PlaceholderHelper::InsertDifficulty(Insertion& insertion, [[maybe_unused]] Player* bot)
{
bool const hasHeroic = insertion.dungeonSuggestion->difficulty == DUNGEON_DIFFICULTY_HEROIC;
std::string difficultyText;
if (hasHeroic)
{
bool const isRandomlyNormal = urand(0, 1);
bool const isRandomlyHeroic = urand(0, 1);
std::vector<std::string> normalAbbrevations = {"Normal", "N"};
std::vector<std::string> heroicAbbrevations = {"Heroic", "HC", "H"};
uint32 const randomAbbrevationIndex = urand(0, 1);
if (isRandomlyNormal)
{
difficultyText = normalAbbrevations[randomAbbrevationIndex];
}
else if (isRandomlyHeroic)
{
difficultyText = heroicAbbrevations[randomAbbrevationIndex];
}
insertion.out << " " << difficultyText;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLACEHOLDERHELPER_H
#define _PLAYERBOT_PLACEHOLDERHELPER_H
#include <map>
#include "Common.h"
#include "Player.h"
#include "PlayerbotDungeonSuggestionMgr.h"
typedef std::map<std::string, std::string> PlaceholderMap;
class PlaceholderHelper
{
public:
static void MapRole(PlaceholderMap& placeholders, Player* bot);
static void MapDungeon(PlaceholderMap& placeholders, DungeonSuggestion const* dungeonSuggestion, Player* bot);
private:
struct Insertion
{
std::ostringstream& out;
DungeonSuggestion const* dungeonSuggestion;
};
static void InsertDungeonName(Insertion& insertion);
static void InsertDungeonStrategy(Insertion& insertion);
static void InsertDifficulty(Insertion& insertion, Player* bot);
};
#endif

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "PlayerbotDungeonSuggestionMgr.h"
#include "Playerbots.h"
std::vector<DungeonSuggestion> const PlayerbotDungeonSuggestionMgr::GetDungeonSuggestions()
{
return m_dungeonSuggestions;
}
void PlayerbotDungeonSuggestionMgr::LoadDungeonSuggestions()
{
LOG_INFO("server.loading", "Loading playerbots dungeon suggestions...");
uint32 oldMSTime = getMSTime();
uint32 count = 0;
auto statement = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_DUNGEON_SUGGESTION);
uint8 const expansion = sWorld->getIntConfig(CONFIG_EXPANSION);
statement->SetData(0, expansion);
PreparedQueryResult result = PlayerbotsDatabase.Query(statement);
if (result)
{
do
{
Field* fields = result->Fetch();
std::string const name = fields[0].Get<std::string>();
uint8 const difficulty = fields[1].Get<uint8>();
uint8 const min_level = fields[2].Get<uint8>();
uint8 const max_level = fields[3].Get<uint8>();
std::string const abbrevation = fields[4].Get<std::string>();
std::string const strategy = fields[5].Get<std::string>();
DungeonSuggestion const row = {
name, static_cast<Difficulty>(difficulty), min_level, max_level, abbrevation, strategy};
m_dungeonSuggestions.push_back(row);
++count;
} while (result->NextRow());
}
LOG_INFO("server.loading", "{} playerbots dungeon suggestions loaded in {} ms", count,
GetMSTimeDiffToNow(oldMSTime));
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLAYERBOTDUNGEONSUGGESTIONMGR_H
#define _PLAYERBOT_PLAYERBOTDUNGEONSUGGESTIONMGR_H
#include <map>
#include <vector>
#include "Common.h"
#include "DBCEnums.h"
struct DungeonSuggestion
{
std::string name;
Difficulty difficulty;
uint8 min_level;
uint8 max_level;
std::string abbrevation;
std::string strategy;
};
class PlayerbotDungeonSuggestionMgr
{
public:
PlayerbotDungeonSuggestionMgr(){};
~PlayerbotDungeonSuggestionMgr(){};
static PlayerbotDungeonSuggestionMgr* instance()
{
static PlayerbotDungeonSuggestionMgr instance;
return &instance;
}
void LoadDungeonSuggestions();
std::vector<DungeonSuggestion> const GetDungeonSuggestions();
private:
std::vector<DungeonSuggestion> m_dungeonSuggestions;
};
#define sPlayerbotDungeonSuggestionMgr PlayerbotDungeonSuggestionMgr::instance()
#endif

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "PlayerbotTextMgr.h"
#include "Playerbots.h"
#include "WorldSessionMgr.h"
void PlayerbotTextMgr::replaceAll(std::string& str, const std::string& from, const std::string& to)
{
if (from.empty())
return;
size_t start_pos = 0;
while ((start_pos = str.find(from, start_pos)) != std::string::npos)
{
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
void PlayerbotTextMgr::LoadBotTexts()
{
LOG_INFO("playerbots", "Loading playerbots texts...");
uint32 count = 0;
if (PreparedQueryResult result =
PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_TEXT)))
{
do
{
std::map<uint32, std::string> text;
Field* fields = result->Fetch();
std::string name = fields[0].Get<std::string>();
text[0] = fields[1].Get<std::string>();
uint8 sayType = fields[2].Get<uint8>();
uint8 replyType = fields[3].Get<uint8>();
for (uint8 i = 1; i < MAX_LOCALES; ++i)
{
text[i] = fields[i + 3].Get<std::string>();
}
botTexts[name].push_back(BotTextEntry(name, text, sayType, replyType));
++count;
} while (result->NextRow());
}
LOG_INFO("playerbots", "{} playerbots texts loaded", count);
}
void PlayerbotTextMgr::LoadBotTextChance()
{
if (botTextChance.empty())
{
QueryResult results = PlayerbotsDatabase.Query("SELECT name, probability FROM ai_playerbot_texts_chance");
if (results)
{
do
{
Field* fields = results->Fetch();
std::string name = fields[0].Get<std::string>();
uint32 probability = fields[1].Get<uint32>();
botTextChance[name] = probability;
} while (results->NextRow());
}
}
}
// general texts
std::string PlayerbotTextMgr::GetBotText(std::string name)
{
if (botTexts.empty())
{
LOG_ERROR("playerbots", "Can't get bot text {}! No bots texts loaded!", name);
return "";
}
if (botTexts[name].empty())
{
LOG_ERROR("playerbots", "Can't get bot text {}! No bots texts for this name!", name);
return "";
}
std::vector<BotTextEntry>& list = botTexts[name];
BotTextEntry textEntry = list[urand(0, list.size() - 1)];
return !textEntry.m_text[GetLocalePriority()].empty() ? textEntry.m_text[GetLocalePriority()] : textEntry.m_text[0];
}
std::string PlayerbotTextMgr::GetBotText(std::string name, std::map<std::string, std::string> placeholders)
{
std::string botText = GetBotText(name);
if (botText.empty())
return "";
for (std::map<std::string, std::string>::iterator i = placeholders.begin(); i != placeholders.end(); ++i)
replaceAll(botText, i->first, i->second);
return botText;
}
// chat replies
std::string PlayerbotTextMgr::GetBotText(ChatReplyType replyType, std::map<std::string, std::string> placeholders)
{
if (botTexts.empty())
{
LOG_ERROR("playerbots", "Can't get bot text reply {}! No bots texts loaded!", replyType);
return "";
}
if (botTexts["reply"].empty())
{
LOG_ERROR("playerbots", "Can't get bot text reply {}! No bots texts replies!", replyType);
return "";
}
std::vector<BotTextEntry>& list = botTexts["reply"];
std::vector<BotTextEntry> proper_list;
for (auto text : list)
{
if (text.m_replyType == replyType)
proper_list.push_back(text);
}
if (proper_list.empty())
return "";
BotTextEntry textEntry = proper_list[urand(0, proper_list.size() - 1)];
std::string botText =
!textEntry.m_text[GetLocalePriority()].empty() ? textEntry.m_text[GetLocalePriority()] : textEntry.m_text[0];
for (auto& placeholder : placeholders)
replaceAll(botText, placeholder.first, placeholder.second);
return botText;
}
std::string PlayerbotTextMgr::GetBotText(ChatReplyType replyType, std::string name)
{
std::map<std::string, std::string> placeholders;
placeholders["%s"] = name;
return GetBotText(replyType, placeholders);
}
// probabilities
bool PlayerbotTextMgr::rollTextChance(std::string name)
{
if (!botTextChance[name])
return true;
return urand(0, 100) < botTextChance[name];
}
bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text)
{
if (!rollTextChance(name))
return false;
text = GetBotText(name);
return !text.empty();
}
bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<std::string, std::string> placeholders)
{
if (!rollTextChance(name))
return false;
text = GetBotText(name, placeholders);
return !text.empty();
}
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
{
if (!locale)
return;
botTextLocalePriority[locale]++;
}
uint32 PlayerbotTextMgr::GetLocalePriority()
{
uint32 topLocale = 0;
// if no real players online, reset top locale
if (!sWorldSessionMgr->GetActiveSessionCount())
{
ResetLocalePriority();
return 0;
}
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
if (botTextLocalePriority[i] > topLocale)
topLocale = i;
}
return topLocale;
}
void PlayerbotTextMgr::ResetLocalePriority()
{
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
botTextLocalePriority[i] = 0;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLAYERBOTTEXTMGR_H
#define _PLAYERBOT_PLAYERBOTTEXTMGR_H
#include <map>
#include <vector>
#include "Common.h"
#define BOT_TEXT1(name) sPlayerbotTextMgr->GetBotText(name)
#define BOT_TEXT2(name, replace) sPlayerbotTextMgr->GetBotText(name, replace)
struct BotTextEntry
{
BotTextEntry(std::string name, std::map<uint32, std::string> text, uint32 say_type, uint32 reply_type)
: m_name(name), m_text(text), m_sayType(say_type), m_replyType(reply_type)
{
}
std::string m_name;
std::map<uint32, std::string> m_text;
uint32 m_sayType;
uint32 m_replyType;
};
struct ChatReplyData
{
ChatReplyData(uint32 guid, uint32 type, std::string chat) : m_type(type), m_guid(guid), m_chat(chat) {}
uint32 m_type, m_guid = 0;
std::string m_chat = "";
};
struct ChatQueuedReply
{
ChatQueuedReply(uint32 type, uint32 guid1, uint32 guid2, std::string msg, std::string chanName, std::string name,
time_t time)
: m_type(type), m_guid1(guid1), m_guid2(guid2), m_msg(msg), m_chanName(chanName), m_name(name), m_time(time)
{
}
uint32 m_type;
uint32 m_guid1;
uint32 m_guid2;
std::string m_msg;
std::string m_chanName;
std::string m_name;
time_t m_time;
};
enum ChatReplyType
{
REPLY_NOT_UNDERSTAND,
REPLY_GRUDGE,
REPLY_VICTIM,
REPLY_ATTACKER,
REPLY_HELLO,
REPLY_NAME,
REPLY_ADMIN_ABUSE
};
class PlayerbotTextMgr
{
public:
PlayerbotTextMgr()
{
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
botTextLocalePriority[i] = 0;
}
};
virtual ~PlayerbotTextMgr(){};
static PlayerbotTextMgr* instance()
{
static PlayerbotTextMgr instance;
return &instance;
}
std::string GetBotText(std::string name, std::map<std::string, std::string> placeholders);
std::string GetBotText(std::string name);
std::string GetBotText(ChatReplyType replyType, std::map<std::string, std::string> placeholders);
std::string GetBotText(ChatReplyType replyType, std::string name);
bool GetBotText(std::string name, std::string& text);
bool GetBotText(std::string name, std::string& text, std::map<std::string, std::string> placeholders);
void LoadBotTexts();
void LoadBotTextChance();
static void replaceAll(std::string& str, const std::string& from, const std::string& to);
bool rollTextChance(std::string text);
uint32 GetLocalePriority();
void AddLocalePriority(uint32 locale);
void ResetLocalePriority();
private:
std::map<std::string, std::vector<BotTextEntry>> botTexts;
std::map<std::string, uint32> botTextChance;
uint32 botTextLocalePriority[MAX_LOCALES];
};
#define sPlayerbotTextMgr PlayerbotTextMgr::instance()
#endif

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "ServerFacade.h"
#include "Playerbots.h"
#include "TargetedMovementGenerator.h"
float ServerFacade::GetDistance2d(Unit* unit, WorldObject* wo)
{
ASSERT_NOTNULL(unit);
ASSERT_NOTNULL(wo);
float dist = unit->GetDistance2d(wo);
return std::round(dist * 10.0f) / 10.0f;
}
float ServerFacade::GetDistance2d(Unit* unit, float x, float y)
{
float dist = unit->GetDistance2d(x, y);
return std::round(dist * 10.0f) / 10.0f;
}
bool ServerFacade::IsDistanceLessThan(float dist1, float dist2)
{
// return dist1 - dist2 < sPlayerbotAIConfig->targetPosRecalcDistance;
return dist1 < dist2;
}
bool ServerFacade::IsDistanceGreaterThan(float dist1, float dist2)
{
// return dist1 - dist2 > sPlayerbotAIConfig->targetPosRecalcDistance;
return dist1 > dist2;
}
bool ServerFacade::IsDistanceGreaterOrEqualThan(float dist1, float dist2) { return !IsDistanceLessThan(dist1, dist2); }
bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return !IsDistanceGreaterThan(dist1, dist2); }
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
{
float angle = bot->GetAngle(wo);
// if (!force && bot->isMoving())
// bot->SetFacingTo(bot->GetAngle(wo));
// else
// {
bot->SetOrientation(angle);
bot->SendMovementFlagUpdate();
// }
}
Unit* ServerFacade::GetChaseTarget(Unit* target)
{
MovementGenerator* movementGen = target->GetMotionMaster()->top();
if (movementGen && movementGen->GetMovementGeneratorType() == CHASE_MOTION_TYPE)
{
if (target->GetTypeId() == TYPEID_PLAYER)
{
return static_cast<ChaseMovementGenerator<Player> const*>(movementGen)->GetTarget();
}
else
{
return static_cast<ChaseMovementGenerator<Creature> const*>(movementGen)->GetTarget();
}
}
return nullptr;
}
void ServerFacade::SendPacket(Player *player, WorldPacket *packet)
{
return player->GetSession()->SendPacket(packet);
}

43
src/helper/ServerFacade.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_SERVERFACADE_H
#define _PLAYERBOT_SERVERFACADE_H
#include "Common.h"
class Player;
class Unit;
class WorldObject;
class WorldPacket;
class ServerFacade
{
public:
ServerFacade(){};
virtual ~ServerFacade(){};
static ServerFacade* instance()
{
static ServerFacade instance;
return &instance;
}
public:
float GetDistance2d(Unit* unit, WorldObject* wo);
float GetDistance2d(Unit* unit, float x, float y);
bool IsDistanceLessThan(float dist1, float dist2);
bool IsDistanceGreaterThan(float dist1, float dist2);
bool IsDistanceGreaterOrEqualThan(float dist1, float dist2);
bool IsDistanceLessOrEqualThan(float dist1, float dist2);
void SetFacingTo(Player* bot, WorldObject* wo, bool force = false);
Unit* GetChaseTarget(Unit* target);
void SendPacket(Player *player, WorldPacket* packet);
};
#define sServerFacade ServerFacade::instance()
#endif

505
src/helper/Talentspec.cpp Normal file
View File

@@ -0,0 +1,505 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "Talentspec.h"
#include "Event.h"
#include "Playerbots.h"
uint32 TalentSpec::TalentListEntry::tabPage() const
{
return talentTabInfo->TalentTabID == 41 ? 1 : talentTabInfo->tabpage;
}
// Checks a talent link on basic validity.
bool TalentSpec::CheckTalentLink(std::string const link, std::ostringstream* out)
{
std::string const validChar = "-";
std::string const validNums = "012345";
uint32 nums = 0;
for (char const& c : link)
{
if (validChar.find(c) == std::string::npos && validNums.find(c) == std::string::npos)
{
*out << "talent link is invalid. Must be in format 0-0-0";
return false;
}
if (validNums.find(c) != std::string::npos)
nums++;
}
if (nums == 0)
{
*out << "talent link is invalid. Needs atleast one number.";
return false;
}
return true;
}
uint32 TalentSpec::LeveltoPoints(uint32 level) const
{
uint32 talentPointsForLevel = level < 10 ? 0 : level - 9;
return uint32(talentPointsForLevel * sWorld->getRate(RATE_TALENT));
}
uint32 TalentSpec::PointstoLevel(uint32 points) const
{
return uint32(ceil(points / sWorld->getRate(RATE_TALENT))) + 9;
}
TalentSpec::TalentSpec(uint32 classMask) { GetTalents(classMask); }
TalentSpec::TalentSpec(TalentSpec* base, std::string const link)
{
talents = base->talents;
ReadTalents(link);
}
TalentSpec::TalentSpec(Player* bot)
{
GetTalents(bot->getClassMask());
ReadTalents(bot);
}
TalentSpec::TalentSpec(Player* bot, std::string const link)
{
GetTalents(bot->getClassMask());
ReadTalents(link);
}
// Check the talentspec for errors.
bool TalentSpec::CheckTalents(uint32 level, std::ostringstream* out)
{
for (auto& entry : talents)
{
if (entry.rank > entry.maxRank)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry.talentInfo->RankID[0]);
*out << "spec is not for this class. " << spellInfo->SpellName[0] << " has " << (entry.rank - entry.maxRank)
<< " points above max rank.";
return false;
}
if (entry.rank > 0 && entry.talentInfo->DependsOn)
{
if (sTalentStore.LookupEntry(entry.talentInfo->DependsOn))
{
bool found = false;
SpellInfo const* spellInfodep = nullptr;
for (auto& dep : talents)
if (dep.talentInfo->TalentID == entry.talentInfo->DependsOn)
{
spellInfodep = sSpellMgr->GetSpellInfo(dep.talentInfo->RankID[0]);
if (dep.rank >= entry.talentInfo->DependsOnRank)
found = true;
}
if (!found)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry.talentInfo->RankID[0]);
*out << "spec is invalid. Talent:" << spellInfo->SpellName[0]
<< " needs: " << spellInfodep->SpellName[0] << " at rank: " << entry.talentInfo->DependsOnRank;
return false;
}
}
}
}
for (uint8 i = 0; i < 3; i++)
{
std::vector<TalentListEntry> talentTree = GetTalentTree(i);
uint32 points = 0;
for (auto& entry : talentTree)
{
if (entry.rank > 0 && entry.talentInfo->Row * 5 > points)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(entry.talentInfo->RankID[0]);
*out << "spec is is invalid. Talent " << spellInfo->SpellName[0] << " is selected with only " << points
<< " in row below it.";
return false;
}
points += entry.rank;
}
}
if (points > LeveltoPoints(level))
{
*out << "spec is for a higher level. (" << PointstoLevel(points) << ")";
return false;
}
return true;
}
// Set the talents for the bots to the current spec.
void TalentSpec::ApplyTalents(Player* bot, std::ostringstream* out)
{
for (auto& entry : talents)
{
if (entry.rank == 0)
continue;
bot->LearnTalent(entry.talentInfo->TalentID, entry.rank - 1);
}
}
// Returns a base talentlist for a class.
void TalentSpec::GetTalents(uint32 classMask)
{
TalentListEntry entry;
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
if (!talentInfo)
continue;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TalentTab);
if (!talentTabInfo)
continue;
if ((classMask & talentTabInfo->ClassMask) == 0)
continue;
entry.entry = i;
entry.rank = 0;
entry.talentInfo = talentInfo;
entry.talentTabInfo = talentTabInfo;
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
{
uint32 spellId = talentInfo->RankID[rank];
if (!spellId)
continue;
entry.maxRank = rank + 1;
}
talents.push_back(entry);
}
SortTalents(talents, SORT_BY_DEFAULT);
}
// Sorts a talent list by page, row, column.
bool sortTalentMap(TalentSpec::TalentListEntry i, TalentSpec::TalentListEntry j, uint32* tabSort)
{
uint32 itab = i.tabPage();
uint32 jtab = j.tabPage();
if (tabSort[itab] < tabSort[jtab])
return true;
if (tabSort[itab] > tabSort[jtab])
return false;
if (i.talentInfo->Row < j.talentInfo->Row)
return true;
if (i.talentInfo->Row > j.talentInfo->Row)
return false;
if (i.talentInfo->Col < j.talentInfo->Col)
return true;
return false;
}
void TalentSpec::SortTalents(uint32 sortBy) { SortTalents(talents, sortBy); }
// Sort the talents.
void TalentSpec::SortTalents(std::vector<TalentListEntry>& talents, uint32 sortBy)
{
switch (sortBy)
{
case SORT_BY_DEFAULT:
{
uint32 tabSort[] = {0, 1, 2};
std::sort(talents.begin(), talents.end(),
[&tabSort](TalentSpec::TalentListEntry i, TalentSpec::TalentListEntry j)
{ return sortTalentMap(i, j, tabSort); });
break;
}
case SORT_BY_POINTS_TREE:
{
uint32 tabSort[] = {GetTalentPoints(talents, 0) * 100 - urand(0, 99),
GetTalentPoints(talents, 1) * 100 - urand(0, 99),
GetTalentPoints(talents, 2) * 100 - urand(0, 99)};
std::sort(talents.begin(), talents.end(),
[&tabSort](TalentSpec::TalentListEntry i, TalentSpec::TalentListEntry j)
{ return sortTalentMap(i, j, tabSort); });
break;
}
}
}
// Set the talent ranks to the current rank of the player.
void TalentSpec::ReadTalents(Player* bot)
{
for (auto& entry : talents)
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
{
uint32 spellId = entry.talentInfo->RankID[rank];
if (!spellId)
continue;
if (bot->HasSpell(spellId))
{
entry.rank = rank + 1;
points += 1;
}
}
}
// Set the talent ranks to the ranks of the link.
void TalentSpec::ReadTalents(std::string const link)
{
//uint32 rank = 0; //not used, line marked for removal.
uint32 pos = 0;
uint32 tab = 0;
std::string chr;
if (link.substr(pos, 1) == "-")
{
pos++;
tab++;
}
if (link.substr(pos, 1) == "-")
{
pos++;
tab++;
}
for (auto& entry : talents)
{
if (entry.tabPage() == tab)
{
chr = link.substr(pos, 1);
if (chr == " " || chr == "#")
break;
entry.rank = stoi(chr);
points += entry.rank;
pos++;
if (pos <= link.size() && link.substr(pos, 1) == "-")
{
pos++;
tab++;
}
if (pos <= link.size() && link.substr(pos, 1) == "-")
{
pos++;
tab++;
}
}
if (pos > link.size() - 1)
break;
}
}
// Returns only a specific tree from a talent list.
std::vector<TalentSpec::TalentListEntry> TalentSpec::GetTalentTree(uint32 tabpage)
{
std::vector<TalentListEntry> retList;
for (auto& entry : talents)
if (entry.tabPage() == tabpage)
retList.push_back(entry);
return std::move(retList);
}
uint32 TalentSpec::GetTalentPoints(int32 tabpage) { return GetTalentPoints(talents, tabpage); };
// Counts the point in a talent list.
uint32 TalentSpec::GetTalentPoints(std::vector<TalentListEntry>& talents, int32 tabpage)
{
if (tabpage == -1)
return points;
uint32 tPoints = 0;
for (auto& entry : talents)
if (entry.tabPage() == tabpage)
tPoints = tPoints + entry.rank;
return tPoints;
}
// Generates a wow-head link from a talent list.
std::string const TalentSpec::GetTalentLink()
{
std::string link = "";
std::string treeLink[3];
uint32 points[3];
uint32 curPoints = 0;
for (uint8 i = 0; i < 3; i++)
{
points[i] = GetTalentPoints(i);
for (auto& entry : GetTalentTree(i))
{
curPoints += entry.rank;
treeLink[i] += std::to_string(entry.rank);
if (curPoints >= points[i])
{
curPoints = 0;
break;
}
}
}
link = treeLink[0];
if (treeLink[1] != "0" || treeLink[2] != "0")
link = link + "-" + treeLink[1];
if (treeLink[2] != "0")
link = link + "-" + treeLink[2];
return std::move(link);
}
uint32 TalentSpec::highestTree()
{
uint32 p1 = GetTalentPoints(0);
uint32 p2 = GetTalentPoints(1);
uint32 p3 = GetTalentPoints(2);
if (p1 > p2 && p1 > p3)
return 0;
if (p2 > p1 && p2 > p3)
return 1;
if (p3 > p1 && p3 > p2)
return 2;
if (p1 > p2 || p1 > p3)
return 0;
if (p2 > p3 || p2 > p1)
return 1;
return 0;
}
std::string const TalentSpec::FormatSpec(Player* bot)
{
// uint8 cls = bot->getClass(); //not used, (used in lined 403), line marked for removal.
std::ostringstream out;
// out << chathelper:: specs[cls][highestTree()] << " (";
uint32 c0 = GetTalentPoints(0);
uint32 c1 = GetTalentPoints(1);
uint32 c2 = GetTalentPoints(2);
out << (c0 ? "|h|cff00ff00" : "") << c0 << "|h|cffffffff/";
out << (c1 ? "|h|cff00ff00" : "") << c1 << "|h|cffffffff/";
out << (c2 ? "|h|cff00ff00" : "") << c2 << "|h|cffffffff";
return out.str();
}
// Removes talentpoints to match the level
void TalentSpec::CropTalents(uint32 level)
{
if (points <= LeveltoPoints(level))
return;
SortTalents(talents, SORT_BY_POINTS_TREE);
uint32 points = 0;
for (auto& entry : talents)
{
if (points + entry.rank > LeveltoPoints(level))
entry.rank = std::max(0u, LeveltoPoints(level) - points);
points += entry.rank;
}
SortTalents(talents, SORT_BY_DEFAULT);
}
// Substracts ranks. Follows the sorting of the newList.
std::vector<TalentSpec::TalentListEntry> TalentSpec::SubTalentList(std::vector<TalentListEntry>& oldList,
std::vector<TalentListEntry>& newList,
uint32 reverse = SUBSTRACT_OLD_NEW)
{
std::vector<TalentSpec::TalentListEntry> deltaList = newList;
for (auto& newentry : deltaList)
for (auto& oldentry : oldList)
if (oldentry.entry == newentry.entry)
{
if (reverse == ABSOLUTE_DIST)
newentry.rank = std::abs(int32(newentry.rank - oldentry.rank));
else if (reverse == ADDED_POINTS || reverse == REMOVED_POINTS)
newentry.rank = std::max(0u, (newentry.rank - oldentry.rank) * (reverse / 2));
else
newentry.rank = (newentry.rank - oldentry.rank) * reverse;
}
return deltaList;
}
bool TalentSpec::isEarlierVersionOf(TalentSpec& newSpec)
{
for (auto& newentry : newSpec.talents)
for (auto& oldentry : talents)
if (oldentry.entry == newentry.entry)
if (oldentry.rank > newentry.rank)
return false;
return true;
}
// Modifies current talents towards new talents up to a maxium of points.
void TalentSpec::ShiftTalents(TalentSpec* currentSpec, uint32 level)
{
//uint32 currentPoints = currentSpec->GetTalentPoints(); //not used, line marked for removal.
if (points >= LeveltoPoints(level)) // We have no more points to spend. Better reset and crop
{
CropTalents(level);
return;
}
SortTalents(SORT_BY_POINTS_TREE); // Apply points first to the largest new tree.
std::vector<TalentSpec::TalentListEntry> deltaList = SubTalentList(currentSpec->talents, talents);
for (auto& entry : deltaList)
{
if (entry.rank < 0) // We have to remove talents. Might as well reset and crop the new list.
{
CropTalents(level);
return;
}
}
// Start from the current spec.
talents = currentSpec->talents;
for (auto& entry : deltaList)
{
if (entry.rank + points > LeveltoPoints(level)) // Running out of points. Only apply what we have left.
entry.rank = std::max(0, std::abs(int32(LeveltoPoints(level) - points)));
for (auto& subentry : talents)
if (entry.entry == subentry.entry)
subentry.rank = subentry.rank + entry.rank;
points = points + entry.rank;
}
}

107
src/helper/Talentspec.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TALENTSPEC_H
#define _PLAYERBOT_TALENTSPEC_H
#include "Action.h"
struct TalentEntry;
struct TalentTabEntry;
#define SORT_BY_DEFAULT 0
#define SORT_BY_POINTS_TREE 1
#define ABSOLUTE_DIST 0
#define SUBSTRACT_OLD_NEW 1
#define SUBSTRACT_NEW_OLD -1
#define ADDED_POINTS 2
#define REMOVED_POINTS -2
// unused currently
class TalentSpec
{
public:
struct TalentListEntry
{
uint32 entry;
uint32 rank;
uint32 maxRank;
TalentEntry const* talentInfo;
TalentTabEntry const* talentTabInfo;
uint32 tabPage() const;
};
TalentSpec(){};
virtual ~TalentSpec() {}
TalentSpec(uint32 classMask);
TalentSpec(TalentSpec* base, std::string const link);
TalentSpec(Player* bot);
TalentSpec(Player* bot, std::string const link);
uint32 points = 0;
std::vector<TalentListEntry> talents;
bool CheckTalentLink(std::string const link, std::ostringstream* out);
virtual bool CheckTalents(uint32 maxPoints, std::ostringstream* out);
void CropTalents(uint32 level);
void ShiftTalents(TalentSpec* oldTalents, uint32 level);
void ApplyTalents(Player* bot, std::ostringstream* out);
uint32 GetTalentPoints(std::vector<TalentListEntry>& talents, int32 tabpage = -1);
uint32 GetTalentPoints(int32 tabpage = -1);
bool isEarlierVersionOf(TalentSpec& newSpec);
std::string const GetTalentLink();
uint32 highestTree();
std::string const FormatSpec(Player* bot);
protected:
uint32 LeveltoPoints(uint32 level) const;
uint32 PointstoLevel(uint32 points) const;
void GetTalents(uint32 classMask);
void SortTalents(std::vector<TalentListEntry>& talents, uint32 sortBy);
void SortTalents(uint32 sortBy);
void ReadTalents(Player* bot);
void ReadTalents(std::string const link);
std::vector<TalentListEntry> GetTalentTree(uint32 tabpage);
std::vector<TalentListEntry> SubTalentList(std::vector<TalentListEntry>& oldList,
std::vector<TalentListEntry>& newList, uint32 reverse);
};
class TalentPath
{
public:
TalentPath(uint32 pathId, std::string const pathName, uint32 pathProbability)
{
id = pathId;
name = pathName;
probability = pathProbability;
};
uint32 id = 0;
std::string name = "";
uint32 probability = 100;
std::vector<TalentSpec> talentSpec;
};
class ClassSpecs
{
public:
ClassSpecs(){};
ClassSpecs(uint32 specsClassMask)
{
classMask = specsClassMask;
baseSpec = TalentSpec(specsClassMask);
}
uint32 classMask = 0;
TalentSpec baseSpec;
std::vector<TalentPath> talentPath;
};
#endif

4298
src/helper/TravelMgr.cpp Normal file

File diff suppressed because it is too large Load Diff

945
src/helper/TravelMgr.h Normal file
View File

@@ -0,0 +1,945 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TRAVELMGR_H
#define _PLAYERBOT_TRAVELMGR_H
#include <boost/functional/hash.hpp>
#include <random>
#include "AiObject.h"
#include "Corpse.h"
#include "CreatureData.h"
#include "GameObject.h"
#include "GridDefines.h"
#include "PlayerbotAIConfig.h"
class GuidPosition;
class ObjectGuid;
class Quest;
class Player;
class PlayerbotAI;
struct QuestStatusData;
namespace G3D
{
class Vector2;
class Vector3;
class Vector4;
} // namespace G3D
// Constructor types for WorldPosition
enum WorldPositionConst
{
WP_RANDOM = 0,
WP_CENTROID = 1,
WP_MEAN_CENTROID = 2,
WP_CLOSEST = 3
};
enum TravelState
{
TRAVEL_STATE_IDLE = 0,
TRAVEL_STATE_TRAVEL_PICK_UP_QUEST = 1,
TRAVEL_STATE_WORK_PICK_UP_QUEST = 2,
TRAVEL_STATE_TRAVEL_DO_QUEST = 3,
TRAVEL_STATE_WORK_DO_QUEST = 4,
TRAVEL_STATE_TRAVEL_HAND_IN_QUEST = 5,
TRAVEL_STATE_WORK_HAND_IN_QUEST = 6,
TRAVEL_STATE_TRAVEL_RPG = 7,
TRAVEL_STATE_TRAVEL_EXPLORE = 8,
MAX_TRAVEL_STATE
};
enum TravelStatus
{
TRAVEL_STATUS_NONE = 0,
TRAVEL_STATUS_PREPARE = 1,
TRAVEL_STATUS_TRAVEL = 2,
TRAVEL_STATUS_WORK = 3,
TRAVEL_STATUS_COOLDOWN = 4,
TRAVEL_STATUS_EXPIRED = 5,
MAX_TRAVEL_STATUS
};
class QuestTravelDestination;
// A quest destination container for quick lookup of all destinations related to a quest.
struct QuestContainer
{
std::vector<QuestTravelDestination*> questGivers;
std::vector<QuestTravelDestination*> questTakers;
std::vector<QuestTravelDestination*> questObjectives;
};
typedef std::pair<int32, int32> mGridCoord;
// Extension of WorldLocation with distance functions.
class WorldPosition : public WorldLocation
{
public:
// Constructors
WorldPosition() : WorldLocation(){};
WorldPosition(WorldLocation const& loc) : WorldLocation(loc) {}
WorldPosition(WorldPosition const& pos) : WorldLocation(pos), visitors(pos.visitors) {}
WorldPosition(std::string const str);
WorldPosition(uint32 mapid, float x, float y, float z = 0.f, float orientation = 0.f)
: WorldLocation(mapid, x, y, z, orientation)
{
}
WorldPosition(uint32 mapId, const Position& pos);
WorldPosition(WorldObject const* wo);
WorldPosition(std::vector<WorldPosition*> list, WorldPositionConst conType);
WorldPosition(std::vector<WorldPosition> list, WorldPositionConst conType);
WorldPosition(uint32 mapid, GridCoord grid);
WorldPosition(uint32 mapid, CellCoord cell);
WorldPosition(uint32 mapid, mGridCoord grid);
//Setters
void set(const WorldLocation& pos);
void set(const WorldObject* wo);
void set(const WorldPosition& pos);
void setMapId(uint32 id);
void setX(float x);
void setY(float y);
void setZ(float z);
void setO(float o);
void addVisitor() { ++visitors; }
void remVisitor() { --visitors; }
// Getters
operator bool() const;
friend bool operator==(WorldPosition const& p1, const WorldPosition& p2);
friend bool operator!=(WorldPosition const& p1, const WorldPosition& p2);
WorldPosition& operator=(WorldPosition const&) = default;
WorldPosition& operator+=(WorldPosition const& p1);
WorldPosition& operator-=(WorldPosition const& p1);
uint32 getMapId();
float getX();
float getY();
float getZ();
float getO();
G3D::Vector3 getVector3();
std::string const print();
std::string const to_string();
std::vector<std::string> split(const std::string& s, char delimiter);
void printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim = 0, bool loop = false);
void printWKT(std::ostringstream& out) { printWKT({*this}, out); }
uint32 getVisitors() { return visitors; }
bool isOverworld();
bool isInWater();
bool isUnderWater();
WorldPosition relPoint(WorldPosition* center);
WorldPosition offset(WorldPosition* center);
float size();
// Slow distance function using possible map transfers.
float distance(WorldPosition* center);
float distance(WorldPosition center) { return distance(&center); }
float fDist(WorldPosition* center);
float fDist(WorldPosition center) { return fDist(&center); }
template <class T>
std::pair<T, WorldPosition> closest(std::vector<std::pair<T, WorldPosition>> list)
{
return *std::min_element(list.begin(), list.end(),
[this](std::pair<T, WorldPosition> i, std::pair<T, WorldPosition> j)
{ return this->distance(i.second) < this->distance(j.second); });
}
template <class T>
std::pair<T, WorldPosition> closest(std::vector<T> list)
{
return closest(GetPosList(list));
}
// Returns the closest point from the list.
WorldPosition* closest(std::vector<WorldPosition*> list)
{
return *std::min_element(list.begin(), list.end(),
[this](WorldPosition* i, WorldPosition* j)
{ return this->distance(i) < this->distance(j); });
}
WorldPosition closest(std::vector<WorldPosition> list)
{
return *std::min_element(list.begin(), list.end(),
[this](WorldPosition i, WorldPosition j)
{ return this->distance(i) < this->distance(j); });
}
// Quick square distance in 2d plane.
float sqDistance2d(WorldPosition center)
{
return (getX() - center.getX()) * (getX() - center.getX()) +
(getY() - center.getY()) * (getY() - center.getY());
}
// Quick square distance calculation without map check. Used for getting the minimum distant points.
float sqDistance(WorldPosition center)
{
return (getX() - center.getX()) * (getX() - center.getX()) +
(getY() - center.getY()) * (getY() - center.getY()) +
(getZ() - center.getZ()) * (getZ() - center.getZ());
}
float sqDistance2d(WorldPosition* center)
{
return (getX() - center->getX()) * (getX() - center->getX()) +
(getY() - center->getY()) * (getY() - center->getY());
}
float sqDistance(WorldPosition* center)
{
return (getX() - center->getX()) * (getX() - center->getX()) +
(getY() - center->getY()) * (getY() - center->getY()) +
(getZ() - center->getZ()) * (getZ() - center->getZ());
}
// Returns the closest point of the list. Fast but only works for the same map.
WorldPosition* closestSq(std::vector<WorldPosition*> list)
{
return *std::min_element(list.begin(), list.end(),
[this](WorldPosition* i, WorldPosition* j)
{ return this->sqDistance(i) < this->sqDistance(j); });
}
WorldPosition closestSq(std::vector<WorldPosition> list)
{
return *std::min_element(list.begin(), list.end(),
[this](WorldPosition i, WorldPosition j)
{ return this->sqDistance(i) < this->sqDistance(j); });
}
float getAngleTo(WorldPosition endPos)
{
float ang = atan2(endPos.getY() - getY(), endPos.getX() - getX());
return (ang >= 0) ? ang : 2 * static_cast<float>(M_PI) + ang;
}
float getAngleBetween(WorldPosition dir1, WorldPosition dir2) { return abs(getAngleTo(dir1) - getAngleTo(dir2)); }
WorldPosition lastInRange(std::vector<WorldPosition> list, float minDist = -1.f, float maxDist = -1.f);
WorldPosition firstOutRange(std::vector<WorldPosition> list, float minDist = -1.f, float maxDist = -1.f);
float mSign(WorldPosition* p1, WorldPosition* p2)
{
return (getX() - p2->getX()) * (p1->getY() - p2->getY()) - (p1->getX() - p2->getX()) * (getY() - p2->getY());
}
bool isInside(WorldPosition* p1, WorldPosition* p2, WorldPosition* p3);
// Map functions. Player independent.
MapEntry const* getMapEntry();
uint32 getInstanceId();
Map* getMap();
float getHeight(); // remove const - whipowill
std::set<Transport*> getTransports(uint32 entry = 0);
GridCoord getGridCoord() { return Acore::ComputeGridCoord(getX(), getY()); };
std::vector<GridCoord> getGridCoord(WorldPosition secondPos);
std::vector<WorldPosition> fromGridCoord(GridCoord GridCoord);
CellCoord getCellCoord() { return Acore::ComputeCellCoord(getX(), getY()); }
std::vector<WorldPosition> fromCellCoord(CellCoord cellCoord);
std::vector<WorldPosition> gridFromCellCoord(CellCoord cellCoord);
mGridCoord getmGridCoord()
{
return std::make_pair((int32)(CENTER_GRID_ID - getX() / SIZE_OF_GRIDS),
(int32)(CENTER_GRID_ID - getY() / SIZE_OF_GRIDS));
}
std::vector<mGridCoord> getmGridCoords(WorldPosition secondPos);
std::vector<WorldPosition> frommGridCoord(mGridCoord GridCoord);
void loadMapAndVMap(uint32 mapId, uint8 x, uint8 y);
void loadMapAndVMap() { loadMapAndVMap(getMapId(), getmGridCoord().first, getmGridCoord().second); }
void loadMapAndVMaps(WorldPosition secondPos);
// Display functions
WorldPosition getDisplayLocation();
float getDisplayX() { return getDisplayLocation().getY() * -1.0; }
float getDisplayY() { return getDisplayLocation().getX(); }
uint16 getAreaId();
AreaTableEntry const* getArea();
std::string const getAreaName(bool fullName = true, bool zoneName = false);
std::vector<WorldPosition> fromPointsArray(std::vector<G3D::Vector3> path);
// Pathfinding
std::vector<WorldPosition> getPathStepFrom(WorldPosition startPos, Unit* bot);
std::vector<WorldPosition> getPathFromPath(std::vector<WorldPosition> startPath, Unit* bot, uint8 maxAttempt = 40);
std::vector<WorldPosition> getPathFrom(WorldPosition startPos, Unit* bot)
{
return getPathFromPath({startPos}, bot);
}
std::vector<WorldPosition> getPathTo(WorldPosition endPos, Unit* bot) { return endPos.getPathFrom(*this, bot); }
bool isPathTo(std::vector<WorldPosition> path, float maxDistance = sPlayerbotAIConfig->targetPosRecalcDistance)
{
return !path.empty() && distance(path.back()) < maxDistance;
};
bool cropPathTo(std::vector<WorldPosition>& path, float maxDistance = sPlayerbotAIConfig->targetPosRecalcDistance);
bool canPathTo(WorldPosition endPos, Unit* bot) { return endPos.isPathTo(getPathTo(endPos, bot)); }
float getPathLength(std::vector<WorldPosition> points)
{
float dist = 0.f;
for (auto& p : points)
{
if (&p == &points.front())
dist = 0.f;
else
dist += std::prev(&p, 1)->distance(p);
}
return dist;
}
bool GetReachableRandomPointOnGround(Player* bot, float radius, bool randomRange = true);
uint32 getUnitsAggro(GuidVector& units, Player* bot);
// Creatures
std::vector<CreatureData const*> getCreaturesNear(float radius = 0, uint32 entry = 0);
// GameObjects
std::vector<GameObjectData const*> getGameObjectsNear(float radius = 0, uint32 entry = 0);
private:
uint32 visitors = 0;
};
inline ByteBuffer& operator<<(ByteBuffer& b, WorldPosition& guidP)
{
b << guidP.getMapId();
b << guidP.getX();
b << guidP.getY();
b << guidP.getZ();
b << guidP.getO();
b << guidP.getVisitors();
return b;
}
inline ByteBuffer& operator>>(ByteBuffer& b, [[maybe_unused]] WorldPosition& g)
{
uint32 mapid;
float coord_x;
float coord_y;
float coord_z;
float orientation;
uint32 visitors = 0;
b >> mapid;
b >> coord_x;
b >> coord_y;
b >> coord_z;
b >> orientation;
b >> visitors;
return b;
}
// Generic creature finder
class FindPointCreatureData
{
public:
FindPointCreatureData(WorldPosition point1 = WorldPosition(), float radius1 = 0, uint32 entry1 = 0)
{
point = point1;
radius = radius1;
entry = entry1;
}
void operator()(CreatureData const& dataPair);
std::vector<CreatureData const*> GetResult() const { return data; };
private:
WorldPosition point;
float radius;
uint32 entry;
std::vector<CreatureData const*> data;
};
// Generic gameObject finder
class FindPointGameObjectData
{
public:
FindPointGameObjectData(WorldPosition point1 = WorldPosition(), float radius1 = 0, uint32 entry1 = 0)
{
point = point1;
radius = radius1;
entry = entry1;
}
void operator()(GameObjectData const& dataPair);
std::vector<GameObjectData const*> GetResult() const { return data; };
private:
WorldPosition point;
float radius;
uint32 entry;
std::vector<GameObjectData const*> data;
};
class GuidPosition : public ObjectGuid, public WorldPosition
{
public:
GuidPosition() : ObjectGuid(), WorldPosition(), loadedFromDB(false) { }
GuidPosition(WorldObject* wo);
GuidPosition(CreatureData const& creData);
GuidPosition(GameObjectData const& goData);
CreatureTemplate const* GetCreatureTemplate();
GameObjectTemplate const* GetGameObjectTemplate();
WorldObject* GetWorldObject();
Creature* GetCreature();
Unit* GetUnit();
GameObject* GetGameObject();
Player* GetPlayer();
bool HasNpcFlag(NPCFlags flag);
bool isDead(); // For loaded grids check if the unit/object is unloaded/dead.
operator bool() const { return !IsEmpty(); }
bool operator==(ObjectGuid const& guid) const { return GetRawValue() == guid.GetRawValue(); }
bool operator!=(ObjectGuid const& guid) const { return GetRawValue() != guid.GetRawValue(); }
bool operator<(ObjectGuid const& guid) const { return GetRawValue() < guid.GetRawValue(); }
private:
bool loadedFromDB;
};
template <class T>
std::vector<std::pair<T, WorldPosition>> GetPosList(std::vector<T> oList)
{
std::vector<std::pair<T, WorldPosition>> retList;
for (auto& obj : oList)
retList.push_back(std::make_pair(obj, WorldPosition(obj)));
return std::move(retList);
};
template <class T>
std::vector<std::pair<T, WorldPosition>> GetPosVector(std::vector<T> oList)
{
std::vector<std::pair<T, WorldPosition>> retList;
for (auto& obj : oList)
retList.push_back(make_pair(obj, WorldPosition(obj)));
return std::move(retList);
};
class mapTransfer
{
public:
mapTransfer(WorldPosition pointFrom1, WorldPosition pointTo1, float portalLength1 = 0.1f)
: pointFrom(pointFrom1), pointTo(pointTo1), portalLength(portalLength1)
{
}
bool isFrom(WorldPosition point) { return point.getMapId() == pointFrom.getMapId(); }
bool isTo(WorldPosition point) { return point.getMapId() == pointTo.getMapId(); }
WorldPosition* getPointFrom() { return &pointFrom; }
WorldPosition* getPointTo() { return &pointTo; }
bool isUseful(WorldPosition point) { return isFrom(point) || isTo(point); }
float distance(WorldPosition point)
{
return isUseful(point) ? (isFrom(point) ? point.distance(pointFrom) : point.distance(pointTo)) : 200000;
}
bool isUseful(WorldPosition start, WorldPosition end) { return isFrom(start) && isTo(end); }
float distance(WorldPosition start, WorldPosition end)
{
return (isUseful(start, end) ? (start.distance(pointFrom) + portalLength + pointTo.distance(end)) : 200000);
}
float fDist(WorldPosition start, WorldPosition end);
private:
WorldPosition pointFrom;
WorldPosition pointTo;
float portalLength = 0.1f;
};
// A destination for a bot to travel to and do something.
class TravelDestination
{
public:
TravelDestination() {}
TravelDestination(float radiusMin1, float radiusMax1)
{
radiusMin = radiusMin1;
radiusMax = radiusMax1;
}
TravelDestination(std::vector<WorldPosition*> points1, float radiusMin1, float radiusMax1)
{
points = points1;
radiusMin = radiusMin1;
radiusMax = radiusMax1;
}
virtual ~TravelDestination() = default;
void addPoint(WorldPosition* pos) { points.push_back(pos); }
void setExpireDelay(uint32 delay) { expireDelay = delay; }
void setCooldownDelay(uint32 delay) { cooldownDelay = delay; }
void setMaxVisitors(uint32 maxVisitors1 = 0, uint32 maxVisitorsPerPoint1 = 0)
{
maxVisitors = maxVisitors1;
maxVisitorsPerPoint = maxVisitorsPerPoint1;
}
std::vector<WorldPosition*> getPoints(bool ignoreFull = false);
uint32 getExpireDelay() { return expireDelay; }
uint32 getCooldownDelay() { return cooldownDelay; }
void addVisitor() { ++visitors; }
void remVisitor() { --visitors; }
uint32 getVisitors() { return visitors; }
virtual Quest const* GetQuestTemplate() { return nullptr; }
virtual bool isActive([[maybe_unused]] Player* bot) { return false; }
bool isFull(bool ignoreFull = false);
virtual std::string const getName() { return "TravelDestination"; }
virtual int32 getEntry() { return 0; }
virtual std::string const getTitle() { return "generic travel destination"; }
WorldPosition* nearestPoint(WorldPosition* pos);
float distanceTo(WorldPosition* pos) { return nearestPoint(pos)->distance(pos); }
bool onMap(WorldPosition* pos) { return nearestPoint(pos)->getMapId() == pos->getMapId(); }
virtual bool isIn(WorldPosition* pos, float radius = 0.f)
{
return onMap(pos) && distanceTo(pos) <= (radius ? radius : radiusMin);
}
virtual bool isOut(WorldPosition* pos, float radius = 0.f)
{
return !onMap(pos) || distanceTo(pos) > (radius ? radius : radiusMax);
}
float getRadiusMin() { return radiusMin; }
std::vector<WorldPosition*> touchingPoints(WorldPosition* pos);
std::vector<WorldPosition*> sortedPoints(WorldPosition* pos);
std::vector<WorldPosition*> nextPoint(WorldPosition* pos, bool ignoreFull = true);
protected:
std::vector<WorldPosition*> points;
float radiusMin = 0;
float radiusMax = 0;
uint32 visitors = 0;
uint32 maxVisitors = 0;
uint32 maxVisitorsPerPoint = 0;
uint32 expireDelay = 5 * 1000;
uint32 cooldownDelay = 60 * 1000;
};
// A travel target that is always inactive and jumps to cooldown.
class NullTravelDestination : public TravelDestination
{
public:
NullTravelDestination(uint32 cooldownDelay1 = 5 * 60 * 1000) : TravelDestination()
{
cooldownDelay = cooldownDelay1;
};
Quest const* GetQuestTemplate() override { return nullptr; }
bool isActive([[maybe_unused]] Player* bot) override { return false; }
std::string const getName() override { return "NullTravelDestination"; }
std::string const getTitle() override { return "no destination"; }
bool isIn([[maybe_unused]] WorldPosition* pos, [[maybe_unused]] float radius = 0.f) override { return true; }
bool isOut([[maybe_unused]] WorldPosition* pos, [[maybe_unused]] float radius = 0.f) override { return false; }
};
// A travel target specifically related to a quest.
class QuestTravelDestination : public TravelDestination
{
public:
QuestTravelDestination(uint32 questId1, float radiusMin1, float radiusMax1);
Quest const* GetQuestTemplate() override { return questTemplate; }
bool isActive(Player* bot) override;
std::string const getName() override { return "QuestTravelDestination"; }
int32 getEntry() override { return 0; }
std::string const getTitle() override;
protected:
uint32 questId;
Quest const* questTemplate;
};
// A quest giver or taker.
class QuestRelationTravelDestination : public QuestTravelDestination
{
public:
QuestRelationTravelDestination(uint32 quest_id1, uint32 entry1, uint32 relation1, float radiusMin1,
float radiusMax1)
: QuestTravelDestination(quest_id1, radiusMin1, radiusMax1)
{
entry = entry1;
relation = relation1;
}
bool isActive(Player* bot) override;
std::string const getName() override { return "QuestRelationTravelDestination"; }
int32 getEntry() override { return entry; }
std::string const getTitle() override;
virtual uint32 getRelation() { return relation; }
private:
uint32 relation;
int32 entry;
};
// A quest objective (creature/gameobject to grind/loot)
class QuestObjectiveTravelDestination : public QuestTravelDestination
{
public:
QuestObjectiveTravelDestination(uint32 quest_id1, uint32 entry1, uint32 objective1, float radiusMin1,
float radiusMax1, uint32 itemId1 = 0)
: QuestTravelDestination(quest_id1, radiusMin1, radiusMax1)
{
objective = objective1;
entry = entry1;
itemId = itemId1;
}
bool isCreature();
uint32 ReqCreature();
uint32 ReqGOId();
uint32 ReqCount();
bool isActive(Player* bot) override;
std::string const getName() override { return "QuestObjectiveTravelDestination"; }
int32 getEntry() override { return entry; }
std::string const getTitle() override;
private:
uint32 objective;
uint32 entry;
uint32 itemId = 0;
};
// A location with rpg target(s) based on race and level
class RpgTravelDestination : public TravelDestination
{
public:
RpgTravelDestination(uint32 entry1, float radiusMin1, float radiusMax1) : TravelDestination(radiusMin1, radiusMax1)
{
entry = entry1;
}
bool isActive(Player* bot) override;
virtual CreatureTemplate const* GetCreatureTemplate();
std::string const getName() override { return "RpgTravelDestination"; }
int32 getEntry() override { return 0; }
std::string const getTitle() override;
protected:
uint32 entry;
};
// A location with zone exploration target(s)
class ExploreTravelDestination : public TravelDestination
{
public:
ExploreTravelDestination(uint32 areaId1, float radiusMin1, float radiusMax1)
: TravelDestination(radiusMin1, radiusMax1)
{
areaId = areaId1;
}
bool isActive(Player* bot) override;
std::string const getName() override { return "ExploreTravelDestination"; }
int32 getEntry() override { return 0; }
std::string const getTitle() override { return title; };
virtual void setTitle(std::string newTitle) { title = newTitle; }
virtual uint32 getAreaId() { return areaId; }
protected:
uint32 areaId;
std::string title = "";
};
// A location with zone exploration target(s)
class GrindTravelDestination : public TravelDestination
{
public:
GrindTravelDestination(int32 entry1, float radiusMin1, float radiusMax1) : TravelDestination(radiusMin1, radiusMax1)
{
entry = entry1;
}
bool isActive(Player* bot) override;
virtual CreatureTemplate const* GetCreatureTemplate();
std::string const getName() override { return "GrindTravelDestination"; }
int32 getEntry() override { return entry; }
std::string const getTitle() override;
protected:
int32 entry;
};
// A location with a boss
class BossTravelDestination : public TravelDestination
{
public:
BossTravelDestination(int32 entry1, float radiusMin1, float radiusMax1) : TravelDestination(radiusMin1, radiusMax1)
{
entry = entry1;
cooldownDelay = 1000;
}
bool isActive(Player* bot) override;
CreatureTemplate const* getCreatureTemplate();
std::string const getName() override { return "BossTravelDestination"; }
int32 getEntry() override { return entry; }
std::string const getTitle() override;
protected:
int32 entry;
};
// Current target and location for the bot to travel to.
// The flow is as follows:
// PREPARE (wait until no loot is near)
// TRAVEL (move towards target until close enough) (rpg and grind is disabled)
// WORK (grind/rpg until the target is no longer active) (rpg and grind is enabled on quest mobs)
// COOLDOWN (wait some time free to do what the bot wants)
// EXPIRE (if any of the above actions take too long pick a new target)
class TravelTarget : AiObject
{
public:
TravelTarget(PlayerbotAI* botAI) : AiObject(botAI), m_status(TRAVEL_STATUS_NONE), startTime(getMSTime()){};
TravelTarget(PlayerbotAI* botAI, TravelDestination* tDestination1, WorldPosition* wPosition1)
: AiObject(botAI), m_status(TRAVEL_STATUS_NONE), startTime(getMSTime())
{
setTarget(tDestination1, wPosition1);
}
~TravelTarget();
void setTarget(TravelDestination* tDestination1, WorldPosition* wPosition1, bool groupCopy1 = false);
void setStatus(TravelStatus status);
void setExpireIn(uint32 expireMs) { statusTime = getExpiredTime() + expireMs; }
void incRetry(bool isMove)
{
if (isMove)
++moveRetryCount;
else
++extendRetryCount;
}
void setRetry(bool isMove, uint32 newCount = 0)
{
if (isMove)
moveRetryCount = newCount;
else
extendRetryCount = newCount;
}
void setForced(bool forced1) { forced = forced1; }
void setRadius(float radius1) { radius = radius1; }
void copyTarget(TravelTarget* target);
void addVisitors();
void releaseVisitors();
float distance(Player* bot);
WorldPosition* getPosition();
TravelDestination* getDestination();
uint32 getEntry()
{
if (!tDestination)
return 0;
return tDestination->getEntry();
}
PlayerbotAI* getAi() { return botAI; }
uint32 getExpiredTime() { return getMSTime() - startTime; }
uint32 getTimeLeft() { return statusTime - getExpiredTime(); }
uint32 getMaxTravelTime();
uint32 getRetryCount(bool isMove) { return isMove ? moveRetryCount : extendRetryCount; }
bool isTraveling();
bool isActive();
bool isWorking();
bool isPreparing();
bool isMaxRetry(bool isMove) { return isMove ? (moveRetryCount > 5) : (extendRetryCount > 5); }
TravelStatus getStatus() { return m_status; }
TravelState getTravelState();
bool isGroupCopy() { return groupCopy; }
bool isForced() { return forced; }
protected:
TravelStatus m_status;
uint32 startTime;
uint32 statusTime = 0;
bool forced = false;
float radius = 0.f;
bool groupCopy = false;
bool visitor = true;
uint32 extendRetryCount = 0;
uint32 moveRetryCount = 0;
TravelDestination* tDestination = nullptr;
WorldPosition* wPosition = nullptr;
};
// General container for all travel destinations.
class TravelMgr
{
public:
TravelMgr(){};
static TravelMgr* instance()
{
static TravelMgr instance;
return &instance;
}
void Clear();
void LoadQuestTravelTable();
template <class D, class W, class URBG>
void weighted_shuffle(D first, D last, W first_weight, W last_weight, URBG&& g)
{
while (first != last && first_weight != last_weight)
{
std::discrete_distribution<int> dd(first_weight, last_weight);
auto i = dd(g);
if (i)
{
std::swap(*first, *std::next(first, i));
std::swap(*first_weight, *std::next(first_weight, i));
}
++first;
++first_weight;
}
}
std::vector<WorldPosition*> getNextPoint(WorldPosition* center, std::vector<WorldPosition*> points,
uint32 amount = 1);
std::vector<WorldPosition> getNextPoint(WorldPosition center, std::vector<WorldPosition> points, uint32 amount = 1);
QuestStatusData* getQuestStatus(Player* bot, uint32 questId);
bool getObjectiveStatus(Player* bot, Quest const* pQuest, uint32 objective);
uint32 getDialogStatus(Player* pPlayer, int32 questgiver, Quest const* pQuest);
std::vector<TravelDestination*> getQuestTravelDestinations(Player* bot, int32 questId = -1, bool ignoreFull = false,
bool ignoreInactive = false, float maxDistance = 5000,
bool ignoreObjectives = false);
std::vector<TravelDestination*> getRpgTravelDestinations(Player* bot, bool ignoreFull = false,
bool ignoreInactive = false, float maxDistance = 5000);
std::vector<TravelDestination*> getExploreTravelDestinations(Player* bot, bool ignoreFull = false,
bool ignoreInactive = false);
std::vector<TravelDestination*> getGrindTravelDestinations(Player* bot, bool ignoreFull = false,
bool ignoreInactive = false, float maxDistance = 5000);
std::vector<TravelDestination*> getBossTravelDestinations(Player* bot, bool ignoreFull = false,
bool ignoreInactive = false, float maxDistance = 25000);
void setNullTravelTarget(Player* player);
void addMapTransfer(WorldPosition start, WorldPosition end, float portalDistance = 0.1f, bool makeShortcuts = true);
void loadMapTransfers();
float mapTransDistance(WorldPosition start, WorldPosition end);
float fastMapTransDistance(WorldPosition start, WorldPosition end);
NullTravelDestination* nullTravelDestination = new NullTravelDestination();
WorldPosition* nullWorldPosition = new WorldPosition();
void addBadVmap(uint32 mapId, uint8 x, uint8 y) { badVmap.push_back(std::make_tuple(mapId, x, y)); }
void addBadMmap(uint32 mapId, uint8 x, uint8 y) { badMmap.push_back(std::make_tuple(mapId, x, y)); }
bool isBadVmap(uint32 mapId, uint8 x, uint8 y)
{
return std::find(badVmap.begin(), badVmap.end(), std::make_tuple(mapId, x, y)) != badVmap.end();
}
bool isBadMmap(uint32 mapId, uint8 x, uint8 y)
{
return std::find(badMmap.begin(), badMmap.end(), std::make_tuple(mapId, x, y)) != badMmap.end();
}
void printGrid(uint32 mapId, int x, int y, std::string const type);
void printObj(WorldObject* obj, std::string const type);
// protected:
void logQuestError(uint32 errorNr, Quest* quest, uint32 objective = 0, uint32 unitId = 0, uint32 itemId = 0);
std::vector<uint32> avoidLoaded;
std::vector<QuestTravelDestination*> questGivers;
std::vector<RpgTravelDestination*> rpgNpcs;
std::vector<GrindTravelDestination*> grindMobs;
std::vector<BossTravelDestination*> bossMobs;
std::unordered_map<uint32, ExploreTravelDestination*> exploreLocs;
std::unordered_map<uint32, QuestContainer*> quests;
std::vector<std::tuple<uint32, uint8, uint8>> badVmap, badMmap;
std::unordered_map<std::pair<uint32, uint32>, std::vector<mapTransfer>, boost::hash<std::pair<uint32, uint32>>>
mapTransfersMap;
};
#define sTravelMgr TravelMgr::instance()
#endif

2586
src/helper/TravelNode.cpp Normal file

File diff suppressed because it is too large Load Diff

600
src/helper/TravelNode.h Normal file
View File

@@ -0,0 +1,600 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TRAVELNODE_H
#define _PLAYERBOT_TRAVELNODE_H
#include <shared_mutex>
#include "TravelMgr.h"
// THEORY
//
// Pathfinding in (c)mangos is based on detour recast an opensource nashmesh creation and pathfinding codebase.
// This system is used for mob and npc pathfinding and in this codebase also for bots.
// Because mobs and npc movement is based on following a player or a set path the PathGenerator is limited to 296y.
// This means that when trying to find a path from A to B distances beyond 296y will be a best guess often moving in a
// straight path. Bots would get stuck moving from Northshire to Stormwind because there is no 296y path that doesn't
// go (initially) the wrong direction.
//
// To remedy this limitation without altering the PathGenerator limits too much this node system was introduced.
//
// <S> ---> [N1] ---> [N2] ---> [N3] ---> <E>
//
// Bot at <S> wants to move to <E>
// [N1],[N2],[N3] are predefined nodes for wich we know we can move from [N1] to [N2] and from [N2] to [N3] but not
// from [N1] to [N3] If we can move fom [S] to [N1] and from [N3] to [E] we have a complete route to travel.
//
// Termonology:
// Node: a location on a map for which we know bots are likely to want to travel to or need to travel past to reach
// other nodes. Link: the connection between two nodes. A link signifies that the bot can travel from one node to
// another. A link is one-directional. Path: the waypointpath returned by the standard PathGenerator to move from one
// node (or position) to another. A path can be imcomplete or empty which means there is no link. Route: the list of
// nodes that give the shortest route from a node to a distant node. Routes are calculated using a standard A* search
// based on links.
//
// On server start saved nodes and links are loaded. Paths and routes are calculated on the fly but saved for future
// use. Nodes can be added and removed realtime however because bots access the nodes from different threads this
// requires a locking mechanism.
//
// Initially the current nodes have been made:
// Flightmasters and Inns (Bots can use these to fast-travel so eventually they will be included in the route
// calculation) WorldBosses and Unique bosses in instances (These are a logical places bots might want to go in
// instances) Player start spawns (Obviously all lvl1 bots will spawn and move from here) Area triggers locations with
// teleport and their teleport destinations (These used to travel in or between maps) Transports including elevators
// (Again used to travel in and in maps) (sub)Zone means (These are the center most point for each sub-zone which is
// good for global coverage)
//
// To increase coverage/linking extra nodes can be automatically be created.
// Current implentation places nodes on paths (including complete) at sub-zone transitions or randomly.
// After calculating possible links the node is removed if it does not create local coverage.
//
enum class TravelNodePathType : uint8
{
none = 0,
walk = 1,
portal = 2,
transport = 3,
flightPath = 4,
teleportSpell = 5
};
// A connection between two nodes.
class TravelNodePath
{
public:
// Legacy Constructor for travelnodestore
// TravelNodePath(float distance1, float extraCost1, bool portal1 = false, uint32 portalId1 = 0, bool transport1 =
// false, bool calculated = false, uint8 maxLevelMob1 = 0, uint8 maxLevelAlliance1 = 0, uint8 maxLevelHorde1 = 0,
// float swimDistance1 = 0, bool flightPath1 = false);
// Constructor
TravelNodePath(float distance = 0.1f, float extraCost = 0, uint8 pathType = (uint8)TravelNodePathType::walk,
uint32 pathObject = 0, bool calculated = false, std::vector<uint8> maxLevelCreature = {0, 0, 0},
float swimDistance = 0)
: extraCost(extraCost),
calculated(calculated),
distance(distance),
maxLevelCreature(maxLevelCreature),
swimDistance(swimDistance),
pathType(TravelNodePathType(pathType)),
pathObject(pathObject) // reorder args - whipowill
{
if (pathType != (uint8)TravelNodePathType::walk)
complete = true;
};
TravelNodePath(TravelNodePath* basePath)
{
complete = basePath->complete;
path = basePath->path;
extraCost = basePath->extraCost;
calculated = basePath->calculated;
distance = basePath->distance;
maxLevelCreature = basePath->maxLevelCreature;
swimDistance = basePath->swimDistance;
pathType = basePath->pathType;
pathObject = basePath->pathObject;
};
// Getters
bool getComplete() { return complete || pathType != TravelNodePathType::walk; }
std::vector<WorldPosition> getPath() { return path; }
TravelNodePathType getPathType() { return pathType; }
uint32 getPathObject() { return pathObject; }
float getDistance() { return distance; }
float getSwimDistance() { return swimDistance; }
float getExtraCost() { return extraCost; }
std::vector<uint8> getMaxLevelCreature() { return maxLevelCreature; }
void setCalculated(bool calculated1 = true) { calculated = calculated1; }
bool getCalculated() { return calculated; }
std::string const print();
// Setters
void setComplete(bool complete1) { complete = complete1; }
void setPath(std::vector<WorldPosition> path1) { path = path1; }
void setPathAndCost(std::vector<WorldPosition> path1, float speed)
{
setPath(path1);
calculateCost(true);
extraCost = distance / speed;
}
// void setPortal(bool portal1, uint32 portalId1 = 0) { portal = portal1; portalId = portalId1; }
// void setTransport(bool transport1) { transport = transport1; }
void setPathType(TravelNodePathType pathType1) { pathType = pathType1; }
void setPathObject(uint32 pathObject1) { pathObject = pathObject1; }
void calculateCost(bool distanceOnly = false);
float getCost(Player* bot = nullptr, uint32 cGold = 0);
uint32 getPrice();
private:
// Does the path have all the points to get to the destination?
bool complete = false;
// List of WorldPositions to get to the destination.
std::vector<WorldPosition> path = {};
// The extra (loading/transport) time it takes to take this path.
float extraCost = 0;
bool calculated = false;
// Derived distance in yards
float distance = 0.1f;
// Calculated mobs level along the way.
std::vector<uint8> maxLevelCreature = {0, 0, 0}; // mobs, horde, alliance
// Calculated swiming distances along the way.
float swimDistance = 0;
TravelNodePathType pathType = TravelNodePathType::walk;
uint32 pathObject = 0;
/*
//Is the path a portal/teleport to the destination?
bool portal = false;
//Area trigger Id
uint32 portalId = 0;
//Is the path transport based?
bool transport = false;
// Is the path a flightpath?
bool flightPath = false;
*/
};
// A waypoint to travel from or to.
// Each node knows which other nodes can be reached without help.
class TravelNode
{
public:
// Constructors
TravelNode(){};
TravelNode(WorldPosition point1, std::string const nodeName1 = "Travel Node", bool important1 = false)
{
nodeName = nodeName1;
point = point1;
important = important1;
}
TravelNode(TravelNode* baseNode)
{
nodeName = baseNode->nodeName;
point = baseNode->point;
important = baseNode->important;
}
// Setters
void setLinked(bool linked1) { linked = linked1; }
void setPoint(WorldPosition point1) { point = point1; }
// Getters
std::string const getName() { return nodeName; };
WorldPosition* getPosition() { return &point; };
std::unordered_map<TravelNode*, TravelNodePath>* getPaths() { return &paths; }
std::unordered_map<TravelNode*, TravelNodePath*>* getLinks() { return &links; }
bool isImportant() { return important; };
bool isLinked() { return linked; }
bool isTransport()
{
for (auto const& link : *getLinks())
if (link.second->getPathType() == TravelNodePathType::transport)
return true;
return false;
}
uint32 getTransportId()
{
for (auto const& link : *getLinks())
if (link.second->getPathType() == TravelNodePathType::transport)
return link.second->getPathObject();
return false;
}
bool isPortal()
{
for (auto const& link : *getLinks())
if (link.second->getPathType() == TravelNodePathType::portal)
return true;
return false;
}
bool isWalking()
{
for (auto link : *getLinks())
if (link.second->getPathType() == TravelNodePathType::walk)
return true;
return false;
}
// WorldLocation shortcuts
uint32 getMapId() { return point.getMapId(); }
float getX() { return point.getX(); }
float getY() { return point.getY(); }
float getZ() { return point.getZ(); }
float getO() { return point.getO(); }
float getDistance(WorldPosition pos) { return point.distance(pos); }
float getDistance(TravelNode* node) { return point.distance(node->getPosition()); }
float fDist(TravelNode* node) { return point.fDist(node->getPosition()); }
float fDist(WorldPosition pos) { return point.fDist(pos); }
TravelNodePath* setPathTo(TravelNode* node, TravelNodePath path = TravelNodePath(), bool isLink = true)
{
if (this != node)
{
paths[node] = path;
if (isLink)
links[node] = &paths[node];
return &paths[node];
}
return nullptr;
}
bool hasPathTo(TravelNode* node) { return paths.find(node) != paths.end(); }
TravelNodePath* getPathTo(TravelNode* node) { return &paths[node]; }
bool hasCompletePathTo(TravelNode* node) { return hasPathTo(node) && getPathTo(node)->getComplete(); }
TravelNodePath* buildPath(TravelNode* endNode, Unit* bot, bool postProcess = false);
void setLinkTo(TravelNode* node, float distance = 0.1f)
{
if (this != node)
{
if (!hasPathTo(node))
setPathTo(node, TravelNodePath(distance));
else
links[node] = &paths[node];
}
}
bool hasLinkTo(TravelNode* node) { return links.find(node) != links.end(); }
float linkCostTo(TravelNode* node) { return paths.find(node)->second.getDistance(); }
float linkDistanceTo(TravelNode* node) { return paths.find(node)->second.getDistance(); }
void removeLinkTo(TravelNode* node, bool removePaths = false);
bool isEqual(TravelNode* compareNode);
// Removes links to other nodes that can also be reached by passing another node.
bool isUselessLink(TravelNode* farNode);
void cropUselessLink(TravelNode* farNode);
bool cropUselessLinks();
// Returns all nodes that can be reached from this node.
std::vector<TravelNode*> getNodeMap(bool importantOnly = false, std::vector<TravelNode*> ignoreNodes = {});
// Checks if it is even possible to route to this node.
bool hasRouteTo(TravelNode* node)
{
if (routes.empty())
for (auto mNode : getNodeMap())
routes[mNode] = true;
return routes.find(node) != routes.end();
};
void print(bool printFailed = true);
protected:
// Logical name of the node
std::string nodeName;
// WorldPosition of the node.
WorldPosition point;
// List of paths to other nodes.
std::unordered_map<TravelNode*, TravelNodePath> paths;
// List of links to other nodes.
std::unordered_map<TravelNode*, TravelNodePath*> links;
// List of nodes and if there is 'any' route possible
std::unordered_map<TravelNode*, bool> routes;
// This node should not be removed
bool important = false;
// This node has been checked for nearby links
bool linked = false;
// This node is a (moving) transport.
// bool transport = false;
// Entry of transport.
// uint32 transportId = 0;
};
class PortalNode : public TravelNode
{
public:
PortalNode(TravelNode* baseNode) : TravelNode(baseNode){};
void SetPortal(TravelNode* baseNode, TravelNode* endNode, uint32 portalSpell)
{
nodeName = baseNode->getName();
point = *baseNode->getPosition();
paths.clear();
links.clear();
TravelNodePath path(0.1f, 0.1f, (uint8)TravelNodePathType::teleportSpell, portalSpell, true);
setPathTo(endNode, path);
};
};
// Route step type
enum PathNodeType
{
NODE_PREPATH = 0,
NODE_PATH = 1,
NODE_NODE = 2,
NODE_PORTAL = 3,
NODE_TRANSPORT = 4,
NODE_FLIGHTPATH = 5,
NODE_TELEPORT = 6
};
struct PathNodePoint
{
WorldPosition point;
PathNodeType type = NODE_PATH;
uint32 entry = 0;
};
// A complete list of points the bots has to walk to or teleport to.
class TravelPath
{
public:
TravelPath(){};
TravelPath(std::vector<PathNodePoint> fullPath1) { fullPath = fullPath1; }
TravelPath(std::vector<WorldPosition> path, PathNodeType type = NODE_PATH, uint32 entry = 0)
{
addPath(path, type, entry);
}
void addPoint(PathNodePoint point) { fullPath.push_back(point); }
void addPoint(WorldPosition point, PathNodeType type = NODE_PATH, uint32 entry = 0)
{
fullPath.push_back(PathNodePoint{point, type, entry});
}
void addPath(std::vector<WorldPosition> path, PathNodeType type = NODE_PATH, uint32 entry = 0)
{
for (auto& p : path)
{
fullPath.push_back(PathNodePoint{p, type, entry});
};
}
void addPath(std::vector<PathNodePoint> newPath)
{
fullPath.insert(fullPath.end(), newPath.begin(), newPath.end());
}
void clear() { fullPath.clear(); }
bool empty() { return fullPath.empty(); }
std::vector<PathNodePoint> getPath() { return fullPath; }
WorldPosition getFront() { return fullPath.front().point; }
WorldPosition getBack() { return fullPath.back().point; }
std::vector<WorldPosition> getPointPath()
{
std::vector<WorldPosition> retVec;
for (auto const& p : fullPath)
retVec.push_back(p.point);
return retVec;
};
bool makeShortCut(WorldPosition startPos, float maxDist);
bool shouldMoveToNextPoint(WorldPosition startPos, std::vector<PathNodePoint>::iterator beg,
std::vector<PathNodePoint>::iterator ed, std::vector<PathNodePoint>::iterator p,
float& moveDist, float maxDist);
WorldPosition getNextPoint(WorldPosition startPos, float maxDist, TravelNodePathType& pathType, uint32& entry);
std::ostringstream const print();
private:
std::vector<PathNodePoint> fullPath;
};
// An stored A* search that gives a complete route from one node to another.
class TravelNodeRoute
{
public:
TravelNodeRoute() {}
TravelNodeRoute(std::vector<TravelNode*> nodes1) { nodes = nodes1; /*currentNode = route.begin();*/ }
bool isEmpty() { return nodes.empty(); }
bool hasNode(TravelNode* node) { return findNode(node) != nodes.end(); }
float getTotalDistance();
std::vector<TravelNode*> getNodes() { return nodes; }
TravelPath buildPath(std::vector<WorldPosition> pathToStart = {}, std::vector<WorldPosition> pathToEnd = {},
Unit* bot = nullptr);
std::ostringstream const print();
private:
std::vector<TravelNode*>::iterator findNode(TravelNode* node)
{
return std::find(nodes.begin(), nodes.end(), node);
}
std::vector<TravelNode*> nodes;
};
// A node container to aid A* calculations with nodes.
class TravelNodeStub
{
public:
TravelNodeStub(TravelNode* dataNode1) { dataNode = dataNode1; }
TravelNode* dataNode;
float m_f = 0.0, m_g = 0.0, m_h = 0.0;
bool open = false, close = false;
TravelNodeStub* parent = nullptr;
uint32 currentGold = 0;
};
// The container of all nodes.
class TravelNodeMap
{
public:
TravelNodeMap(){};
TravelNodeMap(TravelNodeMap* baseMap);
static TravelNodeMap* instance()
{
static TravelNodeMap instance;
return &instance;
}
TravelNode* addNode(WorldPosition pos, std::string const preferedName = "Travel Node", bool isImportant = false,
bool checkDuplicate = true, bool transport = false, uint32 transportId = 0);
void removeNode(TravelNode* node);
bool removeNodes()
{
if (m_nMapMtx.try_lock_for(std::chrono::seconds(10)))
{
for (auto& node : m_nodes)
removeNode(node);
m_nMapMtx.unlock();
return true;
}
return false;
};
void fullLinkNode(TravelNode* startNode, Unit* bot);
// Get all nodes
std::vector<TravelNode*> getNodes() { return m_nodes; }
std::vector<TravelNode*> getNodes(WorldPosition pos, float range = -1);
// Find nearest node.
TravelNode* getNode(TravelNode* sameNode)
{
for (auto& node : m_nodes)
{
if (node->getName() == sameNode->getName() && node->getPosition() == sameNode->getPosition())
return node;
}
return nullptr;
}
TravelNode* getNode(WorldPosition pos, std::vector<WorldPosition>& ppath, Unit* bot = nullptr, float range = -1);
TravelNode* getNode(WorldPosition pos, Unit* bot = nullptr, float range = -1)
{
std::vector<WorldPosition> ppath;
return getNode(pos, ppath, bot, range);
}
// Get Random Node
TravelNode* getRandomNode(WorldPosition pos)
{
std::vector<TravelNode*> rNodes = getNodes(pos);
if (rNodes.empty())
return nullptr;
return rNodes[urand(0, rNodes.size() - 1)];
}
// Finds the best nodePath between two nodes
TravelNodeRoute getRoute(TravelNode* start, TravelNode* goal, Player* bot = nullptr);
// Find the best node between two positions
TravelNodeRoute getRoute(WorldPosition startPos, WorldPosition endPos, std::vector<WorldPosition>& startPath,
Player* bot = nullptr);
// Find the full path between those locations
static TravelPath getFullPath(WorldPosition startPos, WorldPosition endPos, Player* bot = nullptr);
// Manage/update nodes
void manageNodes(Unit* bot, bool mapFull = false);
void setHasToGen() { hasToGen = true; }
void generateNpcNodes();
void generateStartNodes();
void generateAreaTriggerNodes();
void generateNodes();
void generateTransportNodes();
void generateZoneMeanNodes();
void generateWalkPaths();
void removeLowNodes();
void removeUselessPaths();
void calculatePathCosts();
void generateTaxiPaths();
void generatePaths();
void generateAll();
void printMap();
void printNodeStore();
void saveNodeStore();
void loadNodeStore();
bool cropUselessNode(TravelNode* startNode);
TravelNode* addZoneLinkNode(TravelNode* startNode);
TravelNode* addRandomExtNode(TravelNode* startNode);
void calcMapOffset();
WorldPosition getMapOffset(uint32 mapId);
std::shared_timed_mutex m_nMapMtx;
std::unordered_map<ObjectGuid, std::unordered_map<uint32, TravelNode*>> teleportNodes;
private:
std::vector<TravelNode*> m_nodes;
std::vector<std::pair<uint32, WorldPosition>> mapOffsets;
bool hasToSave = false;
bool hasToGen = false;
bool hasToFullGen = false;
};
#define sTravelNodeMap TravelNodeMap::instance()
#endif