mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-27 05:45:55 +00:00
[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)
This commit is contained in:
1258
src/Mgr/Guild/GuildTaskMgr.cpp
Normal file
1258
src/Mgr/Guild/GuildTaskMgr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
62
src/Mgr/Guild/GuildTaskMgr.h
Normal file
62
src/Mgr/Guild/GuildTaskMgr.h
Normal 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
|
||||
322
src/Mgr/Guild/PlayerbotGuildMgr.cpp
Normal file
322
src/Mgr/Guild/PlayerbotGuildMgr.cpp
Normal 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();
|
||||
}
|
||||
52
src/Mgr/Guild/PlayerbotGuildMgr.h
Normal file
52
src/Mgr/Guild/PlayerbotGuildMgr.h
Normal 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
|
||||
95
src/Mgr/Item/ItemVisitors.cpp
Normal file
95
src/Mgr/Item/ItemVisitors.cpp
Normal 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
432
src/Mgr/Item/ItemVisitors.h
Normal 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
|
||||
411
src/Mgr/Item/LootObjectStack.cpp
Normal file
411
src/Mgr/Item/LootObjectStack.cpp
Normal 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 doesn’t 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;
|
||||
}
|
||||
87
src/Mgr/Item/LootObjectStack.h
Normal file
87
src/Mgr/Item/LootObjectStack.h
Normal 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
|
||||
2905
src/Mgr/Item/RandomItemMgr.cpp
Normal file
2905
src/Mgr/Item/RandomItemMgr.cpp
Normal file
File diff suppressed because it is too large
Load Diff
215
src/Mgr/Item/RandomItemMgr.h
Normal file
215
src/Mgr/Item/RandomItemMgr.h
Normal 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
|
||||
838
src/Mgr/Item/StatsCollector.cpp
Normal file
838
src/Mgr/Item/StatsCollector.cpp
Normal 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;
|
||||
}
|
||||
90
src/Mgr/Item/StatsCollector.h
Normal file
90
src/Mgr/Item/StatsCollector.h
Normal 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
|
||||
740
src/Mgr/Item/StatsWeightCalculator.cpp
Normal file
740
src/Mgr/Item/StatsWeightCalculator.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/Mgr/Item/StatsWeightCalculator.h
Normal file
70
src/Mgr/Item/StatsWeightCalculator.h
Normal 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
|
||||
197
src/Mgr/Move/FleeManager.cpp
Normal file
197
src/Mgr/Move/FleeManager.cpp
Normal 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;
|
||||
}
|
||||
59
src/Mgr/Move/FleeManager.h
Normal file
59
src/Mgr/Move/FleeManager.h
Normal 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
|
||||
290
src/Mgr/Security/PlayerbotSecurity.cpp
Normal file
290
src/Mgr/Security/PlayerbotSecurity.cpp
Normal 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;
|
||||
}
|
||||
56
src/Mgr/Security/PlayerbotSecurity.h
Normal file
56
src/Mgr/Security/PlayerbotSecurity.h
Normal 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
|
||||
505
src/Mgr/Talent/Talentspec.cpp
Normal file
505
src/Mgr/Talent/Talentspec.cpp
Normal 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
107
src/Mgr/Talent/Talentspec.h
Normal 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
|
||||
228
src/Mgr/Text/PlayerbotTextMgr.cpp
Normal file
228
src/Mgr/Text/PlayerbotTextMgr.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
105
src/Mgr/Text/PlayerbotTextMgr.h
Normal file
105
src/Mgr/Text/PlayerbotTextMgr.h
Normal 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
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
946
src/Mgr/Travel/TravelMgr.h
Normal 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(¢er); }
|
||||
|
||||
float fDist(WorldPosition* center);
|
||||
float fDist(WorldPosition center) { return fDist(¢er); }
|
||||
|
||||
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
|
||||
2588
src/Mgr/Travel/TravelNode.cpp
Normal file
2588
src/Mgr/Travel/TravelNode.cpp
Normal file
File diff suppressed because it is too large
Load Diff
600
src/Mgr/Travel/TravelNode.h
Normal file
600
src/Mgr/Travel/TravelNode.h
Normal 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
|
||||
Reference in New Issue
Block a user