[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)

This commit is contained in:
bashermens
2026-01-19 22:45:28 +01:00
committed by GitHub
parent fd07e02a8a
commit 41c53365ae
1119 changed files with 27 additions and 27 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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

View File

@@ -0,0 +1,322 @@
#include "PlayerbotGuildMgr.h"
#include "Player.h"
#include "PlayerbotAIConfig.h"
#include "DatabaseEnv.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h"
PlayerbotGuildMgr::PlayerbotGuildMgr(){}
void PlayerbotGuildMgr::Init()
{
_guildCache.clear();
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
DeleteBotGuilds();
LoadGuildNames();
ValidateGuildCache();
}
bool PlayerbotGuildMgr::CreateGuild(Player* player, std::string guildName)
{
Guild* guild = new Guild();
if (!guild->Create(player, guildName))
{
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName,
player->GetName());
delete guild;
return false;
}
sGuildMgr->AddGuild(guild);
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName);
SetGuildEmblem(guild->GetId());
GuildCache entry;
entry.name = guildName;
entry.memberCount = 1;
entry.status = 1;
entry.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
entry.faction = player->GetTeamId();
_guildCache[guild->GetId()] = entry;
return true;
}
bool PlayerbotGuildMgr::SetGuildEmblem(uint32 guildId)
{
Guild* guild = sGuildMgr->GetGuildById(guildId);
if (!guild)
return false;
// create random emblem
uint32 st, cl, br, bc, bg;
bg = urand(0, 51);
bc = urand(0, 17);
cl = urand(0, 17);
br = urand(0, 7);
st = urand(0, 180);
LOG_DEBUG("playerbots",
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), st, cl, br, bc, bg);
// populate guild table with a random tabard design
CharacterDatabase.Execute(
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
"WHERE guildid={}",
st, cl, br, bc, bg, guild->GetId());
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
// Immediate reading for log
if (QueryResult qr = CharacterDatabase.Query(
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
guild->GetId()))
{
Field* f = qr->Fetch();
LOG_DEBUG("playerbots",
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
}
return true;
}
std::string PlayerbotGuildMgr::AssignToGuild(Player* player)
{
if (!player)
return "";
uint8_t playerFaction = player->GetTeamId();
std::vector<GuildCache*> partiallyfilledguilds;
partiallyfilledguilds.reserve(_guildCache.size());
for (auto& keyValue : _guildCache)
{
GuildCache& cached = keyValue.second;
if (!cached.hasRealPlayer && cached.status == 1 && cached.faction == playerFaction)
partiallyfilledguilds.push_back(&cached);
}
if (!partiallyfilledguilds.empty())
{
size_t idx = static_cast<size_t>(urand(0, static_cast<int>(partiallyfilledguilds.size()) - 1));
return (partiallyfilledguilds[idx]->name);
}
size_t count = std::count_if(
_guildCache.begin(), _guildCache.end(),
[](const std::pair<const uint32, GuildCache>& pair)
{
return !pair.second.hasRealPlayer;
}
);
if (count < sPlayerbotAIConfig->randomBotGuildCount)
{
for (auto& key : _shuffled_guild_keys)
{
if (_guildNames[key])
{
LOG_INFO("playerbots","Assigning player [{}] to guild [{}]", player->GetName(), key);
return key;
}
}
LOG_ERROR("playerbots","No available guild names left.");
}
return "";
}
void PlayerbotGuildMgr::OnGuildUpdate(Guild* guild)
{
auto it = _guildCache.find(guild->GetId());
if (it == _guildCache.end())
return;
GuildCache& entry = it->second;
entry.memberCount = guild->GetMemberCount();
if (entry.memberCount < entry.maxMembers)
entry.status = 1;
else if (entry.memberCount >= entry.maxMembers)
entry.status = 2; // Full
std::string guildName = guild->GetName();
for (auto& it : _guildNames)
{
if (it.first == guildName)
{
it.second = false;
break;
}
}
}
void PlayerbotGuildMgr::ResetGuildCache()
{
for (auto it = _guildCache.begin(); it != _guildCache.end();)
{
GuildCache& cached = it->second;
cached.memberCount = 0;
cached.faction = 2;
cached.status = 0;
}
}
void PlayerbotGuildMgr::LoadGuildNames()
{
LOG_INFO("playerbots", "Loading guild names from playerbots_guild_names...");
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM playerbots_guild_names");
if (!result)
{
LOG_ERROR("playerbots", "No entries found in playerbots_guild_names. List is empty.");
return;
}
do
{
Field* fields = result->Fetch();
_guildNames[fields[1].Get<std::string>()] = true;
} while (result->NextRow());
for (auto& pair : _guildNames)
_shuffled_guild_keys.push_back(pair.first);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(_shuffled_guild_keys.begin(), _shuffled_guild_keys.end(), g);
LOG_INFO("playerbots", "Loaded {} guild entries from playerbots_guild_names table.", _guildNames.size());
}
void PlayerbotGuildMgr::ValidateGuildCache()
{
QueryResult result = CharacterDatabase.Query("SELECT guildid, name FROM guild");
if (!result)
{
LOG_ERROR("playerbots", "No guilds found in database, resetting guild cache");
ResetGuildCache();
return;
}
std::unordered_map<uint32, std::string> dbGuilds;
do
{
Field* fields = result->Fetch();
uint32 guildId = fields[0].Get<uint32>();
std::string guildName = fields[1].Get<std::string>();
dbGuilds[guildId] = guildName;
} while (result->NextRow());
for (auto it = dbGuilds.begin(); it != dbGuilds.end(); it++)
{
uint32 guildId = it->first;
GuildCache cache;
cache.name = it->second;
cache.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
Guild* guild = sGuildMgr ->GetGuildById(guildId);
if (!guild)
continue;
cache.memberCount = guild->GetMemberCount();
ObjectGuid leaderGuid = guild->GetLeaderGUID();
CharacterCacheEntry const* leaderEntry = sCharacterCache->GetCharacterCacheByGuid(leaderGuid);
uint32 leaderAccount = leaderEntry->AccountId;
cache.hasRealPlayer = !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
cache.faction = Player::TeamIdForRace(leaderEntry->Race);
if (cache.memberCount == 0)
cache.status = 0; // empty
else if (cache.memberCount < cache.maxMembers)
cache.status = 1; // partially filled
else
cache.status = 2; // full
_guildCache.insert_or_assign(guildId, cache);
for (auto& it : _guildNames)
{
if (it.first == cache.name)
{
it.second = false;
break;
}
}
}
}
void PlayerbotGuildMgr::DeleteBotGuilds()
{
LOG_INFO("playerbots", "Deleting random bot guilds...");
std::vector<uint32> randomBots;
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
stmt->SetData(0, "add");
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
randomBots.push_back(bot);
} while (result->NextRow());
}
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
{
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
guild->Disband();
}
LOG_INFO("playerbots", "Random bot guilds deleted");
}
bool PlayerbotGuildMgr::IsRealGuild(Player* bot)
{
if (!bot)
return false;
uint32 guildId = bot->GetGuildId();
if (!guildId)
return false;
return IsRealGuild(guildId);
}
bool PlayerbotGuildMgr::IsRealGuild(uint32 guildId)
{
if (!guildId)
return false;
auto it = _guildCache.find(guildId);
if (it == _guildCache.end())
return false;
return it->second.hasRealPlayer;
}
class BotGuildCacheWorldScript : public WorldScript
{
public:
BotGuildCacheWorldScript() : WorldScript("BotGuildCacheWorldScript"), _validateTimer(0){}
void OnUpdate(uint32 diff) override
{
_validateTimer += diff;
if (_validateTimer >= _validateInterval) // Validate every hour
{
_validateTimer = 0;
sPlayerbotGuildMgr->ValidateGuildCache();
LOG_INFO("playerbots", "Scheduled guild cache validation");
}
}
private:
uint32 _validateInterval = HOUR*IN_MILLISECONDS;
uint32 _validateTimer;
};
void PlayerBotsGuildValidationScript()
{
new BotGuildCacheWorldScript();
}

View File

@@ -0,0 +1,52 @@
#ifndef _PLAYERBOT_PLAYERBOTGUILDMGR_H
#define _PLAYERBOT_PLAYERBOTGUILDMGR_H
#include "Guild.h"
#include "Player.h"
#include "PlayerbotAI.h"
class PlayerbotAI;
class PlayerbotGuildMgr
{
public:
static PlayerbotGuildMgr* instance()
{
static PlayerbotGuildMgr instance;
return &instance;
}
void Init();
std::string AssignToGuild(Player* player);
void LoadGuildNames();
void ValidateGuildCache();
void ResetGuildCache();
bool CreateGuild(Player* player, std::string guildName);
void OnGuildUpdate (Guild* guild);
bool SetGuildEmblem(uint32 guildId);
void DeleteBotGuilds();
bool IsRealGuild(uint32 guildId);
bool IsRealGuild(Player* bot);
private:
PlayerbotGuildMgr();
std::unordered_map<std::string, bool> _guildNames;
struct GuildCache
{
std::string name;
uint8 status;
uint32 maxMembers = 0;
uint32 memberCount = 0;
uint8 faction = 0;
bool hasRealPlayer = false;
};
std::unordered_map<uint32 , GuildCache> _guildCache;
std::vector<std::string> _shuffled_guild_keys;
};
void PlayerBotsGuildValidationScript();
#define sPlayerbotGuildMgr PlayerbotGuildMgr::instance()
#endif

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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());
}

432
src/Mgr/Item/ItemVisitors.h Normal file
View File

@@ -0,0 +1,432 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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 CollectItemsVisitor : public IterateItemsVisitor
{
public:
CollectItemsVisitor() : IterateItemsVisitor() {}
std::vector<Item*> items;
bool Visit(Item* item) override
{
items.push_back(item);
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,411 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LootObjectStack.h"
#include "LootMgr.h"
#include "Object.h"
#include "ObjectAccessor.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 (size_t 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;
}
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event) or on
// respawn time
GameObject* go = botAI->GetGameObject(guid);
if (go && (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE) || !go->isSpawned()))
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)
{
LootObject nearest = GetNearest(maxDistance);
return !nearest.IsEmpty();
}
LootObject LootObjectStack::GetLoot(float maxDistance)
{
LootObject nearest = GetNearest(maxDistance);
return nearest.IsEmpty() ? LootObject() : nearest;
}
LootObject LootObjectStack::GetNearest(float maxDistance)
{
availableLoot.shrink(time(nullptr) - 30);
LootObject nearest;
float nearestDistance = std::numeric_limits<float>::max();
LootTargetList safeCopy(availableLoot);
for (LootTargetList::iterator i = safeCopy.begin(); i != safeCopy.end(); i++)
{
ObjectGuid guid = i->guid;
WorldObject* worldObj = ObjectAccessor::GetWorldObject(*bot, guid);
if (!worldObj)
continue;
float distance = bot->GetDistance(worldObj);
if (distance >= nearestDistance || (maxDistance && distance > maxDistance))
continue;
LootObject lootObject(bot, guid);
if (!lootObject.IsLootPossible(bot))
continue;
nearestDistance = distance;
nearest = lootObject;
}
return nearest;
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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);
LootObject& operator=(LootObject const& other) = default;
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:
LootObject GetNearest(float maxDistance = 0);
Player* bot;
LootTargetList availableLoot;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RANDOMITEMMGR_H
#define _PLAYERBOT_RANDOMITEMMGR_H
#include <map>
#include <set>
#include <unordered_set>
#include <vector>
#include "AiFactory.h"
#include "Common.h"
#include "ItemTemplate.h"
class ChatHandler;
struct ItemTemplate;
enum EquipmentSlots : uint32;
enum RandomItemType
{
RANDOM_ITEM_GUILD_TASK,
RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE,
RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN,
RANDOM_ITEM_GUILD_TASK_REWARD_TRADE,
RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE
};
#define MAX_STAT_SCALES 32
enum ItemSource
{
ITEM_SOURCE_NONE,
ITEM_SOURCE_DROP,
ITEM_SOURCE_VENDOR,
ITEM_SOURCE_QUEST,
ITEM_SOURCE_CRAFT,
ITEM_SOURCE_PVP
};
struct WeightScaleInfo
{
uint32 id;
std::string name;
};
struct WeightScaleStat
{
std::string stat;
uint32 weight;
};
struct StatWeight
{
uint32 id;
uint32 weight;
};
struct ItemInfoEntry
{
ItemInfoEntry()
: minLevel(0), source(0), sourceId(0), team(0), repRank(0), repFaction(0), quality(0), slot(0), itemId(0)
{
for (uint8 i = 1; i <= MAX_STAT_SCALES; ++i)
{
weights[i] = 0;
}
}
std::map<uint32, uint32> weights;
uint32 minLevel;
uint32 source;
uint32 sourceId;
uint32 team;
uint32 repRank;
uint32 repFaction;
uint32 quality;
uint32 slot;
uint32 itemId;
};
typedef std::vector<WeightScaleStat> WeightScaleStats;
// typedef std::map<WeightScaleInfo, WeightScaleStats> WeightScaleList;
struct WeightScale
{
WeightScaleInfo info;
WeightScaleStats stats;
};
// typedef map<uint32, WeightScale> WeightScales;
class RandomItemPredicate
{
public:
virtual ~RandomItemPredicate(){};
virtual bool Apply(ItemTemplate const* proto) = 0;
};
typedef std::vector<uint32> RandomItemList;
typedef std::map<RandomItemType, RandomItemList> RandomItemCache;
class BotEquipKey
{
public:
BotEquipKey() : level(0), clazz(0), slot(0), quality(0), key(GetKey()) {}
BotEquipKey(uint32 level, uint8 clazz, uint8 slot, uint32 quality)
: level(level), clazz(clazz), slot(slot), quality(quality), key(GetKey())
{
}
BotEquipKey(BotEquipKey const& other)
: level(other.level), clazz(other.clazz), slot(other.slot), quality(other.quality), key(GetKey())
{
}
bool operator<(BotEquipKey const& other) const { return other.key < this->key; }
uint32 level;
uint8 clazz;
uint8 slot;
uint32 quality;
uint64 key;
private:
uint64 GetKey();
};
typedef std::map<BotEquipKey, RandomItemList> BotEquipCache;
class RandomItemMgr
{
public:
RandomItemMgr();
virtual ~RandomItemMgr();
static RandomItemMgr* instance()
{
static RandomItemMgr instance;
return &instance;
}
public:
void Init();
void InitAfterAhBot();
static bool HandleConsoleCommand(ChatHandler* handler, char const* args);
RandomItemList Query(uint32 level, RandomItemType type, RandomItemPredicate* predicate);
RandomItemList Query(uint32 level, uint8 clazz, uint8 slot, uint32 quality);
uint32 GetUpgrade(Player* player, std::string spec, uint8 slot, uint32 quality, uint32 itemId);
std::vector<uint32> GetUpgradeList(Player* player, std::string spec, uint8 slot, uint32 quality, uint32 itemId,
uint32 amount = 1);
bool HasStatWeight(uint32 itemId);
uint32 GetMinLevelFromCache(uint32 itemId);
uint32 GetStatWeight(Player* player, uint32 itemId);
uint32 GetLiveStatWeight(Player* player, uint32 itemId);
uint32 GetRandomItem(uint32 level, RandomItemType type, RandomItemPredicate* predicate = nullptr);
std::vector<uint32> GetAmmo(uint32 level, uint32 subClass);
uint32 GetRandomPotion(uint32 level, uint32 effect);
uint32 GetRandomFood(uint32 level, uint32 category);
uint32 GetFood(uint32 level, uint32 category);
uint32 GetRandomTrade(uint32 level);
uint32 CalculateStatWeight(uint8 playerclass, uint8 spec, ItemTemplate const* proto);
uint32 CalculateSingleStatWeight(uint8 playerclass, uint8 spec, std::string stat, uint32 value);
bool CanEquipArmor(uint8 clazz, uint32 level, ItemTemplate const* proto);
bool ShouldEquipArmorForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto);
bool CanEquipWeapon(uint8 clazz, ItemTemplate const* proto);
bool ShouldEquipWeaponForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto);
float GetItemRarity(uint32 itemId);
uint32 GetQuestIdForItem(uint32 itemId);
std::vector<uint32> GetQuestIdsForItem(uint32 itemId);
static bool IsUsedBySkill(ItemTemplate const* proto, uint32 skillId);
bool IsTestItem(uint32 itemId) { return itemForTest.find(itemId) != itemForTest.end(); }
std::vector<uint32> GetCachedEquipments(uint32 requiredLevel, uint32 inventoryType);
private:
void BuildRandomItemCache();
void BuildEquipCache();
void BuildEquipCacheNew();
void BuildItemInfoCache();
void BuildAmmoCache();
void BuildFoodCache();
void BuildPotionCache();
void BuildTradeCache();
void BuildRarityCache();
bool CanEquipItem(BotEquipKey key, ItemTemplate const* proto);
bool CanEquipItemNew(ItemTemplate const* proto);
void AddItemStats(uint32 mod, uint8& sp, uint8& ap, uint8& tank);
bool CheckItemStats(uint8 clazz, uint8 sp, uint8 ap, uint8 tank);
private:
std::map<uint32, RandomItemCache> randomItemCache;
std::map<RandomItemType, RandomItemPredicate*> predicates;
BotEquipCache equipCache;
std::map<EquipmentSlots, std::set<InventoryType>> viableSlots;
std::map<uint32, std::map<uint32, std::vector<uint32>>> ammoCache;
std::map<uint32, std::map<uint32, std::vector<uint32>>> potionCache;
std::map<uint32, std::map<uint32, std::vector<uint32>>> foodCache;
std::map<uint32, std::vector<uint32>> tradeCache;
std::map<uint32, float> rarityCache;
std::map<uint8, WeightScale> m_weightScales[MAX_CLASSES];
std::map<std::string, uint32> weightStatLink;
std::map<std::string, uint32> weightRatingLink;
std::map<uint32, ItemInfoEntry> itemInfoCache;
std::unordered_set<uint32> itemForTest;
static std::set<uint32> itemCache;
// equipCacheNew[RequiredLevel][InventoryType]
std::map<uint32, std::map<uint32, std::vector<uint32>>> equipCacheNew;
};
#define sRandomItemMgr RandomItemMgr::instance()
#endif

View File

@@ -0,0 +1,838 @@
#include "StatsCollector.h"
#include <cstdint>
#include "DBCStores.h"
#include "ItemEnchantmentMgr.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIAware.h"
#include "SharedDefines.h"
#include "SpellAuraDefines.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "UpdateFields.h"
#include "Util.h"
StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); }
void StatsCollector::Reset()
{
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
stats[i] = 0;
}
}
void StatsCollector::CollectItemStats(ItemTemplate const* proto)
{
if (proto->IsRangedWeapon())
{
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_RANGED_DPS] += val;
}
else if (proto->IsWeapon())
{
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
stats[STATS_TYPE_MELEE_DPS] += val;
}
stats[STATS_TYPE_ARMOR] += proto->Armor;
stats[STATS_TYPE_BLOCK_VALUE] += proto->Block;
for (int i = 0; i < proto->StatsCount; i++)
{
const _ItemStat& stat = proto->ItemStat[i];
const int32& val = stat.ItemStatValue;
CollectByItemStatType(stat.ItemStatType, val);
}
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
switch (proto->Spells[j].SpellTrigger)
{
case ITEM_SPELLTRIGGER_ON_USE:
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown);
break;
case ITEM_SPELLTRIGGER_ON_EQUIP:
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0);
break;
case ITEM_SPELLTRIGGER_CHANCE_ON_HIT:
if (type_ & CollectorType::MELEE)
{
if (proto->Spells[j].SpellPPMRate > 0.01f)
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate);
else
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8
}
break;
default:
break;
}
}
if (proto->socketBonus)
{
if (const SpellItemEnchantmentEntry* enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus))
CollectEnchantStats(enchant);
}
}
void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 spellCooldown)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return;
if (SpecialSpellFilter(spellId))
return;
const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id);
uint32 triggerCooldown = eventEntry ? eventEntry->cooldown : 0;
bool canNextTrigger = true;
uint32 procFlags;
uint32 procChance;
if (eventEntry && eventEntry->procFlags)
procFlags = eventEntry->procFlags;
else
procFlags = spellInfo->ProcFlags;
if (eventEntry && eventEntry->customChance)
procChance = eventEntry->customChance;
else
procChance = spellInfo->ProcChance;
bool lowChance = procChance <= 5;
if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)))
canNextTrigger = false;
if (spellInfo->StackAmount)
{
// Heuristic multiplier for spell with stackAmount since high stackAmount may not be available
if (spellInfo->StackAmount <= 1)
multiplier *= spellInfo->StackAmount * 1;
else if (spellInfo->StackAmount <= 5)
multiplier *= 1 + (spellInfo->StackAmount - 1) * 0.75;
else if (spellInfo->StackAmount <= 10)
multiplier *= 4 + (spellInfo->StackAmount - 5) * 0.6;
else if (spellInfo->StackAmount <= 20)
multiplier *= 7 + (spellInfo->StackAmount - 10) * 0.4;
else
multiplier *= 11;
}
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
{
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
if (!effectInfo.Effect)
continue;
switch (effectInfo.Effect)
{
case SPELL_EFFECT_APPLY_AURA:
{
if (spellInfo->SpellFamilyName && /*effectInfo.ApplyAuraName != SPELL_AURA_DUMMY &&*/
effectInfo.ApplyAuraName != SPELL_AURA_PROC_TRIGGER_SPELL)
{
if (!CheckSpellValidation(spellInfo->SpellFamilyName, effectInfo.SpellClassMask))
return;
// Some dummy effects cannot be recognized, make some bonus to identify
stats[STATS_TYPE_BONUS] += 1;
}
/// @todo Handle negative spell
if (!spellInfo->IsPositive())
break;
float coverage;
if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1)
coverage = 1.0f;
else
coverage =
std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
multiplier *= coverage;
HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown);
break;
}
case SPELL_EFFECT_HEAL:
{
/// @todo Handle spell without cooldown
if (!spellCooldown)
break;
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
int32 val = AverageValue(effectInfo);
float transfer_multiplier = 1;
stats[STATS_TYPE_HEAL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
break;
}
case SPELL_EFFECT_ENERGIZE:
{
/// @todo Handle spell without cooldown
if (!spellCooldown)
break;
if (effectInfo.MiscValue != POWER_MANA)
break;
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
int32 val = AverageValue(effectInfo);
float transfer_multiplier = 0.2;
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / normalizedCd * multiplier * transfer_multiplier;
break;
}
case SPELL_EFFECT_SCHOOL_DAMAGE:
{
/// @todo Handle spell without cooldown
if (!spellCooldown)
break;
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
int32 val = AverageValue(effectInfo);
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
{
float transfer_multiplier = 1;
stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
}
else if (type_ & CollectorType::SPELL_DMG)
{
float transfer_multiplier = 0.5;
stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
}
break;
}
default:
break;
}
}
}
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount)
{
for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
{
uint32 enchant_display_type = enchant->type[s];
uint32 enchant_amount = enchant->amount[s];
uint32 enchant_spell_id = enchant->spellid[s];
if (SpecialEnchantFilter(enchant_spell_id))
continue;
switch (enchant_display_type)
{
case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL:
{
if (type_ & CollectorType::MELEE)
CollectSpellStats(enchant_spell_id, 0.25f);
break;
}
case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL:
{
CollectSpellStats(enchant_spell_id, 1.0f);
break;
}
case ITEM_ENCHANTMENT_TYPE_STAT:
{
// for item random suffix
if (!enchant_amount)
enchant_amount = default_enchant_amount;
if (!enchant_amount)
{
break;
}
CollectByItemStatType(enchant_spell_id, enchant_amount);
break;
}
default:
break;
}
}
}
/// @todo Special case for some spell that hard to calculate, like trinket, relic, etc.
bool StatsCollector::SpecialSpellFilter(uint32 spellId)
{
// trinket
switch (spellId)
{
case 60764: // Totem of Splintering
if (type_ & (CollectorType::SPELL))
return true;
break;
case 27521: // Insightful Earthstorm Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 20;
return true;
case 55381: // Insightful Earthsiege Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 40;
return true;
case 39442: // Darkmoon Card: Wrath
if (!(type_ & CollectorType::SPELL_HEAL))
stats[STATS_TYPE_CRIT] += 50;
return true;
case 59620: // Berserk
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += 120;
return true;
case 67702: // Death's Verdict
stats[STATS_TYPE_ATTACK_POWER] += 225;
return true;
case 67771: // Death's Verdict (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 260;
return true;
case 71406: // Tiny Abomination in a Jar
if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 600;
else
stats[STATS_TYPE_ATTACK_POWER] += 150;
return true;
case 71545: // Tiny Abomination in a Jar (heroic)
if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 800;
else
stats[STATS_TYPE_ATTACK_POWER] += 200;
return true;
case 71519: // Deathbringer's Will
stats[STATS_TYPE_ATTACK_POWER] += 350;
return true;
case 71562: // Deathbringer's Will (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 400;
return true;
case 71602: // Dislodged Foreign Object
/// @todo The item can be triggered by heal spell, which mismatch with it's description
/// Noticing that heroic item can not be triggered, probably a bug to report to AC
if (type_ & CollectorType::SPELL_HEAL)
return true;
break;
case 71903: // Shadowmourne
stats[STATS_TYPE_STRENGTH] += 200;
return true;
default:
break;
}
// switch (spellId)
// {
// case 50457: // Idol of the Lunar Eclipse
// stats[STATS_TYPE_CRIT] += 150;
// return true;
// default:
// break;
// }
return false;
}
bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
{
switch (enchantSpellId)
{
case 64440:
if (type_ & CollectorType::MELEE)
{
stats[STATS_TYPE_PARRY] += 50;
}
return true;
case 53365: // Rune of the Fallen Crusader
if (type_ & CollectorType::MELEE)
{
stats[STATS_TYPE_STRENGTH] += 75;
}
return true;
case 62157: // Rune of the Stoneskin Gargoyle
if (type_ & CollectorType::MELEE)
{
stats[STATS_TYPE_DEFENSE] += 25;
stats[STATS_TYPE_STAMINA] += 50;
}
return true;
case 64571: // Blood draining
if (type_ & CollectorType::MELEE)
{
stats[STATS_TYPE_STAMINA] += 50;
}
return true;
default:
break;
}
return false;
}
bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict)
{
const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id);
uint32 spellFamilyName = 0;
if (eventEntry)
{
spellFamilyName = eventEntry->spellFamilyName;
flag96 spellFamilyMask = eventEntry->spellFamilyMask;
if (spellFamilyName != 0)
{
if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict))
return false;
}
}
uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask
switch (type_)
{
case CollectorType::MELEE_DMG:
{
triggerMask |= MELEE_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::MELEE_TANK:
{
triggerMask |= MELEE_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PERIODIC_PROC_FLAG_MASK;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::RANGED:
{
triggerMask |= RANGED_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::SPELL_DMG:
{
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Healing spell cannot trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS;
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::SPELL_HEAL:
{
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Dmg spell should not trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
if (!spellFamilyName)
triggerMask &=
~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell
if (procFlags & triggerMask)
return true;
break;
}
default:
break;
}
return false;
}
void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
{
switch (itemStatType)
{
case ITEM_MOD_MANA:
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
break;
case ITEM_MOD_HEALTH:
stats[STATS_TYPE_STAMINA] += (float)val / 15;
break;
case ITEM_MOD_AGILITY:
stats[STATS_TYPE_AGILITY] += val;
break;
case ITEM_MOD_STRENGTH:
stats[STATS_TYPE_STRENGTH] += val;
break;
case ITEM_MOD_INTELLECT:
stats[STATS_TYPE_INTELLECT] += val;
break;
case ITEM_MOD_SPIRIT:
stats[STATS_TYPE_SPIRIT] += val;
break;
case ITEM_MOD_STAMINA:
stats[STATS_TYPE_STAMINA] += val;
break;
case ITEM_MOD_DEFENSE_SKILL_RATING:
stats[STATS_TYPE_DEFENSE] += val;
break;
case ITEM_MOD_DODGE_RATING:
stats[STATS_TYPE_DODGE] += val;
break;
case ITEM_MOD_PARRY_RATING:
stats[STATS_TYPE_PARRY] += val;
break;
case ITEM_MOD_BLOCK_RATING:
stats[STATS_TYPE_BLOCK_RATING] += val;
break;
case ITEM_MOD_HIT_MELEE_RATING:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_HIT_RANGED_RATING:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_HIT_SPELL_RATING:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_CRIT_MELEE_RATING:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_CRIT_RANGED_RATING:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_CRIT_SPELL_RATING:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_HASTE_MELEE_RATING:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_HASTE_RANGED_RATING:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_HASTE_SPELL_RATING:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_HIT_RATING:
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_CRIT_RATING:
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_RESILIENCE_RATING:
stats[STATS_TYPE_RESILIENCE] += val;
break;
case ITEM_MOD_HASTE_RATING:
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_EXPERTISE_RATING:
stats[STATS_TYPE_EXPERTISE] += val;
break;
case ITEM_MOD_ATTACK_POWER:
stats[STATS_TYPE_ATTACK_POWER] += val;
break;
case ITEM_MOD_RANGED_ATTACK_POWER:
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_ATTACK_POWER] += val;
break;
case ITEM_MOD_MANA_REGENERATION:
stats[STATS_TYPE_MANA_REGENERATION] += val;
break;
case ITEM_MOD_ARMOR_PENETRATION_RATING:
stats[STATS_TYPE_ARMOR_PENETRATION] += val;
break;
case ITEM_MOD_SPELL_POWER:
stats[STATS_TYPE_SPELL_POWER] += val;
stats[STATS_TYPE_HEAL_POWER] += val;
break;
case ITEM_MOD_HEALTH_REGEN:
stats[STATS_TYPE_HEALTH_REGENERATION] += val;
break;
case ITEM_MOD_SPELL_PENETRATION:
stats[STATS_TYPE_SPELL_PENETRATION] += val;
break;
case ITEM_MOD_BLOCK_VALUE:
stats[STATS_TYPE_BLOCK_VALUE] += val;
break;
case ITEM_MOD_SPELL_HEALING_DONE: // deprecated
case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated
default:
break;
}
}
void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
uint32 triggerCooldown)
{
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
return;
int32 val = AverageValue(effectInfo);
switch (effectInfo.ApplyAuraName)
{
case SPELL_AURA_MOD_DAMAGE_DONE:
{
int32 schoolType = effectInfo.MiscValue;
if (schoolType & SPELL_SCHOOL_MASK_NORMAL)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
if ((schoolType & SPELL_SCHOOL_MASK_MAGIC) == SPELL_SCHOOL_MASK_MAGIC)
stats[STATS_TYPE_SPELL_POWER] += val * multiplier;
break;
}
case SPELL_AURA_MOD_HEALING_DONE:
stats[STATS_TYPE_HEAL_POWER] += val * multiplier;
break;
case SPELL_AURA_MOD_INCREASE_HEALTH:
stats[STATS_TYPE_STAMINA] += val * multiplier / 15;
break;
case SPELL_AURA_SCHOOL_ABSORB:
{
int32 schoolType = effectInfo.MiscValue;
if (schoolType & SPELL_SCHOOL_MASK_NORMAL)
stats[STATS_TYPE_STAMINA] += val * multiplier / 15;
break;
}
case SPELL_AURA_MOD_ATTACK_POWER:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break;
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier;
break;
case SPELL_AURA_MOD_STAT:
{
int32 statType = effectInfo.MiscValue;
switch (statType)
{
case STAT_STRENGTH:
stats[STATS_TYPE_STRENGTH] += val * multiplier;
break;
case STAT_AGILITY:
stats[STATS_TYPE_AGILITY] += val * multiplier;
break;
case STAT_STAMINA:
stats[STATS_TYPE_STAMINA] += val * multiplier;
break;
case STAT_INTELLECT:
stats[STATS_TYPE_INTELLECT] += val * multiplier;
break;
case STAT_SPIRIT:
stats[STATS_TYPE_SPIRIT] += val * multiplier;
break;
case -1: // Stat all
stats[STATS_TYPE_STRENGTH] += val * multiplier;
stats[STATS_TYPE_AGILITY] += val * multiplier;
stats[STATS_TYPE_STAMINA] += val * multiplier;
stats[STATS_TYPE_INTELLECT] += val * multiplier;
stats[STATS_TYPE_SPIRIT] += val * multiplier;
break;
default:
break;
}
break;
}
case SPELL_AURA_MOD_RESISTANCE:
{
int32 statType = effectInfo.MiscValue;
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
stats[STATS_TYPE_ARMOR] += val * multiplier;
break;
}
case SPELL_AURA_MOD_RATING:
{
for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating)
{
if (effectInfo.MiscValue & (1 << rating))
{
switch (rating)
{
case CR_DEFENSE_SKILL:
stats[STATS_TYPE_DEFENSE] += val * multiplier;
break;
case CR_DODGE:
stats[STATS_TYPE_DODGE] += val * multiplier;
break;
case CR_PARRY:
stats[STATS_TYPE_PARRY] += val * multiplier;
break;
case CR_BLOCK:
stats[STATS_TYPE_BLOCK_RATING] += val * multiplier;
break;
case CR_HIT_MELEE:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_HIT_RANGED:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_HIT_SPELL:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_CRIT_MELEE:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_CRIT_RANGED:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_CRIT_SPELL:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_HASTE_MELEE:
if (type_ & CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_HASTE_RANGED:
if (type_ & CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_HASTE_SPELL:
if (type_ & CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_EXPERTISE:
stats[STATS_TYPE_EXPERTISE] += val * multiplier;
break;
case CR_ARMOR_PENETRATION:
stats[STATS_TYPE_ARMOR_PENETRATION] += val * multiplier;
break;
default:
break;
}
}
}
}
case SPELL_AURA_MOD_POWER_REGEN:
{
int32 powerType = effectInfo.MiscValue;
switch (powerType)
{
case POWER_MANA:
stats[STATS_TYPE_MANA_REGENERATION] += val * multiplier;
default:
break;
}
break;
}
case SPELL_AURA_PROC_TRIGGER_SPELL:
{
if (canNextTrigger)
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
{
if (canNextTrigger)
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_ADD_TARGET_TRIGGER:
{
if (canNextTrigger)
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS:
{
if (type_ != CollectorType::SPELL_HEAL)
{
int32 statType = effectInfo.MiscValue;
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
stats[STATS_TYPE_CRIT] += 30 * val * multiplier;
}
break;
}
default:
break;
}
}
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
{
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
float basePoints = effectInfo.BasePoints;
int32 randomPoints = effectInfo.DieSides;
switch (randomPoints)
{
case 0:
break;
case 1:
basePoints += 1;
break;
default:
float randvalue = (1 + randomPoints) / 2.0f;
basePoints += randvalue;
break;
}
return basePoints;
}
bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict)
{
if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName)
return false;
bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags);
// strict to healer
if (strict && (type_ & CollectorType::SPELL_HEAL))
{
return isHealingSpell;
}
if (!(type_ & CollectorType::SPELL_HEAL) && isHealingSpell)
return false;
// spells for caster/melee/tank are ambiguous
if (cls_ == CLASS_DRUID && spellFamilyName == SPELLFAMILY_DRUID && (type_ & CollectorType::MELEE))
{
uint32 castingFlagsA = 0x4 | 0x2 | 0x1 | 0x200000; // starfire | moonfire | wrath | insect swarm
uint32 castingFlagsB = 0x0;
uint32 castingFlagsC = 0x0;
flag96 invalidFlags = {castingFlagsA, castingFlagsB, castingFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_TANK))
{
uint32 retributionFlagsA = 0x0;
uint32 retributionFlagsB = 0x8000; // crusader strike
uint32 retributionFlagsC = 0x0;
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_DMG))
{
uint32 retributionFlagsA = 0x0;
uint32 retributionFlagsB = 0x100000 | 0x40; // shield of righteouness | holy shield
uint32 retributionFlagsC = 0x0;
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::SPELL_DMG))
{
uint32 meleeFlagsA = 0x0;
uint32 meleeFlagsB = 0x1000010; // stromstrike
uint32 meleeFlagsC = 0x4; // lava lash
flag96 invalidFlags = {meleeFlagsA, meleeFlagsB, meleeFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::MELEE_DMG))
{
uint32 casterFlagsA = 0x0;
uint32 casterFlagsB = 0x1000; // lava burst
uint32 casterFlagsC = 0x0;
flag96 invalidFlags = {casterFlagsA, casterFlagsB, casterFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
return true;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_STATSCOLLECTOR_H
#define _PLAYERBOT_STATSCOLLECTOR_H
#include "ItemTemplate.h"
#include "SpellInfo.h"
enum StatsType : uint8
{
// Basic stats
STATS_TYPE_AGILITY = 0,
STATS_TYPE_STRENGTH,
STATS_TYPE_INTELLECT,
STATS_TYPE_SPIRIT,
STATS_TYPE_STAMINA,
STATS_TYPE_HIT,
STATS_TYPE_CRIT,
STATS_TYPE_HASTE,
// Stats for tank
STATS_TYPE_ARMOR,
STATS_TYPE_DEFENSE,
STATS_TYPE_DODGE,
STATS_TYPE_PARRY,
STATS_TYPE_BLOCK_VALUE,
STATS_TYPE_BLOCK_RATING,
STATS_TYPE_RESILIENCE,
STATS_TYPE_HEALTH_REGENERATION,
// Stats for spell damage
STATS_TYPE_SPELL_POWER,
STATS_TYPE_SPELL_PENETRATION,
// Stats for heal
STATS_TYPE_HEAL_POWER,
STATS_TYPE_MANA_REGENERATION,
// Stats for physical damage and melee
STATS_TYPE_ATTACK_POWER,
STATS_TYPE_ARMOR_PENETRATION,
STATS_TYPE_EXPERTISE,
// Stats for weapon dps
STATS_TYPE_MELEE_DPS,
STATS_TYPE_RANGED_DPS,
// Bonus for unrecognized stats
STATS_TYPE_BONUS,
STATS_TYPE_MAX = 26
};
enum CollectorType : uint8
{
MELEE_DMG = 1,
MELEE_TANK = 2,
RANGED = 4,
SPELL_DMG = 8,
SPELL_HEAL = 16,
MELEE = MELEE_DMG | MELEE_TANK,
SPELL = SPELL_DMG | SPELL_HEAL
};
class StatsCollector
{
public:
StatsCollector(CollectorType type, int32 cls = -1);
StatsCollector(StatsCollector& stats) = default;
void Reset();
void CollectItemStats(ItemTemplate const* proto);
void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1);
void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount = 0);
bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict = true);
bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true);
public:
float stats[STATS_TYPE_MAX];
private:
void CollectByItemStatType(uint32 itemStatType, int32 val);
bool SpecialSpellFilter(uint32 spellId);
bool SpecialEnchantFilter(uint32 enchantSpellId);
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
uint32 triggerCooldown);
float AverageValue(const SpellEffectInfo& effectInfo);
private:
CollectorType type_;
uint32 cls_;
};
#endif

View File

@@ -0,0 +1,740 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "StatsWeightCalculator.h"
#include <memory>
#include "AiFactory.h"
#include "DBCStores.h"
#include "ItemEnchantmentMgr.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "PlayerbotAI.h"
#include "PlayerbotFactory.h"
#include "SharedDefines.h"
#include "SpellAuraDefines.h"
#include "SpellMgr.h"
#include "StatsCollector.h"
#include "Unit.h"
StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
{
if (PlayerbotAI::IsHeal(player))
type_ = CollectorType::SPELL_HEAL;
else if (PlayerbotAI::IsCaster(player))
type_ = CollectorType::SPELL_DMG;
else if (PlayerbotAI::IsTank(player))
type_ = CollectorType::MELEE_TANK;
else if (PlayerbotAI::IsMelee(player))
type_ = CollectorType::MELEE_DMG;
else
type_ = CollectorType::RANGED;
cls = player->getClass();
lvl = player->GetLevel();
tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_ROGUE)
hitOverflowType_ = CollectorType::SPELL;
else
hitOverflowType_ = type_;
enable_overflow_penalty_ = true;
enable_item_set_bonus_ = true;
enable_quality_blend_ = true;
}
void StatsWeightCalculator::Reset()
{
collector_->Reset();
weight_ = 0;
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
stats_weights_[i] = 0;
}
}
float StatsWeightCalculator::CalculateItem(uint32 itemId, int32 randomPropertyIds)
{
ItemTemplate const* proto = &sObjectMgr->GetItemTemplateStore()->at(itemId);
if (!proto)
return 0.0f;
Reset();
collector_->CollectItemStats(proto);
if (randomPropertyIds != 0)
CalculateRandomProperty(randomPropertyIds, itemId);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player_);
GenerateWeights(player_);
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
weight_ += stats_weights_[i] * collector_->stats[i];
}
CalculateItemTypePenalty(proto);
if (enable_item_set_bonus_)
CalculateItemSetMod(player_, proto);
CalculateSocketBonus(player_, proto);
if (enable_quality_blend_)
{
// Heirloom items scale with player level
// Use player level as effective item level for heirlooms - Quality EPIC
// Else - Blend with item quality and level for normal items
if (proto->Quality == ITEM_QUALITY_HEIRLOOM)
weight_ *= PlayerbotFactory::CalcMixedGearScore(lvl, ITEM_QUALITY_EPIC);
else
weight_ *= PlayerbotFactory::CalcMixedGearScore(proto->ItemLevel, proto->Quality);
return weight_;
}
// If quality/level blending is disabled, also return the calculated weight.
return weight_;
}
float StatsWeightCalculator::CalculateEnchant(uint32 enchantId)
{
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
if (!enchant)
return 0.0f;
Reset();
collector_->CollectEnchantStats(enchant);
if (enable_overflow_penalty_)
ApplyOverflowPenalty(player_);
GenerateWeights(player_);
for (uint32 i = 0; i < STATS_TYPE_MAX; i++)
{
weight_ += stats_weights_[i] * collector_->stats[i];
}
return weight_;
}
void StatsWeightCalculator::CalculateRandomProperty(int32 randomPropertyId, uint32 itemId)
{
if (randomPropertyId > 0)
{
ItemRandomPropertiesEntry const* item_rand = sItemRandomPropertiesStore.LookupEntry(randomPropertyId);
if (!item_rand)
{
return;
}
for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
uint32 enchantId = item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0];
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
if (enchant)
collector_->CollectEnchantStats(enchant);
}
}
else
{
ItemRandomSuffixEntry const* item_rand = sItemRandomSuffixStore.LookupEntry(-randomPropertyId);
if (!item_rand)
{
return;
}
for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i)
{
uint32 enchantId = item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0];
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
uint32 enchant_amount = 0;
for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k)
{
if (item_rand->Enchantment[k] == enchantId)
{
enchant_amount = uint32((item_rand->AllocationPct[k] * GenerateEnchSuffixFactor(itemId)) / 10000);
break;
}
}
if (enchant)
collector_->CollectEnchantStats(enchant, enchant_amount);
}
}
}
void StatsWeightCalculator::GenerateWeights(Player* player)
{
GenerateBasicWeights(player);
GenerateAdditionalWeights(player);
ApplyWeightFinetune(player);
}
void StatsWeightCalculator::GenerateBasicWeights(Player* player)
{
// Basic weights
stats_weights_[STATS_TYPE_STAMINA] += 0.1f;
stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
stats_weights_[STATS_TYPE_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEAST_MASTERY || tab == HUNTER_TAB_SURVIVAL))
{
stats_weights_[STATS_TYPE_AGILITY] += 2.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f;
stats_weights_[STATS_TYPE_HIT] += 1.7f;
stats_weights_[STATS_TYPE_CRIT] += 1.4f;
stats_weights_[STATS_TYPE_HASTE] += 1.6f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 7.5f;
}
else if (cls == CLASS_HUNTER && tab == HUNTER_TAB_MARKSMANSHIP)
{
stats_weights_[STATS_TYPE_AGILITY] += 2.3f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.25f;
stats_weights_[STATS_TYPE_HIT] += 2.1f;
stats_weights_[STATS_TYPE_CRIT] += 2.0f;
stats_weights_[STATS_TYPE_HASTE] += 1.8f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 10.0f;
}
else if (cls == CLASS_ROGUE && tab == ROGUE_TAB_COMBAT)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.9f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.8f;
stats_weights_[STATS_TYPE_HIT] += 2.1f;
stats_weights_[STATS_TYPE_CRIT] += 1.4f;
stats_weights_[STATS_TYPE_HASTE] += 1.7f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))
{
stats_weights_[STATS_TYPE_AGILITY] += 2.2f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.4f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.3f;
stats_weights_[STATS_TYPE_HIT] += 1.9f;
stats_weights_[STATS_TYPE_CRIT] += 1.5f;
stats_weights_[STATS_TYPE_HASTE] += 2.1f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 15.0f;
}
else if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY))
{
stats_weights_[STATS_TYPE_AGILITY] += 1.5f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.2f;
stats_weights_[STATS_TYPE_HIT] += 2.1f;
stats_weights_[STATS_TYPE_CRIT] += 1.1f;
stats_weights_[STATS_TYPE_HASTE] += 1.8f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
}
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.8f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.6f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.1f;
stats_weights_[STATS_TYPE_HIT] += 2.3f;
stats_weights_[STATS_TYPE_CRIT] += 2.2f;
stats_weights_[STATS_TYPE_HASTE] += 1.8f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.3f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.7f;
stats_weights_[STATS_TYPE_HIT] += 2.0f;
stats_weights_[STATS_TYPE_CRIT] += 1.9f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.7f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.8f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.7f;
stats_weights_[STATS_TYPE_HIT] += 2.3f;
stats_weights_[STATS_TYPE_CRIT] += 2.2f;
stats_weights_[STATS_TYPE_HASTE] += 2.1f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
{
stats_weights_[STATS_TYPE_AGILITY] += 0.9f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.3f;
stats_weights_[STATS_TYPE_HIT] += 2.2f;
stats_weights_[STATS_TYPE_CRIT] += 1.7f;
stats_weights_[STATS_TYPE_HASTE] += 1.8f;
stats_weights_[STATS_TYPE_EXPERTISE] += 1.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
}
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
stats_weights_[STATS_TYPE_INTELLECT] += 0.1f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 0.3f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f;
stats_weights_[STATS_TYPE_HIT] += 1.9f;
stats_weights_[STATS_TYPE_CRIT] += 1.7f;
stats_weights_[STATS_TYPE_HASTE] += 1.6f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f;
}
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT))
{
stats_weights_[STATS_TYPE_AGILITY] += 1.4f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 0.95f;
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.9f;
stats_weights_[STATS_TYPE_HIT] += 2.1f;
stats_weights_[STATS_TYPE_CRIT] += 1.5f;
stats_weights_[STATS_TYPE_HASTE] += 1.8f;
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
}
else if (cls == CLASS_WARLOCK ||
(cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) ||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_HIT] += 1.1f;
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f;
}
else if (cls == CLASS_MAGE && tab == MAGE_TAB_FIRE)
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.7f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_HIT] += 1.2f;
stats_weights_[STATS_TYPE_CRIT] += 1.1f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f;
}
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ELEMENTAL)
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.25f;
stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_HIT] += 1.1f;
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
}
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.9f;
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
}
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) ||
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.9f;
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f;
}
else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION))
{
stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
stats_weights_[STATS_TYPE_STAMINA] += 3.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f;
stats_weights_[STATS_TYPE_DEFENSE] += 2.5f;
stats_weights_[STATS_TYPE_PARRY] += 2.0f;
stats_weights_[STATS_TYPE_DODGE] += 2.0f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f;
stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f;
stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
stats_weights_[STATS_TYPE_HIT] += 2.0f;
stats_weights_[STATS_TYPE_CRIT] += 0.2f;
stats_weights_[STATS_TYPE_HASTE] += 0.5f;
stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD)
{
stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
stats_weights_[STATS_TYPE_STAMINA] += 3.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f;
stats_weights_[STATS_TYPE_DEFENSE] += 3.5f;
stats_weights_[STATS_TYPE_PARRY] += 2.0f;
stats_weights_[STATS_TYPE_DODGE] += 2.0f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
stats_weights_[STATS_TYPE_HIT] += 2.0f;
stats_weights_[STATS_TYPE_CRIT] += 0.5f;
stats_weights_[STATS_TYPE_HASTE] += 0.5f;
stats_weights_[STATS_TYPE_EXPERTISE] += 3.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
}
else
{
// BEAR DRUID TANK
stats_weights_[STATS_TYPE_AGILITY] += 2.2f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.4f;
stats_weights_[STATS_TYPE_STAMINA] += 4.0f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_DEFENSE] += 0.3f;
stats_weights_[STATS_TYPE_DODGE] += 0.7f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
stats_weights_[STATS_TYPE_HIT] += 3.0f;
stats_weights_[STATS_TYPE_CRIT] += 1.3f;
stats_weights_[STATS_TYPE_HASTE] += 2.3f;
stats_weights_[STATS_TYPE_EXPERTISE] += 3.7f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 3.0f;
}
}
void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
{
uint8 cls = player->getClass();
// int tab = AiFactory::GetPlayerSpecTab(player);
if (cls == CLASS_HUNTER)
{
if (player->HasAura(34484))
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
if (player->HasAura(56341))
stats_weights_[STATS_TYPE_STAMINA] += 0.3f;
}
else if (cls == CLASS_WARRIOR)
{
if (player->HasAura(61222))
stats_weights_[STATS_TYPE_ARMOR] += 0.03f;
}
else if (cls == CLASS_SHAMAN)
{
if (player->HasAura(51885))
stats_weights_[STATS_TYPE_INTELLECT] += 1.1f;
}
}
void StatsWeightCalculator::CalculateItemSetMod(Player* player, ItemTemplate const* proto)
{
uint32 itemSet = proto->ItemSet;
if (!itemSet)
return;
float multiplier = 1.0f;
size_t i = 0;
for (i = 0; i < player->ItemSetEff.size(); i++)
{
if (player->ItemSetEff[i])
{
ItemSetEffect* eff = player->ItemSetEff[i];
uint32 setId = eff->setid;
if (itemSet != setId)
continue;
const ItemSetEntry* setEntry = sItemSetStore.LookupEntry(setId);
if (!setEntry)
continue;
uint32 itemCount = eff->item_count;
uint32 max_items = 0;
for (size_t j = 0; j < MAX_ITEM_SET_SPELLS; j++)
max_items = std::max(max_items, setEntry->items_to_triggerspell[j]);
if (itemCount < max_items)
{
multiplier += 0.1f * itemCount; // 10% bonus for each item already equipped
}
else
{
multiplier = 1.0f; // All item set effect has been triggerred
}
break;
}
}
if (i == player->ItemSetEff.size())
multiplier = 1.05f; // this is the first item in the item set
weight_ *= multiplier;
}
void StatsWeightCalculator::CalculateSocketBonus(Player* player, ItemTemplate const* proto)
{
uint32 socketNum = 0;
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS;
++enchant_slot)
{
uint8 socketColor = proto->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color;
if (!socketColor) // no socket slot
continue;
socketNum++;
}
float multiplier = 1.0f + socketNum * 0.03f; // 3% bonus for socket
weight_ *= multiplier;
}
void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{
// // penalty for different type armor
// if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH &&
// proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass))
// {
// weight_ *= 1.0;
// }
if (proto->Class == ITEM_CLASS_WEAPON)
{
// double hand
bool isDoubleHand = proto->Class == ITEM_CLASS_WEAPON &&
!(ITEM_SUBCLASS_MASK_SINGLE_HAND & (1 << proto->SubClass)) &&
!(ITEM_SUBCLASS_MASK_WEAPON_RANGED & (1 << proto->SubClass));
if (isDoubleHand)
{
weight_ *= 0.5;
// spec without double hand
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
{
weight_ *= 0.1;
}
}
// spec with double hand
// fury without duel wield, arms, bear, retribution, blood dk
if (!isDoubleHand)
{
if ((cls == CLASS_HUNTER && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
{
weight_ *= 0.1;
}
// caster's main hand (cannot duel weapon but can equip two-hands stuff)
if (cls == CLASS_MAGE || cls == CLASS_PRIEST || cls == CLASS_WARLOCK || cls == CLASS_DRUID ||
(cls == CLASS_SHAMAN && !player_->CanDualWield()))
{
weight_ *= 0.65;
}
}
// fury with titan's grip
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip()))
{
weight_ *= 0.1;
}
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
{
weight_ *= 0.1;
}
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
{
weight_ *= 1.5;
}
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE))
{
weight_ *= 1.1;
}
if (cls == CLASS_WARRIOR && player_->HasAura(12785) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2))
{
weight_ *= 1.1;
}
if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand)
{
weight_ *= 1.3;
}
bool slowDelay = proto->Delay > 2500;
if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay)
weight_ *= 1.1;
}
}
bool StatsWeightCalculator::NotBestArmorType(uint32 item_subclass_armor)
{
if (player_->HasSkill(SKILL_PLATE_MAIL))
{
return item_subclass_armor != ITEM_SUBCLASS_ARMOR_PLATE;
}
if (player_->HasSkill(SKILL_MAIL))
{
return item_subclass_armor != ITEM_SUBCLASS_ARMOR_MAIL;
}
if (player_->HasSkill(SKILL_LEATHER))
{
return item_subclass_armor != ITEM_SUBCLASS_ARMOR_LEATHER;
}
return false;
}
void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
{
{
float hit_current, hit_overflow;
float validPoints;
if (hitOverflowType_ & CollectorType::SPELL)
{
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE);
hit_current +=
player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
hit_current += player->GetRatingBonusValue(CR_HIT_SPELL);
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus
hit_current += 3;
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
hit_current += 3;
hit_overflow = SPELL_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL);
else
validPoints = 0;
}
else if (hitOverflowType_ & CollectorType::MELEE)
{
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_MELEE);
hit_overflow = MELEE_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_MELEE);
else
validPoints = 0;
}
else
{
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_RANGED);
hit_overflow = RANGED_HIT_OVERFLOW;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_RANGED);
else
validPoints = 0;
}
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], validPoints);
}
{
if (type_ & CollectorType::MELEE)
{
float expertise_current, expertise_overflow;
expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE);
expertise_current += player->GetRatingBonusValue(CR_EXPERTISE);
expertise_overflow = EXPERTISE_OVERFLOW;
float validPoints;
if (expertise_overflow > expertise_current)
validPoints = (expertise_overflow - expertise_current) / player->GetRatingMultiplier(CR_EXPERTISE);
else
validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], validPoints);
}
}
{
if (type_ & CollectorType::MELEE)
{
float defense_current, defense_overflow;
defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL);
defense_overflow = DEFENSE_OVERFLOW;
float validPoints;
if (defense_overflow > defense_current)
validPoints = (defense_overflow - defense_current) / player->GetRatingMultiplier(CR_DEFENSE_SKILL);
else
validPoints = 0;
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], validPoints);
}
}
{
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
{
float armor_penetration_current, armor_penetration_overflow;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
armor_penetration_overflow = ARMOR_PENETRATION_OVERFLOW;
float validPoints;
if (armor_penetration_overflow > armor_penetration_current)
validPoints = (armor_penetration_overflow - armor_penetration_current) /
player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
else
validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], validPoints);
}
}
}
void StatsWeightCalculator::ApplyWeightFinetune(Player* player)
{
{
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
{
float armor_penetration_current /*, armor_penetration_overflow*/; // not used, line marked for removal.
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
if (armor_penetration_current > 50)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_GEARSCORECALCULATOR_H
#define _PLAYERBOT_GEARSCORECALCULATOR_H
#include "Player.h"
#include "StatsCollector.h"
#define ITEM_SUBCLASS_MASK_SINGLE_HAND \
((1 << ITEM_SUBCLASS_WEAPON_AXE) | (1 << ITEM_SUBCLASS_WEAPON_MACE) | (1 << ITEM_SUBCLASS_WEAPON_SWORD) | \
(1 << ITEM_SUBCLASS_WEAPON_DAGGER) | (1 << ITEM_SUBCLASS_WEAPON_FIST))
enum StatsOverflowThreshold
{
SPELL_HIT_OVERFLOW = 14,
MELEE_HIT_OVERFLOW = 8,
RANGED_HIT_OVERFLOW = 8,
EXPERTISE_OVERFLOW = 26,
DEFENSE_OVERFLOW = 140,
ARMOR_PENETRATION_OVERFLOW = 100
};
class StatsWeightCalculator
{
public:
StatsWeightCalculator(Player* player);
void Reset();
float CalculateItem(uint32 itemId, int32 randomPropertyId = 0);
float CalculateEnchant(uint32 enchantId);
void SetOverflowPenalty(bool apply) { enable_overflow_penalty_ = apply; }
void SetItemSetBonus(bool apply) { enable_item_set_bonus_ = apply; }
void SetQualityBlend(bool apply) { enable_quality_blend_ = apply; }
private:
void GenerateWeights(Player* player);
void GenerateBasicWeights(Player* player);
void GenerateAdditionalWeights(Player* player);
void CalculateRandomProperty(int32 randomPropertyId, uint32 itemId);
void CalculateItemSetMod(Player* player, ItemTemplate const* proto);
void CalculateSocketBonus(Player* player, ItemTemplate const* proto);
void CalculateItemTypePenalty(ItemTemplate const* proto);
bool NotBestArmorType(uint32 item_subclass_armor);
void ApplyOverflowPenalty(Player* player);
void ApplyWeightFinetune(Player* player);
private:
Player* player_;
CollectorType type_;
CollectorType hitOverflowType_;
std::unique_ptr<StatsCollector> collector_;
uint8 cls;
uint8 lvl;
int tab;
bool enable_overflow_penalty_;
bool enable_item_set_bonus_;
bool enable_quality_blend_;
float weight_;
float stats_weights_[STATS_TYPE_MAX];
};
#endif

View File

@@ -0,0 +1,197 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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

View File

@@ -0,0 +1,290 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "PlayerbotSecurity.h"
#include "LFGMgr.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
{
if (bot)
account = sCharacterCache->GetCharacterAccountIdByGuid(bot->GetGUID());
}
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
{
// Basic pointer validity checks
if (!bot || !from || !from->GetSession())
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
// GMs always have full access
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
return PLAYERBOT_SECURITY_ALLOW_ALL;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
if (botAI->IsOpposing(from))
{
if (reason)
*reason = PLAYERBOT_DENY_OPPOSING;
return PLAYERBOT_SECURITY_DENY_ALL;
}
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
{
// (duplicate check in case of faction change)
if (botAI->IsOpposing(from))
{
if (reason)
*reason = PLAYERBOT_DENY_OPPOSING;
return PLAYERBOT_SECURITY_DENY_ALL;
}
Group* fromGroup = from->GetGroup();
Group* botGroup = bot->GetGroup();
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
{
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
if (reason)
*reason = PLAYERBOT_DENY_NOT_YOURS;
return PLAYERBOT_SECURITY_TALK;
}
if (sPlayerbotAIConfig->groupInvitationPermission <= 0)
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_TALK;
}
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
{
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
if (levelDiff > 5)
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
return PLAYERBOT_SECURITY_TALK;
}
}
}
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
{
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
if (diffPct >= reqPct)
{
if (reason)
*reason = PLAYERBOT_DENY_GEARSCORE;
return PLAYERBOT_SECURITY_TALK;
}
}
if (bot->InBattlegroundQueue())
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_BG;
return PLAYERBOT_SECURITY_TALK;
}
}
// If the bot is not in the group, we offer an invite
botGroup = bot->GetGroup();
if (!botGroup)
{
if (reason)
*reason = PLAYERBOT_DENY_INVITE;
return PLAYERBOT_SECURITY_INVITE;
}
if (!ignoreGroup && botGroup->IsFull())
{
if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP;
return PLAYERBOT_SECURITY_TALK;
}
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER;
return PLAYERBOT_SECURITY_TALK;
}
// The bot is the group leader, you can invite the initiator
if (reason)
*reason = PLAYERBOT_DENY_IS_LEADER;
return PLAYERBOT_SECURITY_INVITE;
}
// Non-random bots: only their master has full access
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
if (reason)
*reason = PLAYERBOT_DENY_NOT_YOURS;
return PLAYERBOT_SECURITY_INVITE;
}
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
{
// If something is wrong with the pointers, we silently refuse
if (!bot || !from || !from->GetSession())
return false;
DenyReason reason = PLAYERBOT_DENY_NONE;
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
if (realLevel >= level || from == bot)
return true;
PlayerbotAI* fromBotAI = GET_PLAYERBOT_AI(from);
if (silent || (fromBotAI && !fromBotAI->IsRealPlayer()))
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return false;
Player* master = botAI->GetMaster();
if (master && botAI->IsOpposing(master))
if (WorldSession* session = master->GetSession())
if (session->GetSecurity() < SEC_GAMEMASTER)
return false;
std::ostringstream out;
switch (realLevel)
{
case PLAYERBOT_SECURITY_DENY_ALL:
out << "I'm kind of busy now";
break;
case PLAYERBOT_SECURITY_TALK:
switch (reason)
{
case PLAYERBOT_DENY_NONE:
out << "I'll do it later";
break;
case PLAYERBOT_DENY_LOW_LEVEL:
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
<< uint32(bot->GetLevel());
break;
case PLAYERBOT_DENY_GEARSCORE:
{
int botGS = int(botAI->GetEquipGearScore(bot));
int fromGS = int(botAI->GetEquipGearScore(from));
int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
break;
}
case PLAYERBOT_DENY_NOT_YOURS:
out << "I have a master already";
break;
case PLAYERBOT_DENY_IS_BOT:
out << "You are a bot";
break;
case PLAYERBOT_DENY_OPPOSING:
out << "You are the enemy";
break;
case PLAYERBOT_DENY_DEAD:
out << "I'm dead. Will do it later";
break;
case PLAYERBOT_DENY_INVITE:
out << "Invite me to your group first";
break;
case PLAYERBOT_DENY_FAR:
{
out << "You must be closer to invite me to your group. I am in ";
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
break;
}
case PLAYERBOT_DENY_FULL_GROUP:
out << "I am in a full group. Will do it later";
break;
case PLAYERBOT_DENY_IS_LEADER:
out << "I am currently leading a group. I can invite you if you want.";
break;
case PLAYERBOT_DENY_NOT_LEADER:
if (Player* leader = botAI->GetGroupLeader())
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
else
out << "I am in a group with someone else. You can ask him for invite.";
break;
case PLAYERBOT_DENY_BG:
out << "I am in a queue for BG. Will do it later";
break;
case PLAYERBOT_DENY_LFG:
out << "I am in a queue for dungeon. Will do it later";
break;
default:
out << "I can't do that";
break;
}
break;
case PLAYERBOT_SECURITY_INVITE:
out << "Invite me to your group first";
break;
default:
out << "I can't do that";
break;
}
std::string const text = out.str();
ObjectGuid guid = from->GetGUID();
time_t lastSaid = whispers[guid][text];
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{
whispers[guid][text] = time(nullptr);
// Additional protection against crashes during logout
if (bot->IsInWorld() && from->IsInWorld())
bot->Whisper(text, LANG_UNIVERSAL, from);
}
return false;
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLAYERBOTSECURITY_H
#define _PLAYERBOT_PLAYERBOTSECURITY_H
#include <map>
#include "Common.h"
#include "ObjectGuid.h"
class Player;
enum PlayerbotSecurityLevel : uint32
{
PLAYERBOT_SECURITY_DENY_ALL = 0,
PLAYERBOT_SECURITY_TALK = 1,
PLAYERBOT_SECURITY_INVITE = 2,
PLAYERBOT_SECURITY_ALLOW_ALL = 3
};
enum DenyReason
{
PLAYERBOT_DENY_NONE,
PLAYERBOT_DENY_LOW_LEVEL,
PLAYERBOT_DENY_GEARSCORE,
PLAYERBOT_DENY_NOT_YOURS,
PLAYERBOT_DENY_IS_BOT,
PLAYERBOT_DENY_OPPOSING,
PLAYERBOT_DENY_DEAD,
PLAYERBOT_DENY_FAR,
PLAYERBOT_DENY_INVITE,
PLAYERBOT_DENY_FULL_GROUP,
PLAYERBOT_DENY_NOT_LEADER,
PLAYERBOT_DENY_IS_LEADER,
PLAYERBOT_DENY_BG,
PLAYERBOT_DENY_LFG
};
class PlayerbotSecurity
{
public:
PlayerbotSecurity(Player* const bot);
PlayerbotSecurityLevel LevelFor(Player* from, DenyReason* reason = nullptr, bool ignoreGroup = false);
bool CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup = false);
private:
Player* const bot;
uint32 account;
std::map<ObjectGuid, std::map<std::string, time_t> > whispers;
};
#endif

View File

@@ -0,0 +1,505 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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/Mgr/Talent/Talentspec.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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;
}
std::string PlayerbotTextMgr::GetBotTextOrDefault(std::string name, std::string defaultText,
std::map<std::string, std::string> placeholders)
{
std::string botText = GetBotText(name, placeholders);
if (botText.empty())
{
for (std::map<std::string, std::string>::iterator i = placeholders.begin(); i != placeholders.end(); ++i)
{
replaceAll(defaultText, i->first, i->second);
}
return defaultText;
}
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 >= MAX_LOCALES)
{
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
return;
}
botTextLocalePriority[locale]++;
}
uint32 PlayerbotTextMgr::GetLocalePriority()
{
// if no real players online, reset top locale
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
if (!activeSessions)
{
ResetLocalePriority();
return 0;
}
uint32 topLocale = 0;
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
topLocale = i;
}
return topLocale;
}
void PlayerbotTextMgr::ResetLocalePriority()
{
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
botTextLocalePriority[i] = 0;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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);
std::string GetBotTextOrDefault(std::string name, std::string defaultText,
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

4300
src/Mgr/Travel/TravelMgr.cpp Normal file

File diff suppressed because it is too large Load Diff

946
src/Mgr/Travel/TravelMgr.h Normal file
View File

@@ -0,0 +1,946 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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();
bool IsValid();
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

File diff suppressed because it is too large Load Diff

600
src/Mgr/Travel/TravelNode.h Normal file
View File

@@ -0,0 +1,600 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 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