mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Compare commits
185 Commits
directory_
...
hermensbas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c191edf280 | ||
|
|
8d51092d42 | ||
|
|
3fff58df1a | ||
|
|
ca2e2ef0db | ||
|
|
4e3ac609bd | ||
|
|
c6b0424c29 | ||
|
|
2e0a161623 | ||
|
|
e4ea8e2694 | ||
|
|
ddfa919154 | ||
|
|
380312ffd2 | ||
|
|
872e417613 | ||
|
|
3d28a81508 | ||
|
|
bcd6f5bc06 | ||
|
|
15f138aab0 | ||
|
|
5759a98d5a | ||
|
|
13fca4398d | ||
|
|
a307eb2f08 | ||
|
|
966bf1d6af | ||
|
|
f5ef5bd1c2 | ||
|
|
0afcf29490 | ||
|
|
a6c07ca16d | ||
|
|
ee99b66d04 | ||
|
|
ede7697784 | ||
|
|
ba9cb5a256 | ||
|
|
a1dd6f6fc5 | ||
|
|
e950f65a83 | ||
|
|
938872564a | ||
|
|
baa1aa9e9d | ||
|
|
65a3bf481c | ||
|
|
989b48f491 | ||
|
|
40874624a8 | ||
|
|
f76435b4c4 | ||
|
|
df3c44419d | ||
|
|
b7b8c60d17 | ||
|
|
e5f1446b9f | ||
|
|
e92029dd6e | ||
|
|
b8c0a54f92 | ||
|
|
18d1821dab | ||
|
|
8a8571c54f | ||
|
|
21bcbece7a | ||
|
|
e40c2b21f2 | ||
|
|
8a9a833c98 | ||
|
|
101c7f3046 | ||
|
|
d8d94f33ee | ||
|
|
eef2e8c1ef | ||
|
|
a675e74d97 | ||
|
|
de9f8fbbea | ||
|
|
66b326d79e | ||
|
|
0d8e8fbd61 | ||
|
|
66c88d4815 | ||
|
|
4c3906c243 | ||
|
|
64b09fd3ca | ||
|
|
db7a17ffde | ||
|
|
1e33b28abe | ||
|
|
e59bad26c4 | ||
|
|
a632fa2194 | ||
|
|
c6005449e0 | ||
|
|
2aca50c1c7 | ||
|
|
179e3bbf71 | ||
|
|
00b03bd29d | ||
|
|
e62da73706 | ||
|
|
5108f709c7 | ||
|
|
2beee4aec9 | ||
|
|
1e128ea24f | ||
|
|
e64da42f87 | ||
|
|
e0ef04e1b9 | ||
|
|
c5c1274d3c | ||
|
|
849b21f916 | ||
|
|
4c9e4e7b0f | ||
|
|
961629f4ce | ||
|
|
1801d7c314 | ||
|
|
237c0cffc4 | ||
|
|
ee245f73b5 | ||
|
|
c04477b54d | ||
|
|
b65646170c | ||
|
|
55a37c48eb | ||
|
|
a33bb3b51e | ||
|
|
feda619066 | ||
|
|
564bb198fb | ||
|
|
5ac6e8751c | ||
|
|
7d7edbd961 | ||
|
|
4a00c954ed | ||
|
|
e150b8281b | ||
|
|
0b03b277c2 | ||
|
|
d6b7693b8b | ||
|
|
551c698e37 | ||
|
|
45694ad6e6 | ||
|
|
761ef634da | ||
|
|
96cc0daea6 | ||
|
|
5202ac8db3 | ||
|
|
fa79fff4f4 | ||
|
|
6d07d6febe | ||
|
|
8ca4ab1344 | ||
|
|
bc5d602326 | ||
|
|
3611cfbdd3 | ||
|
|
0400c1c8b3 | ||
|
|
393a65a15b | ||
|
|
8b9bcce3bc | ||
|
|
8ed053ca01 | ||
|
|
51ed9c4649 | ||
|
|
86390f90fd | ||
|
|
3f39a57fe2 | ||
|
|
102aa24bb8 | ||
|
|
50792e5646 | ||
|
|
78f4bd6d29 | ||
|
|
b558e86df0 | ||
|
|
f0c6aaff0b | ||
|
|
1a20d549fe | ||
|
|
64df7439d8 | ||
|
|
05a3c318d6 | ||
|
|
1c69490290 | ||
|
|
8fd188ff3b | ||
|
|
7fa6e5833a | ||
|
|
305f769a84 | ||
|
|
c0aa55416b | ||
|
|
59af34809c | ||
|
|
326783ed4f | ||
|
|
9503d32d46 | ||
|
|
edc9241fa3 | ||
|
|
40535f6e55 | ||
|
|
9a980c171e | ||
|
|
ce91c335b3 | ||
|
|
605fa223ce | ||
|
|
309d177dd8 | ||
|
|
36fd5b8f15 | ||
|
|
720c063716 | ||
|
|
57a2c9a742 | ||
|
|
3f7814abb4 | ||
|
|
3dd0f11453 | ||
|
|
00cc2468f1 | ||
|
|
cf8253579e | ||
|
|
2000c06167 | ||
|
|
453925153f | ||
|
|
9a10f07c74 | ||
|
|
7d6f44ab09 | ||
|
|
bb004825aa | ||
|
|
097bd00f38 | ||
|
|
290bf50faf | ||
|
|
e739f7820b | ||
|
|
6fbaf6510b | ||
|
|
499a80db14 | ||
|
|
dfa87faf5e | ||
|
|
d15ec79252 | ||
|
|
cfc8e85706 | ||
|
|
fc6309c521 | ||
|
|
9986469042 | ||
|
|
db9bcb97ba | ||
|
|
3c05e47cb2 | ||
|
|
89556dafa1 | ||
|
|
b726f3dfcb | ||
|
|
45f98e52b4 | ||
|
|
f8660bc939 | ||
|
|
c24b7a7bb3 | ||
|
|
1195e67c97 | ||
|
|
faee49beaa | ||
|
|
d87d5a46c7 | ||
|
|
c34617e133 | ||
|
|
a4ff66f12a | ||
|
|
e68c5a76c6 | ||
|
|
5910866362 | ||
|
|
c5b185455c | ||
|
|
e1a8bd66c5 | ||
|
|
f7bd9ae5c1 | ||
|
|
b5426152fb | ||
|
|
f3ee83d471 | ||
|
|
b2534691b8 | ||
|
|
36adb62f2a | ||
|
|
fd99b373c2 | ||
|
|
e2b5ab766d | ||
|
|
9641092078 | ||
|
|
aaad67f7b9 | ||
|
|
7901f5da19 | ||
|
|
4f26a8a09b | ||
|
|
f17ec36cde | ||
|
|
3881940f33 | ||
|
|
dc703cc897 | ||
|
|
97f582b9b1 | ||
|
|
95c572bf48 | ||
|
|
43b0852438 | ||
|
|
9ca326c8cb | ||
|
|
f365b79e96 | ||
|
|
e104c5f8be | ||
|
|
0c6f656236 | ||
|
|
4603dbaf35 | ||
|
|
e48c3351d3 |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
DROP TABLE IF EXISTS `playerbot_account_keys`;
|
||||
DROP TABLE IF EXISTS `playerbots_account_keys`;
|
||||
|
||||
CREATE TABLE `playerbot_account_keys` (
|
||||
CREATE TABLE `playerbots_account_keys` (
|
||||
`account_id` INT PRIMARY KEY,
|
||||
`security_key` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -1,6 +1,6 @@
|
||||
DROP TABLE IF EXISTS `playerbot_account_links`;
|
||||
DROP TABLE IF EXISTS `playerbots_account_links`;
|
||||
|
||||
CREATE TABLE `playerbot_account_links` (
|
||||
CREATE TABLE `playerbots_account_links` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`account_id` INT NOT NULL,
|
||||
`linked_account_id` INT NOT NULL,
|
||||
|
||||
8
data/sql/playerbots/base/playerbots_account_type.sql
Normal file
8
data/sql/playerbots/base/playerbots_account_type.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
DROP TABLE IF EXISTS `playerbots_account_type`;
|
||||
CREATE TABLE `playerbots_account_type` (
|
||||
`account_id` int unsigned NOT NULL,
|
||||
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
|
||||
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`account_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
15
data/sql/playerbots/updates/db_playerbots/2025_05_09_00.sql
Normal file
15
data/sql/playerbots/updates/db_playerbots/2025_05_09_00.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
DROP TABLE IF EXISTS `playerbot_account_keys`;
|
||||
CREATE TABLE IF NOT EXISTS `playerbots_account_keys` (
|
||||
`account_id` INT PRIMARY KEY,
|
||||
`security_key` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=INNODB DEFAULT CHARSET=latin1;
|
||||
|
||||
DROP TABLE IF EXISTS `playerbot_account_links`;
|
||||
CREATE TABLE IF NOT EXISTS `playerbots_account_links` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`account_id` INT NOT NULL,
|
||||
`linked_account_id` INT NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
|
||||
) ENGINE=INNODB DEFAULT CHARSET=latin1;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Create playerbots_account_type table for tracking accounts assignments
|
||||
DROP TABLE IF EXISTS `playerbots_account_type`;
|
||||
CREATE TABLE `playerbots_account_type` (
|
||||
`account_id` int unsigned NOT NULL,
|
||||
`account_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 = unassigned, 1 = RNDbot, 2 = AddClass',
|
||||
`assignment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`account_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Playerbot account type assignments';
|
||||
|
||||
@@ -80,13 +80,16 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_MAGE:
|
||||
tab = 1;
|
||||
tab = MAGE_TAB_FROST;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
tab = 2;
|
||||
tab = PALADIN_TAB_RETRIBUTION;
|
||||
break;
|
||||
case CLASS_PRIEST:
|
||||
tab = 1;
|
||||
tab = PRIEST_TAB_HOLY;
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
tab = WARLOCK_TAB_DEMONOLOGY;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -302,22 +305,22 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0)
|
||||
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
|
||||
engine->addStrategiesNoInit("arcane", nullptr);
|
||||
else if (tab == 1)
|
||||
{
|
||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||
{
|
||||
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
|
||||
engine->addStrategiesNoInit("frostfire", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
|
||||
engine->addStrategiesNoInit("fire", nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
engine->addStrategiesNoInit("frost", "frost aoe", nullptr);
|
||||
engine->addStrategiesNoInit("frost", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", nullptr);
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_WARRIOR:
|
||||
if (tab == 2)
|
||||
@@ -367,12 +370,14 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr);
|
||||
engine->addStrategy("dps debuff", false);
|
||||
// if (tab == HUNTER_TAB_SURVIVAL)
|
||||
// {
|
||||
// engine->addStrategy("trap weave", false);
|
||||
// }
|
||||
if (tab == 0) // Beast Mastery
|
||||
engine->addStrategiesNoInit("bm", nullptr);
|
||||
else if (tab == 1) // Marksmanship
|
||||
engine->addStrategiesNoInit("mm", nullptr);
|
||||
else if (tab == 2) // Survival
|
||||
engine->addStrategiesNoInit("surv", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_ROGUE:
|
||||
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
||||
@@ -385,8 +390,16 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
}
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
engine->addStrategiesNoInit("dps assist", "dps", "dps debuff", "aoe", nullptr);
|
||||
if (tab == 0) // Affliction
|
||||
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
||||
else if (tab == 1) // Demonology
|
||||
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
||||
else if (tab == 2) // Destruction
|
||||
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||
break;
|
||||
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
|
||||
@@ -407,7 +420,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
{
|
||||
if (sPlayerbotAIConfig->autoSaveMana)
|
||||
engine->addStrategy("save mana", false);
|
||||
engine->addStrategy("healer dps", false);
|
||||
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
|
||||
engine->addStrategy("healer dps", false);
|
||||
}
|
||||
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
@@ -531,7 +545,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
|
||||
if (player->GetLevel() >= 20)
|
||||
{
|
||||
nonCombatEngine->addStrategy("bstats", false);
|
||||
nonCombatEngine->addStrategy("bhealth", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -588,17 +602,17 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
||||
case CLASS_WARLOCK:
|
||||
if (tab == WARLOCK_TAB_AFFLICATION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bmana", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bdps", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
|
||||
}
|
||||
else if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||
{
|
||||
nonCombatEngine->addStrategiesNoInit("bhealth", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
|
||||
}
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", nullptr);
|
||||
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (tab == 0)
|
||||
@@ -305,6 +305,49 @@ ItemIds ChatHelper::parseItems(std::string const text)
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
ItemWithRandomProperty ChatHelper::parseItemWithRandomProperty(std::string const text)
|
||||
{
|
||||
ItemWithRandomProperty res;
|
||||
|
||||
size_t itemStart = text.find("Hitem:");
|
||||
if (itemStart == std::string::npos)
|
||||
return res;
|
||||
|
||||
itemStart += 6;
|
||||
if (itemStart >= text.length())
|
||||
return res;
|
||||
|
||||
size_t colonPos = text.find(':', itemStart);
|
||||
if (colonPos == std::string::npos)
|
||||
return res;
|
||||
|
||||
std::string itemIdStr = text.substr(itemStart, colonPos - itemStart);
|
||||
res.itemId = atoi(itemIdStr.c_str());
|
||||
|
||||
std::vector<std::string> params;
|
||||
size_t currentPos = colonPos + 1;
|
||||
|
||||
while (currentPos < text.length()) {
|
||||
size_t nextColon = text.find(':', currentPos);
|
||||
if (nextColon == std::string::npos) {
|
||||
size_t hTag = text.find("|h", currentPos);
|
||||
if (hTag != std::string::npos) {
|
||||
params.push_back(text.substr(currentPos, hTag - currentPos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
params.push_back(text.substr(currentPos, nextColon - currentPos));
|
||||
currentPos = nextColon + 1;
|
||||
}
|
||||
|
||||
if (params.size() >= 6) {
|
||||
res.randomPropertyId = atoi(params[5].c_str());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string const ChatHelper::FormatQuest(Quest const* quest)
|
||||
{
|
||||
if (!quest)
|
||||
@@ -382,7 +425,7 @@ std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo)
|
||||
std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count, uint32 total)
|
||||
{
|
||||
char color[32];
|
||||
sprintf(color, "%x", ItemQualityColors[proto->Quality]);
|
||||
snprintf(color, sizeof(color), "%x", ItemQualityColors[proto->Quality]);
|
||||
|
||||
std::string itemName;
|
||||
const ItemLocale* locale = sObjectMgr->GetItemLocale(proto->ItemId);
|
||||
@@ -409,7 +452,7 @@ std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count
|
||||
std::string const ChatHelper::FormatQItem(uint32 itemId)
|
||||
{
|
||||
char color[32];
|
||||
sprintf(color, "%x", ItemQualityColors[0]);
|
||||
snprintf(color, sizeof(color), "%x", ItemQualityColors[0]);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "|c" << color << "|Hitem:" << itemId << ":0:0:0:0:0:0:0"
|
||||
@@ -25,6 +25,11 @@ struct ItemTemplate;
|
||||
typedef std::set<uint32> ItemIds;
|
||||
typedef std::set<uint32> SpellIds;
|
||||
|
||||
struct ItemWithRandomProperty {
|
||||
uint32 itemId{0};
|
||||
int32 randomPropertyId{0};
|
||||
};
|
||||
|
||||
class ChatHelper : public PlayerbotAIAware
|
||||
{
|
||||
public:
|
||||
@@ -33,6 +38,7 @@ public:
|
||||
static std::string const formatMoney(uint32 copper);
|
||||
static uint32 parseMoney(std::string const text);
|
||||
static ItemIds parseItems(std::string const text);
|
||||
static ItemWithRandomProperty parseItemWithRandomProperty(std::string const text);
|
||||
uint32 parseSpell(std::string const text);
|
||||
static std::string parseValue(const std::string& type, const std::string& text);
|
||||
|
||||
@@ -525,6 +525,11 @@ uint32 GuildTaskMgr::GetMaxItemTaskCount(uint32 itemId)
|
||||
|
||||
bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 value = 0;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
@@ -548,6 +553,11 @@ bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
|
||||
std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string const type,
|
||||
[[maybe_unused]] uint32* validIn /* = nullptr */)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return std::map<uint32, uint32>();
|
||||
}
|
||||
|
||||
std::map<uint32, uint32> results;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
@@ -576,6 +586,11 @@ std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string c
|
||||
|
||||
uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string const type, [[maybe_unused]] uint32* validIn /* = nullptr */)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->guildTaskEnabled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 value = 0;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "LootObjectStack.h"
|
||||
|
||||
#include "LootMgr.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Unit.h"
|
||||
|
||||
@@ -85,7 +87,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
|
||||
bool hasAnyQuestItems = false;
|
||||
|
||||
GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry());
|
||||
for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
|
||||
for (size_t i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
|
||||
{
|
||||
if (!items || i >= items->size())
|
||||
break;
|
||||
@@ -287,7 +289,7 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
if (reqItem && !bot->HasItemCount(reqItem, 1))
|
||||
return false;
|
||||
|
||||
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f)
|
||||
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE - 2.0f)
|
||||
return false;
|
||||
|
||||
Creature* creature = botAI->GetCreature(guid);
|
||||
@@ -297,6 +299,11 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent bot from running to chests that are unlootable (e.g. Gunship Armory before completing the event)
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND | GO_FLAG_NOT_SELECTABLE))
|
||||
return false;
|
||||
|
||||
if (skillId == SKILL_NONE)
|
||||
return true;
|
||||
|
||||
@@ -312,29 +319,17 @@ bool LootObject::IsLootPossible(Player* bot)
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
}
|
||||
@@ -371,42 +366,45 @@ void LootObjectStack::Clear() { availableLoot.clear(); }
|
||||
|
||||
bool LootObjectStack::CanLoot(float maxDistance)
|
||||
{
|
||||
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
|
||||
return !ordered.empty();
|
||||
LootObject nearest = GetNearest(maxDistance);
|
||||
return !nearest.IsEmpty();
|
||||
}
|
||||
|
||||
LootObject LootObjectStack::GetLoot(float maxDistance)
|
||||
{
|
||||
std::vector<LootObject> ordered = OrderByDistance(maxDistance);
|
||||
return ordered.empty() ? LootObject() : *ordered.begin();
|
||||
LootObject nearest = GetNearest(maxDistance);
|
||||
return nearest.IsEmpty() ? LootObject() : nearest;
|
||||
}
|
||||
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
|
||||
|
||||
LootObject LootObjectStack::GetNearest(float maxDistance)
|
||||
{
|
||||
availableLoot.shrink(time(nullptr) - 30);
|
||||
|
||||
std::map<float, LootObject> sortedMap;
|
||||
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;
|
||||
LootObject lootObject(bot, guid);
|
||||
if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid
|
||||
continue;
|
||||
|
||||
WorldObject* worldObj = lootObject.GetWorldObject(bot);
|
||||
if (!worldObj) // Prevent null pointer dereference
|
||||
{
|
||||
WorldObject* worldObj = ObjectAccessor::GetWorldObject(*bot, guid);
|
||||
if (!worldObj)
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = bot->GetDistance(worldObj);
|
||||
if (!maxDistance || distance <= maxDistance)
|
||||
sortedMap[distance] = lootObject;
|
||||
|
||||
if (distance >= nearestDistance || (maxDistance && distance > maxDistance))
|
||||
continue;
|
||||
|
||||
LootObject lootObject(bot, guid);
|
||||
|
||||
if (!lootObject.IsLootPossible(bot))
|
||||
continue;
|
||||
|
||||
nearestDistance = distance;
|
||||
nearest = lootObject;
|
||||
}
|
||||
|
||||
std::vector<LootObject> result;
|
||||
for (auto& [_, lootObject] : sortedMap)
|
||||
result.push_back(lootObject);
|
||||
|
||||
return result;
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
@@ -29,6 +29,7 @@ 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);
|
||||
@@ -77,7 +78,7 @@ public:
|
||||
LootObject GetLoot(float maxDistance = 0);
|
||||
|
||||
private:
|
||||
std::vector<LootObject> OrderByDistance(float maxDistance = 0);
|
||||
LootObject GetNearest(float maxDistance = 0);
|
||||
|
||||
Player* bot;
|
||||
LootTargetList availableLoot;
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "CreatureData.h"
|
||||
#include "EmoteAction.h"
|
||||
#include "Engine.h"
|
||||
#include "EventProcessor.h"
|
||||
#include "ExternalEventHelper.h"
|
||||
#include "GameObjectData.h"
|
||||
#include "GameTime.h"
|
||||
@@ -156,7 +157,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_AREATRIGGER, "area trigger");
|
||||
// masterIncomingPacketHandlers.AddHandler(CMSG_GAMEOBJ_USE, "use game object");
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll");
|
||||
// masterIncomingPacketHandlers.AddHandler(CMSG_LOOT_ROLL, "loot roll");
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_GOSSIP_HELLO, "gossip hello");
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_QUESTGIVER_HELLO, "gossip hello");
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_ACTIVATETAXI, "activate taxi");
|
||||
@@ -211,7 +212,8 @@ PlayerbotAI::PlayerbotAI(Player* bot)
|
||||
masterIncomingPacketHandlers.AddHandler(CMSG_PUSHQUESTTOPARTY, "quest share");
|
||||
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_COMPLETE, "quest update complete");
|
||||
botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_KILL, "quest update add kill");
|
||||
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item"); // SMSG_QUESTUPDATE_ADD_ITEM no longer used
|
||||
// SMSG_QUESTUPDATE_ADD_ITEM no longer used
|
||||
// botOutgoingPacketHandlers.AddHandler(SMSG_QUESTUPDATE_ADD_ITEM, "quest update add item");
|
||||
botOutgoingPacketHandlers.AddHandler(SMSG_QUEST_CONFIRM_ACCEPT, "confirm quest");
|
||||
}
|
||||
|
||||
@@ -720,6 +722,7 @@ void PlayerbotAI::HandleTeleportAck()
|
||||
// SetNextCheckDelay(urand(2000, 5000));
|
||||
if (sPlayerbotAIConfig->applyInstanceStrategies)
|
||||
ApplyInstanceStrategies(bot->GetMapId(), true);
|
||||
EvaluateHealerDpsStrategy();
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
@@ -796,6 +799,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text)
|
||||
unsecuredCommands.insert("sendmail");
|
||||
unsecuredCommands.insert("invite");
|
||||
unsecuredCommands.insert("leave");
|
||||
unsecuredCommands.insert("lfg");
|
||||
unsecuredCommands.insert("rpg status");
|
||||
}
|
||||
|
||||
@@ -1046,6 +1050,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (chanName == "World")
|
||||
return;
|
||||
|
||||
// do not reply to self but always try to reply to real player
|
||||
if (guid1 != bot->GetGUID())
|
||||
@@ -1289,11 +1296,6 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
return;
|
||||
}
|
||||
|
||||
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
{
|
||||
SetNextCheckDelay(sPlayerbotAIConfig->passiveDelay);
|
||||
return;
|
||||
}
|
||||
|
||||
// Change engine if just died
|
||||
bool isBotAlive = bot->IsAlive();
|
||||
@@ -1423,12 +1425,21 @@ void PlayerbotAI::DoNextAction(bool min)
|
||||
master = newMaster;
|
||||
botAI->SetMaster(newMaster);
|
||||
botAI->ResetStrategies();
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
if (!bot->InBattleground())
|
||||
{
|
||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||
|
||||
if (botAI->GetMaster() == botAI->GetGroupMaster())
|
||||
botAI->TellMaster("Hello, I follow you!");
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
}
|
||||
else
|
||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
||||
{
|
||||
// we're in a battleground, stay with the pack and focus on objective
|
||||
botAI->ChangeStrategy("-follow", BOT_STATE_NON_COMBAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2347,7 +2358,6 @@ std::string PlayerbotAI::GetLocalizedCreatureName(uint32 entry)
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
|
||||
{
|
||||
std::string name;
|
||||
@@ -2926,6 +2936,18 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((bot->GetShapeshiftForm() == FORM_FLIGHT || bot->GetShapeshiftForm() == FORM_FLIGHT_EPIC) && !bot->IsInCombat())
|
||||
{
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
{
|
||||
LOG_DEBUG(
|
||||
"playerbots",
|
||||
"Can cast spell failed. In flight form (not in combat). - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration();
|
||||
// bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT;
|
||||
if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving())
|
||||
@@ -2940,14 +2962,19 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
|
||||
|
||||
if (!itemTarget)
|
||||
{
|
||||
// Exception for Deep Freeze (44572) - allow cast for damage on immune targets (e.g., bosses)
|
||||
if (target->IsImmunedToSpell(spellInfo))
|
||||
{
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
if (spellid != 44572) // Deep Freeze
|
||||
{
|
||||
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster()))
|
||||
{
|
||||
LOG_DEBUG("playerbots", "target is immuned to spell - target name: {}, spellid: {}, bot name: {}",
|
||||
target->GetName(), spellid, bot->GetName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
// Otherwise, allow Deep Freeze even if immune
|
||||
}
|
||||
|
||||
if (bot != target && sServerFacade->GetDistance2d(bot, target) > sPlayerbotAIConfig->sightDistance)
|
||||
@@ -3143,22 +3170,41 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
if (pet && pet->HasSpell(spellId))
|
||||
{
|
||||
bool autocast = false;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
|
||||
// We are excluding Spell Lock and Devour Magic because they are casted in the GenericWarlockStrategy
|
||||
// Without this exclusion, the skill would be togged for auto-cast and the player would
|
||||
// be spammed with messages about enabling/disabling auto-cast
|
||||
switch (spellId)
|
||||
{
|
||||
if (m_autospell == spellId)
|
||||
{
|
||||
autocast = true;
|
||||
case 19244: // Spell Lock rank 1
|
||||
case 19647: // Spell Lock rank 2
|
||||
case 19505: // Devour Magic rank 1
|
||||
case 19731: // Devour Magic rank 2
|
||||
case 19734: // Devour Magic rank 3
|
||||
case 19736: // Devour Magic rank 4
|
||||
case 27276: // Devour Magic rank 5
|
||||
case 27277: // Devour Magic rank 6
|
||||
case 48011: // Devour Magic rank 7
|
||||
// No message - just break out of the switch and let normal cast logic continue
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
bool autocast = false;
|
||||
for (unsigned int& m_autospell : pet->m_autospells)
|
||||
{
|
||||
if (m_autospell == spellId)
|
||||
{
|
||||
autocast = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pet->ToggleAutocast(spellInfo, !autocast);
|
||||
std::ostringstream out;
|
||||
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
|
||||
out << chatHelper.FormatSpell(spellInfo);
|
||||
TellMaster(out);
|
||||
return true;
|
||||
pet->ToggleAutocast(spellInfo, !autocast);
|
||||
std::ostringstream out;
|
||||
out << (autocast ? "|cffff0000|Disabling" : "|cFF00ff00|Enabling") << " pet auto-cast for ";
|
||||
out << chatHelper.FormatSpell(spellInfo);
|
||||
TellMaster(out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
||||
@@ -3300,13 +3346,14 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
std::ostringstream out;
|
||||
out << "Spell cast failed - ";
|
||||
out << "Spell ID: " << spellId << " (" << ChatHelper::FormatSpell(spellInfo) << "), ";
|
||||
out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result) << std::dec << "), ";
|
||||
out << "Error Code: " << static_cast<int>(result) << " (0x" << std::hex << static_cast<int>(result)
|
||||
<< std::dec << "), ";
|
||||
out << "Bot: " << bot->GetName() << ", ";
|
||||
|
||||
|
||||
// Check spell target type
|
||||
if (targets.GetUnitTarget())
|
||||
{
|
||||
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
|
||||
out << "Target: Unit (" << targets.GetUnitTarget()->GetName()
|
||||
<< ", Low GUID: " << targets.GetUnitTarget()->GetGUID().GetCounter()
|
||||
<< ", High GUID: " << static_cast<uint32>(targets.GetUnitTarget()->GetGUID().GetHigh()) << "), ";
|
||||
}
|
||||
@@ -3322,7 +3369,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
out << "Target: Item (Low GUID: " << targets.GetItemTarget()->GetGUID().GetCounter()
|
||||
<< ", High GUID: " << static_cast<uint32>(targets.GetItemTarget()->GetGUID().GetHigh()) << "), ";
|
||||
}
|
||||
|
||||
|
||||
// Check if bot is in trade mode
|
||||
if (bot->GetTradeData())
|
||||
{
|
||||
@@ -3330,7 +3377,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
Item* tradeItem = bot->GetTradeData()->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED);
|
||||
if (tradeItem)
|
||||
{
|
||||
out << "Trade Item: " << tradeItem->GetEntry()
|
||||
out << "Trade Item: " << tradeItem->GetEntry()
|
||||
<< " (Low GUID: " << tradeItem->GetGUID().GetCounter()
|
||||
<< ", High GUID: " << static_cast<uint32>(tradeItem->GetGUID().GetHigh()) << "), ";
|
||||
}
|
||||
@@ -3343,7 +3390,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
||||
{
|
||||
out << "Trade Mode: Inactive, ";
|
||||
}
|
||||
|
||||
|
||||
TellMasterNoFacing(out);
|
||||
}
|
||||
|
||||
@@ -3923,15 +3970,37 @@ bool IsAlliance(uint8 race)
|
||||
|
||||
bool PlayerbotAI::HasRealPlayerMaster()
|
||||
{
|
||||
if (master)
|
||||
// if (master)
|
||||
// {
|
||||
// PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
// return !masterBotAI || masterBotAI->IsRealPlayer();
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
|
||||
// Removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
/* 1) The "master" pointer can be null if the bot was created
|
||||
without a master player or if the master was just removed. */
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
/* 2) Is the master player still present in the world?
|
||||
If FindPlayer fails, we invalidate "master" and stop here. */
|
||||
if (!ObjectAccessor::FindPlayer(master->GetGUID()))
|
||||
{
|
||||
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
|
||||
return !masterBotAI || masterBotAI->IsRealPlayer();
|
||||
master = nullptr; // avoids repeating the check on the next tick
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
/* 3) If the master is a bot, we check that it is itself controlled
|
||||
by a real player. Otherwise, it's already a real player → true. */
|
||||
if (PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master))
|
||||
return masterBotAI->IsRealPlayer(); // bot controlled by a player?
|
||||
|
||||
return true; // master = real player
|
||||
}
|
||||
|
||||
|
||||
bool PlayerbotAI::HasActivePlayerMaster() { return master && !GET_PLAYERBOT_AI(master); }
|
||||
|
||||
bool PlayerbotAI::IsAlt() { return HasRealPlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(bot); }
|
||||
@@ -4282,7 +4351,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
||||
if (!player || !player->IsInWorld())
|
||||
continue;
|
||||
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
|
||||
if (!connectedPlayer)
|
||||
continue;
|
||||
|
||||
@@ -4354,26 +4423,28 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
|
||||
|
||||
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
|
||||
{
|
||||
// Current max server update time (ms), and the configured floor/ceiling values for bot scaling
|
||||
uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTimeOfCurrentTable();
|
||||
uint32 diffLimitFloor = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitfloor;
|
||||
uint32 diffLimitCeiling = sPlayerbotAIConfig->botActiveAloneSmartScaleDiffLimitCeiling;
|
||||
double spreadSize = (double)(diffLimitCeiling - diffLimitFloor) / 6;
|
||||
|
||||
// apply scaling
|
||||
if (diffLimitCeiling <= diffLimitFloor)
|
||||
{
|
||||
// Perfrom binary decision if ceiling <= floor: Either all bots are active or none are
|
||||
return (maxDiff > diffLimitCeiling) ? 0 : mod;
|
||||
}
|
||||
|
||||
if (maxDiff > diffLimitCeiling)
|
||||
return 0;
|
||||
if (maxDiff > diffLimitFloor + (4 * spreadSize))
|
||||
return (mod * 1) / 10;
|
||||
if (maxDiff > diffLimitFloor + (3 * spreadSize))
|
||||
return (mod * 3) / 10;
|
||||
if (maxDiff > diffLimitFloor + (2 * spreadSize))
|
||||
return (mod * 5) / 10;
|
||||
if (maxDiff > diffLimitFloor + (1 * spreadSize))
|
||||
return (mod * 7) / 10;
|
||||
if (maxDiff > diffLimitFloor)
|
||||
return (mod * 9) / 10;
|
||||
|
||||
return mod;
|
||||
if (maxDiff <= diffLimitFloor)
|
||||
return mod;
|
||||
|
||||
// Calculate lag progress from floor to ceiling (0 to 1)
|
||||
double lagProgress = (maxDiff - diffLimitFloor) / (double)(diffLimitCeiling - diffLimitFloor);
|
||||
|
||||
// Apply the percentage of active bots (the complement of lag progress) to the mod value
|
||||
return static_cast<uint32>(mod * (1 - lagProgress));
|
||||
}
|
||||
|
||||
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
|
||||
@@ -4398,12 +4469,51 @@ void PlayerbotAI::RemoveShapeshift()
|
||||
// RemoveAura("tree of life");
|
||||
}
|
||||
|
||||
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
|
||||
// one-hand/2H Weapon in wrong slots
|
||||
// Mirrors Blizzard’s GetAverageItemLevel rules :
|
||||
// https://wowpedia.fandom.com/wiki/API_GetAverageItemLevel
|
||||
uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
constexpr uint8 TOTAL_SLOTS = 17; // every slot except Body & Tabard
|
||||
uint32 sumLevel = 0;
|
||||
|
||||
/* ---------- 0. Detect “ignore off-hand” situations --------- */
|
||||
Item* main = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
Item* off = player->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||
|
||||
bool ignoreOffhand = false; // true → divisor = 16
|
||||
if (main)
|
||||
{
|
||||
bool twoHand = (main->GetTemplate()->InventoryType == INVTYPE_2HWEAPON);
|
||||
if (twoHand && !player->HasAura(SPELL_TITAN_GRIP))
|
||||
ignoreOffhand = true; // classic 2-hander
|
||||
}
|
||||
else if (!off) // both hands empty
|
||||
ignoreOffhand = true;
|
||||
|
||||
/* ---------- 1. Sum up item-levels -------------------------- */
|
||||
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||
{
|
||||
if (slot == EQUIPMENT_SLOT_BODY || slot == EQUIPMENT_SLOT_TABARD)
|
||||
continue; // Blizzard never counts these
|
||||
|
||||
if (ignoreOffhand && slot == EQUIPMENT_SLOT_OFFHAND)
|
||||
continue; // skip off-hand in 2-H case
|
||||
|
||||
if (Item* it = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
sumLevel += it->GetTemplate()->ItemLevel; // missing items add 0
|
||||
}
|
||||
|
||||
/* ---------- 2. Divide by 17 or 16 -------------------------- */
|
||||
const uint8 divisor = ignoreOffhand ? TOTAL_SLOTS - 1 : TOTAL_SLOTS; // 16 or 17
|
||||
return sumLevel / divisor;
|
||||
}
|
||||
|
||||
// NOTE : function rewritten as flags "withBags" and "withBank" not used, and _fillGearScoreData sometimes attribute
|
||||
// one-hand/2H Weapon in wrong slots
|
||||
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
// This function aims to calculate the equipped gear score
|
||||
|
||||
|
||||
uint32 sum = 0;
|
||||
uint8 count = EQUIPMENT_SLOT_END - 2; // ignore body and tabard slots
|
||||
uint8 mh_type = 0;
|
||||
@@ -4412,21 +4522,21 @@ uint32 PlayerbotAI::GetEquipGearScore(Player* player)
|
||||
{
|
||||
Item* item =player->GetItemByPos(INVENTORY_SLOT_BAG_0, i);
|
||||
if (item && i != EQUIPMENT_SLOT_BODY && i != EQUIPMENT_SLOT_TABARD)
|
||||
{
|
||||
{
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
sum += proto->ItemLevel;
|
||||
|
||||
|
||||
// If character is not warfury and have 2 hand weapon equipped, main hand will be counted twice
|
||||
if (i == SLOT_MAIN_HAND)
|
||||
mh_type = item->GetTemplate()->InventoryType;
|
||||
if (!player->HasAura(SPELL_TITAN_GRIP) && mh_type == INVTYPE_2HWEAPON && i == SLOT_MAIN_HAND)
|
||||
sum += item->GetTemplate()->ItemLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 gs = uint32(sum / count);
|
||||
return gs;
|
||||
}
|
||||
}*/
|
||||
|
||||
/*uint32 PlayerbotAI::GetEquipGearScore(Player* player, bool withBags, bool withBank)
|
||||
{
|
||||
@@ -4659,7 +4769,7 @@ void PlayerbotAI::_fillGearScoreData(Player* player, Item* item, std::vector<uin
|
||||
case INVTYPE_SHOULDERS:
|
||||
(*gearScore)[EQUIPMENT_SLOT_SHOULDERS] = std::max((*gearScore)[EQUIPMENT_SLOT_SHOULDERS], level);
|
||||
break;
|
||||
case INVTYPE_BODY: //Shouldn't be considered when calculating average ilevel
|
||||
case INVTYPE_BODY: // Shouldn't be considered when calculating average ilevel
|
||||
(*gearScore)[EQUIPMENT_SLOT_BODY] = std::max((*gearScore)[EQUIPMENT_SLOT_BODY], level);
|
||||
break;
|
||||
case INVTYPE_CHEST:
|
||||
@@ -5017,13 +5127,13 @@ Item* PlayerbotAI::FindAmmo() const
|
||||
}
|
||||
|
||||
// Find Consumable
|
||||
Item* PlayerbotAI::FindConsumable(uint32 displayId) const
|
||||
Item* PlayerbotAI::FindConsumable(uint32 itemId) const
|
||||
{
|
||||
return FindItemInInventory(
|
||||
[displayId](ItemTemplate const* pItemProto) -> bool
|
||||
[itemId](ItemTemplate const* pItemProto) -> bool
|
||||
{
|
||||
return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) &&
|
||||
pItemProto->DisplayInfoID == displayId;
|
||||
pItemProto->ItemId == itemId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5037,80 +5147,80 @@ Item* PlayerbotAI::FindBandage() const
|
||||
|
||||
Item* PlayerbotAI::FindOpenableItem() const
|
||||
{
|
||||
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
|
||||
{
|
||||
return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
|
||||
(itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
|
||||
});
|
||||
return FindItemInInventory(
|
||||
[this](ItemTemplate const* itemTemplate) -> bool
|
||||
{
|
||||
return (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) &&
|
||||
(itemTemplate->LockID == 0 || !this->bot->GetItemByEntry(itemTemplate->ItemId)->IsLocked());
|
||||
});
|
||||
}
|
||||
|
||||
Item* PlayerbotAI::FindLockedItem() const
|
||||
{
|
||||
return FindItemInInventory([this](ItemTemplate const* itemTemplate) -> bool
|
||||
{
|
||||
if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill
|
||||
return false;
|
||||
|
||||
if (itemTemplate->LockID == 0) // Ensure the item is actually locked
|
||||
return false;
|
||||
|
||||
Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
|
||||
if (!item || !item->IsLocked()) // Ensure item instance is locked
|
||||
return false;
|
||||
|
||||
// Check if bot has enough Lockpicking skill
|
||||
LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
|
||||
if (!lockInfo)
|
||||
return false;
|
||||
|
||||
for (uint8 j = 0; j < 8; ++j)
|
||||
return FindItemInInventory(
|
||||
[this](ItemTemplate const* itemTemplate) -> bool
|
||||
{
|
||||
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
|
||||
if (!this->bot->HasSkill(SKILL_LOCKPICKING)) // Ensure bot has Lockpicking skill
|
||||
return false;
|
||||
|
||||
if (itemTemplate->LockID == 0) // Ensure the item is actually locked
|
||||
return false;
|
||||
|
||||
Item* item = this->bot->GetItemByEntry(itemTemplate->ItemId);
|
||||
if (!item || !item->IsLocked()) // Ensure item instance is locked
|
||||
return false;
|
||||
|
||||
// Check if bot has enough Lockpicking skill
|
||||
LockEntry const* lockInfo = sLockStore.LookupEntry(itemTemplate->LockID);
|
||||
if (!lockInfo)
|
||||
return false;
|
||||
|
||||
for (uint8 j = 0; j < 8; ++j)
|
||||
{
|
||||
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
|
||||
if (skillId == SKILL_LOCKPICKING)
|
||||
if (lockInfo->Type[j] == LOCK_KEY_SKILL)
|
||||
{
|
||||
uint32 requiredSkill = lockInfo->Skill[j];
|
||||
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
|
||||
return botSkill >= requiredSkill;
|
||||
uint32 skillId = SkillByLockType(LockType(lockInfo->Index[j]));
|
||||
if (skillId == SKILL_LOCKPICKING)
|
||||
{
|
||||
uint32 requiredSkill = lockInfo->Skill[j];
|
||||
uint32 botSkill = this->bot->GetSkillValue(SKILL_LOCKPICKING);
|
||||
return botSkill >= requiredSkill;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID,
|
||||
ELEMENTAL_SHARPENING_DISPLAYID, DENSE_SHARPENING_DISPLAYID,
|
||||
SOLID_SHARPENING_DISPLAYID, HEAVY_SHARPENING_DISPLAYID,
|
||||
COARSE_SHARPENING_DISPLAYID, ROUGH_SHARPENING_DISPLAYID};
|
||||
|
||||
static const uint32 uPriorizedWeightStoneIds[7] = {ADAMANTITE_WEIGHTSTONE_DISPLAYID, FEL_WEIGHTSTONE_DISPLAYID,
|
||||
DENSE_WEIGHTSTONE_DISPLAYID, SOLID_WEIGHTSTONE_DISPLAYID,
|
||||
HEAVY_WEIGHTSTONE_DISPLAYID, COARSE_WEIGHTSTONE_DISPLAYID,
|
||||
ROUGH_WEIGHTSTONE_DISPLAYID};
|
||||
|
||||
/**
|
||||
* FindStoneFor()
|
||||
* return Item* Returns sharpening/weight stone item eligible to enchant a bot weapon
|
||||
*
|
||||
* params:weapon Item* the weap<61>n the function should search and return a enchanting item for
|
||||
* return nullptr if no relevant item is found in bot inventory, else return a sharpening or weight
|
||||
* stone based on the weapon subclass
|
||||
*
|
||||
*/
|
||||
Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
{
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
const ItemTemplate* item_template = weapon->GetTemplate();
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
|
||||
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
|
||||
};
|
||||
|
||||
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
|
||||
};
|
||||
|
||||
Item* stone = nullptr;
|
||||
ItemTemplate const* pProto = weapon->GetTemplate();
|
||||
if (pProto && (pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || pProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER))
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER || pProto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM))
|
||||
{
|
||||
for (uint8 i = 0; i < std::size(uPriorizedSharpStoneIds); ++i)
|
||||
for (uint8 i = 0; i < std::size(uPrioritizedSharpStoneIds); ++i)
|
||||
{
|
||||
stone = FindConsumable(uPriorizedSharpStoneIds[i]);
|
||||
stone = FindConsumable(uPrioritizedSharpStoneIds[i]);
|
||||
if (stone)
|
||||
{
|
||||
return stone;
|
||||
@@ -5118,11 +5228,12 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
}
|
||||
}
|
||||
else if (pProto &&
|
||||
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2))
|
||||
(pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE || pProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
|
||||
pProto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || pProto->SubClass == ITEM_SUBCLASS_WEAPON_FIST))
|
||||
{
|
||||
for (uint8 i = 0; i < std::size(uPriorizedWeightStoneIds); ++i)
|
||||
for (uint8 i = 0; i < std::size(uPrioritizedWeightStoneIds); ++i)
|
||||
{
|
||||
stone = FindConsumable(uPriorizedWeightStoneIds[i]);
|
||||
stone = FindConsumable(uPrioritizedWeightStoneIds[i]);
|
||||
if (stone)
|
||||
{
|
||||
return stone;
|
||||
@@ -5135,6 +5246,7 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
|
||||
|
||||
Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
{
|
||||
|
||||
if (!weapon)
|
||||
return nullptr;
|
||||
|
||||
@@ -5142,35 +5254,60 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
|
||||
if (!item_template)
|
||||
return nullptr;
|
||||
|
||||
// static const will only get created once whatever the call amout
|
||||
static const std::vector<uint32_t> uPriorizedWizardOilIds = {
|
||||
MINOR_WIZARD_OIL, MINOR_MANA_OIL, LESSER_WIZARD_OIL, LESSER_MANA_OIL, BRILLIANT_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, WIZARD_OIL, SUPERIOR_MANA_OIL, SUPERIOR_WIZARD_OIL};
|
||||
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
|
||||
// static const will only get created once whatever the call amout
|
||||
static const std::vector<uint32_t> uPriorizedManaOilIds = {
|
||||
MINOR_MANA_OIL, MINOR_WIZARD_OIL, LESSER_MANA_OIL, LESSER_WIZARD_OIL, BRILLIANT_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_MANA_OIL, WIZARD_OIL, SUPERIOR_WIZARD_OIL};
|
||||
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
|
||||
Item* oil = nullptr;
|
||||
if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_SWORD ||
|
||||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_STAFF || item_template->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
int botClass = bot->getClass();
|
||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
|
||||
const std::vector<uint32_t>* prioritizedOils = nullptr;
|
||||
switch (botClass)
|
||||
{
|
||||
for (const auto& id : uPriorizedWizardOilIds)
|
||||
{
|
||||
oil = FindConsumable(id);
|
||||
if (oil)
|
||||
return oil;
|
||||
}
|
||||
case CLASS_PRIEST:
|
||||
prioritizedOils = (specTab == 2) ? &uPrioritizedWizardOilIds : &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (specTab == 0) // Balance
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 1) // Feral
|
||||
prioritizedOils = nullptr;
|
||||
else // Restoration (specTab == 2) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_HUNTER:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (specTab == 1) // Protection
|
||||
prioritizedOils = &uPrioritizedWizardOilIds;
|
||||
else if (specTab == 2) // Retribution
|
||||
prioritizedOils = nullptr;
|
||||
else // Holy (specTab == 0) or any other/unspecified spec
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
default:
|
||||
prioritizedOils = &uPrioritizedManaOilIds;
|
||||
break;
|
||||
}
|
||||
else if (item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE ||
|
||||
item_template->SubClass == ITEM_SUBCLASS_WEAPON_MACE2)
|
||||
|
||||
if (prioritizedOils)
|
||||
{
|
||||
for (const auto& id : uPriorizedManaOilIds)
|
||||
for (const auto& id : *prioritizedOils)
|
||||
{
|
||||
oil = FindConsumable(id);
|
||||
if (oil)
|
||||
{
|
||||
return oil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6001,6 +6138,35 @@ ChatChannelSource PlayerbotAI::GetChatChannelSource(Player* bot, uint32 type, st
|
||||
return ChatChannelSource::SRC_UNDEFINED;
|
||||
}
|
||||
|
||||
bool PlayerbotAI::CheckLocationDistanceByLevel(Player* player, const WorldLocation& loc, bool fromStartUp)
|
||||
{
|
||||
if (player->GetLevel() > 16)
|
||||
return true;
|
||||
|
||||
float dis = 0.0f;
|
||||
if (fromStartUp)
|
||||
{
|
||||
PlayerInfo const* pInfo = sObjectMgr->GetPlayerInfo(player->getRace(true), player->getClass());
|
||||
if (loc.GetMapId() != pInfo->mapId)
|
||||
return false;
|
||||
dis = loc.GetExactDist(pInfo->positionX, pInfo->positionY, pInfo->positionZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loc.GetMapId() != player->GetMapId())
|
||||
return false;
|
||||
dis = loc.GetExactDist(player);
|
||||
}
|
||||
|
||||
float bound = 10000.0f;
|
||||
if (player->GetLevel() <= 4)
|
||||
bound = 500.0f;
|
||||
else if (player->GetLevel() <= 10)
|
||||
bound = 2500.0f;
|
||||
|
||||
return dis <= bound;
|
||||
}
|
||||
|
||||
std::vector<const Quest*> PlayerbotAI::GetAllCurrentQuests()
|
||||
{
|
||||
std::vector<const Quest*> result;
|
||||
@@ -6270,3 +6436,33 @@ SpellFamilyNames PlayerbotAI::Class2SpellFamilyName(uint8 cls)
|
||||
}
|
||||
return SPELLFAMILY_GENERIC;
|
||||
}
|
||||
|
||||
void PlayerbotAI::AddTimedEvent(std::function<void()> callback, uint32 delayMs)
|
||||
{
|
||||
class LambdaEvent final : public BasicEvent
|
||||
{
|
||||
std::function<void()> _cb;
|
||||
|
||||
public:
|
||||
explicit LambdaEvent(std::function<void()> cb) : _cb(std::move(cb)) {}
|
||||
bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override
|
||||
{
|
||||
_cb();
|
||||
return true; // remove after execution
|
||||
}
|
||||
};
|
||||
|
||||
// Every Player already owns an EventMap called m_Events
|
||||
bot->m_Events.AddEvent(new LambdaEvent(std::move(callback)), bot->m_Events.CalculateTime(delayMs));
|
||||
}
|
||||
|
||||
void PlayerbotAI::EvaluateHealerDpsStrategy()
|
||||
{
|
||||
if (!IsHeal(bot, true))
|
||||
return;
|
||||
|
||||
if (sPlayerbotAIConfig->IsRestrictedHealerDPSMap(bot->GetMapId()))
|
||||
ChangeStrategy("-healer dps", BOT_STATE_COMBAT);
|
||||
else
|
||||
ChangeStrategy("+healer dps", BOT_STATE_COMBAT);
|
||||
}
|
||||
|
||||
@@ -46,27 +46,27 @@ struct GameObjectData;
|
||||
|
||||
enum StrategyType : uint32;
|
||||
|
||||
enum HealingItemDisplayId
|
||||
enum HealingItemId
|
||||
{
|
||||
HEALTHSTONE_DISPLAYID = 8026,
|
||||
MAJOR_HEALING_POTION = 24152,
|
||||
WHIPPER_ROOT_TUBER = 21974,
|
||||
NIGHT_DRAGON_BREATH = 21975,
|
||||
LIMITED_INVULNERABILITY_POTION = 24213,
|
||||
GREATER_DREAMLESS_SLEEP_POTION = 17403,
|
||||
SUPERIOR_HEALING_POTION = 15714,
|
||||
CRYSTAL_RESTORE = 2516,
|
||||
DREAMLESS_SLEEP_POTION = 17403,
|
||||
GREATER_HEALING_POTION = 15713,
|
||||
HEALING_POTION = 15712,
|
||||
LESSER_HEALING_POTION = 15711,
|
||||
DISCOLORED_HEALING_POTION = 15736,
|
||||
MINOR_HEALING_POTION = 15710,
|
||||
VOLATILE_HEALING_POTION = 24212,
|
||||
SUPER_HEALING_POTION = 37807,
|
||||
CRYSTAL_HEALING_POTION = 47132,
|
||||
FEL_REGENERATION_POTION = 37864,
|
||||
MAJOR_DREAMLESS_SLEEP_POTION = 37845
|
||||
HEALTHSTONE = 5512,
|
||||
MAJOR_HEALING_POTION = 13446,
|
||||
WHIPPER_ROOT_TUBER = 11951,
|
||||
NIGHT_DRAGON_BREATH = 11952,
|
||||
LIMITED_INVULNERABILITY_POTION = 3387,
|
||||
GREATER_DREAMLESS_SLEEP_POTION = 22886,
|
||||
SUPERIOR_HEALING_POTION = 3928,
|
||||
CRYSTAL_RESTORE = 11564,
|
||||
DREAMLESS_SLEEP_POTION = 12190,
|
||||
GREATER_HEALING_POTION = 1710,
|
||||
HEALING_POTION = 929,
|
||||
LESSER_HEALING_POTION = 858,
|
||||
DISCOLORED_HEALING_POTION = 3391,
|
||||
MINOR_HEALING_POTION = 118,
|
||||
VOLATILE_HEALING_POTION = 28100,
|
||||
SUPER_HEALING_POTION = 22829,
|
||||
CRYSTAL_HEALING_POTION = 13462,
|
||||
FEL_REGENERATION_POTION = 28101,
|
||||
MAJOR_DREAMLESS_SLEEP_POTION = 20002
|
||||
};
|
||||
|
||||
enum BotState
|
||||
@@ -152,6 +152,7 @@ static std::map<ChatChannelSource, std::string> ChatChannelSourceStr = {
|
||||
{SRC_RAID, "SRC_RAID"},
|
||||
|
||||
{SRC_UNDEFINED, "SRC_UNDEFINED"}};
|
||||
|
||||
enum ChatChannelId
|
||||
{
|
||||
GENERAL = 1,
|
||||
@@ -162,60 +163,66 @@ enum ChatChannelId
|
||||
GUILD_RECRUITMENT = 25,
|
||||
};
|
||||
|
||||
enum RoguePoisonDisplayId
|
||||
enum RoguePoisonId
|
||||
{
|
||||
DEADLY_POISON_DISPLAYID = 13707,
|
||||
INSTANT_POISON_DISPLAYID = 13710,
|
||||
WOUND_POISON_DISPLAYID = 37278
|
||||
INSTANT_POISON = 6947,
|
||||
INSTANT_POISON_II = 6949,
|
||||
INSTANT_POISON_III = 6950,
|
||||
INSTANT_POISON_IV = 8926,
|
||||
INSTANT_POISON_V = 8927,
|
||||
INSTANT_POISON_VI = 8928,
|
||||
INSTANT_POISON_VII = 21927,
|
||||
INSTANT_POISON_VIII = 43230,
|
||||
INSTANT_POISON_IX = 43231,
|
||||
DEADLY_POISON = 2892,
|
||||
DEADLY_POISON_II = 2893,
|
||||
DEADLY_POISON_III = 8984,
|
||||
DEADLY_POISON_IV = 8985,
|
||||
DEADLY_POISON_V = 20844,
|
||||
DEADLY_POISON_VI = 22053,
|
||||
DEADLY_POISON_VII = 22054,
|
||||
DEADLY_POISON_VIII = 43232,
|
||||
DEADLY_POISON_IX = 43233
|
||||
};
|
||||
|
||||
enum SharpeningStoneDisplayId
|
||||
enum SharpeningStoneId
|
||||
{
|
||||
ROUGH_SHARPENING_DISPLAYID = 24673,
|
||||
COARSE_SHARPENING_DISPLAYID = 24674,
|
||||
HEAVY_SHARPENING_DISPLAYID = 24675,
|
||||
SOLID_SHARPENING_DISPLAYID = 24676,
|
||||
DENSE_SHARPENING_DISPLAYID = 24677,
|
||||
CONSECRATED_SHARPENING_DISPLAYID =
|
||||
24674, // will not be used because bot can not know if it will face undead targets
|
||||
ELEMENTAL_SHARPENING_DISPLAYID = 21072,
|
||||
FEL_SHARPENING_DISPLAYID = 39192,
|
||||
ADAMANTITE_SHARPENING_DISPLAYID = 39193
|
||||
ROUGH_SHARPENING_STONE = 2862,
|
||||
COARSE_SHARPENING_STONE = 2863,
|
||||
HEAVY_SHARPENING_STONE = 2871,
|
||||
SOLID_SHARPENING_STONE = 7964,
|
||||
DENSE_SHARPENING_STONE = 12404,
|
||||
ELEMENTAL_SHARPENING_STONE = 18262,
|
||||
FEL_SHARPENING_STONE = 23528,
|
||||
ADAMANTITE_SHARPENING_STONE = 23529
|
||||
};
|
||||
|
||||
enum WeightStoneDisplayId
|
||||
enum WeightstoneId
|
||||
{
|
||||
ROUGH_WEIGHTSTONE_DISPLAYID = 24683,
|
||||
COARSE_WEIGHTSTONE_DISPLAYID = 24684,
|
||||
HEAVY_WEIGHTSTONE_DISPLAYID = 24685,
|
||||
SOLID_WEIGHTSTONE_DISPLAYID = 24686,
|
||||
DENSE_WEIGHTSTONE_DISPLAYID = 24687,
|
||||
FEL_WEIGHTSTONE_DISPLAYID = 39548,
|
||||
ADAMANTITE_WEIGHTSTONE_DISPLAYID = 39549
|
||||
ROUGH_WEIGHTSTONE = 3239,
|
||||
COARSE_WEIGHTSTONE = 3240,
|
||||
HEAVY_WEIGHTSTONE = 3241,
|
||||
SOLID_WEIGHTSTONE = 7965,
|
||||
DENSE_WEIGHTSTONE = 12643,
|
||||
FEL_WEIGHTSTONE = 28420,
|
||||
ADAMANTITE_WEIGHTSTONE = 28421
|
||||
};
|
||||
|
||||
enum WizardOilDisplayId
|
||||
enum WizardOilId
|
||||
{
|
||||
MINOR_WIZARD_OIL = 9731,
|
||||
LESSER_WIZARD_OIL = 47903,
|
||||
BRILLIANT_WIZARD_OIL = 47901,
|
||||
WIZARD_OIL = 47905,
|
||||
SUPERIOR_WIZARD_OIL = 47904,
|
||||
/// Blessed Wizard Oil = 26865 //scourge inv
|
||||
MINOR_WIZARD_OIL = 20744,
|
||||
LESSER_WIZARD_OIL = 20746,
|
||||
WIZARD_OIL = 20750,
|
||||
BRILLIANT_WIZARD_OIL = 20749,
|
||||
SUPERIOR_WIZARD_OIL = 22522
|
||||
};
|
||||
|
||||
enum ManaOilDisplayId
|
||||
enum ManaOilId
|
||||
{
|
||||
MINOR_MANA_OIL = 34492,
|
||||
LESSER_MANA_OIL = 47902,
|
||||
BRILLIANT_MANA_OIL = 41488,
|
||||
SUPERIOR_MANA_OIL = 36862
|
||||
};
|
||||
|
||||
enum ShieldWardDisplayId
|
||||
{
|
||||
LESSER_WARD_OFSHIELDING = 38759,
|
||||
GREATER_WARD_OFSHIELDING = 38760
|
||||
MINOR_MANA_OIL = 20745,
|
||||
LESSER_MANA_OIL = 20747,
|
||||
BRILLIANT_MANA_OIL = 20748,
|
||||
SUPERIOR_MANA_OIL = 22521
|
||||
};
|
||||
|
||||
enum class BotTypeNumber : uint8
|
||||
@@ -401,6 +408,7 @@ public:
|
||||
void ClearStrategies(BotState type);
|
||||
std::vector<std::string> GetStrategies(BotState type);
|
||||
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);
|
||||
void EvaluateHealerDpsStrategy();
|
||||
bool ContainsStrategy(StrategyType type);
|
||||
bool HasStrategy(std::string const name, BotState type);
|
||||
BotState GetState() { return currentState; };
|
||||
@@ -471,7 +479,7 @@ public:
|
||||
Item* FindBandage() const;
|
||||
Item* FindOpenableItem() const;
|
||||
Item* FindLockedItem() const;
|
||||
Item* FindConsumable(uint32 displayId) const;
|
||||
Item* FindConsumable(uint32 itemId) const;
|
||||
Item* FindStoneFor(Item* weapon) const;
|
||||
Item* FindOilFor(Item* weapon) const;
|
||||
void ImbueItem(Item* item, uint32 targetFlag, ObjectGuid targetGUID);
|
||||
@@ -544,6 +552,8 @@ public:
|
||||
bool IsSafe(Player* player);
|
||||
bool IsSafe(WorldObject* obj);
|
||||
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
|
||||
|
||||
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
|
||||
|
||||
bool HasCheat(BotCheatMask mask)
|
||||
{
|
||||
@@ -590,6 +600,9 @@ public:
|
||||
NewRpgStatistic rpgStatistic;
|
||||
std::unordered_set<uint32> lowPriorityQuest;
|
||||
|
||||
// Schedules a callback to run once after <delayMs> milliseconds.
|
||||
void AddTimedEvent(std::function<void()> callback, uint32 delayMs);
|
||||
|
||||
private:
|
||||
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
|
||||
bool mixed = false);
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
*/
|
||||
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "Config.h"
|
||||
#include "NewRpgInfo.h"
|
||||
#include "PlayerbotDungeonSuggestionMgr.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
@@ -82,6 +81,8 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
|
||||
returnDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReturnDelay", 7000);
|
||||
lootDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.LootDelay", 1000);
|
||||
disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30);
|
||||
disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300);
|
||||
|
||||
farDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FarDistance", 20.0f);
|
||||
sightDistance = sConfigMgr->GetOption<float>("AiPlayerbot.SightDistance", 75.0f);
|
||||
@@ -117,6 +118,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", false);
|
||||
|
||||
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
|
||||
incrementalGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.IncrementalGearInit", true);
|
||||
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
|
||||
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
|
||||
|
||||
@@ -129,6 +131,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
allowAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowAccountBots", true);
|
||||
allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true);
|
||||
allowTrustedAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowTrustedAccountBots", true);
|
||||
disabledWithoutRealPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.DisabledWithoutRealPlayer", false);
|
||||
randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false);
|
||||
randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", false);
|
||||
inviteChat = sConfigMgr->GetOption<bool>("AiPlayerbot.InviteChat", false);
|
||||
@@ -145,7 +148,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedZoneIds",
|
||||
"2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,"
|
||||
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395"),
|
||||
"3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"),
|
||||
pvpProhibitedZoneIds);
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
|
||||
pvpProhibitedAreaIds);
|
||||
@@ -154,6 +157,8 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
||||
randomBotQuestIds);
|
||||
|
||||
LoadSet<std::set<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects", "176213,17155"),
|
||||
disallowedGameObjects);
|
||||
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
|
||||
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
|
||||
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
|
||||
@@ -185,6 +190,19 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotsPriceChangeInterval", 48 * HOUR);
|
||||
randomBotJoinLfg = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinLfg", true);
|
||||
|
||||
restrictHealerDPS = sConfigMgr->GetOption<bool>("AiPlayerbot.HealerDPSMapRestriction", false);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RestrictedHealerDPSMaps",
|
||||
"33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,"
|
||||
"1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,"
|
||||
"575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,"
|
||||
"531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"),
|
||||
restrictedHealerDPSMaps);
|
||||
|
||||
//////////////////////////// ICC
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
|
||||
//////////////////////////// CHAT
|
||||
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
|
||||
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
|
||||
@@ -307,11 +325,48 @@ bool PlayerbotAIConfig::Initialize()
|
||||
summonAtInnkeepersEnabled = sConfigMgr->GetOption<bool>("AiPlayerbot.SummonAtInnkeepersEnabled", true);
|
||||
randomBotMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotMinLevel", 1);
|
||||
randomBotMaxLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotMaxLevel", 80);
|
||||
if (randomBotMaxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
|
||||
randomBotMaxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
|
||||
randomBotLoginAtStartup = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotLoginAtStartup", true);
|
||||
randomBotTeleLowerLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleLowerLevel", 3);
|
||||
randomBotTeleHigherLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleHigherLevel", 1);
|
||||
randomBotTeleLowerLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleLowerLevel", 1);
|
||||
randomBotTeleHigherLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleHigherLevel", 3);
|
||||
openGoSpell = sConfigMgr->GetOption<int32>("AiPlayerbot.OpenGoSpell", 6477);
|
||||
|
||||
// Zones for NewRpgStrategy teleportation brackets
|
||||
std::vector<uint32> zoneIds = {
|
||||
// Classic WoW - Low-level zones
|
||||
1, 12, 14, 85, 141, 215, 3430, 3524,
|
||||
// Classic WoW - Mid-level zones
|
||||
17, 38, 40, 130, 148, 3433, 3525,
|
||||
// Classic WoW - High-level zones
|
||||
10, 11, 44, 267, 331, 400, 406,
|
||||
// Classic WoW - Higher-level zones
|
||||
3, 8, 15, 16, 33, 45, 47, 51, 357, 405, 440,
|
||||
// Classic WoW - Top-level zones
|
||||
4, 28, 46, 139, 361, 490, 618, 1377,
|
||||
// The Burning Crusade - Zones
|
||||
3483, 3518, 3519, 3520, 3521, 3522, 3523, 4080,
|
||||
// Wrath of the Lich King - Zones
|
||||
65, 66, 67, 210, 394, 495, 2817, 3537, 3711, 4197
|
||||
};
|
||||
|
||||
for (uint32 zoneId : zoneIds)
|
||||
{
|
||||
std::string setting = "AiPlayerbot.ZoneBracket." + std::to_string(zoneId);
|
||||
std::string value = sConfigMgr->GetOption<std::string>(setting, "");
|
||||
|
||||
if (!value.empty())
|
||||
{
|
||||
size_t commaPos = value.find(',');
|
||||
if (commaPos != std::string::npos)
|
||||
{
|
||||
uint32 minLevel = atoi(value.substr(0, commaPos).c_str());
|
||||
uint32 maxLevel = atoi(value.substr(commaPos + 1).c_str());
|
||||
zoneBrackets[zoneId] = std::make_pair(minLevel, maxLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
randomChangeMultiplier = sConfigMgr->GetOption<float>("AiPlayerbot.RandomChangeMultiplier", 1.0);
|
||||
|
||||
randomBotCombatStrategies = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotCombatStrategies", "-threat");
|
||||
@@ -331,6 +386,12 @@ bool PlayerbotAIConfig::Initialize()
|
||||
useFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFlyMountAtMinLevel", 60);
|
||||
useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70);
|
||||
|
||||
// stagger bot flightpath takeoff
|
||||
delayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350u);
|
||||
delayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000u);
|
||||
gapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200u);
|
||||
gapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100u);
|
||||
|
||||
LOG_INFO("server.loading", "Loading TalentSpecs...");
|
||||
|
||||
for (uint32 cls = 1; cls < MAX_CLASSES; ++cls)
|
||||
@@ -395,7 +456,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
}
|
||||
|
||||
botCheats.clear();
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi"),
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.BotCheats", "taxi,raid"),
|
||||
botCheats);
|
||||
|
||||
botCheatMask = 0;
|
||||
@@ -410,30 +471,17 @@ bool PlayerbotAIConfig::Initialize()
|
||||
botCheatMask |= (uint32)BotCheatMask::mana;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "power") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::power;
|
||||
if (std::find(botCheats.begin(), botCheats.end(), "raid") != botCheats.end())
|
||||
botCheatMask |= (uint32)BotCheatMask::raid;
|
||||
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
|
||||
allowedLogFiles);
|
||||
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""),
|
||||
tradeActionExcludedPrefixes);
|
||||
|
||||
worldBuffs.clear();
|
||||
|
||||
LOG_INFO("playerbots", "Loading Worldbuff...");
|
||||
for (uint32 factionId = 0; factionId < 3; factionId++)
|
||||
{
|
||||
for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
|
||||
{
|
||||
for (uint32 specId = 0; specId <= MAX_WORLDBUFF_SPECNO; specId++)
|
||||
{
|
||||
for (uint32 minLevel = 0; minLevel <= randomBotMaxLevel; minLevel++)
|
||||
{
|
||||
for (uint32 maxLevel = minLevel; maxLevel <= randomBotMaxLevel; maxLevel++)
|
||||
{
|
||||
loadWorldBuff(factionId, classId, specId, minLevel, maxLevel);
|
||||
}
|
||||
loadWorldBuff(factionId, classId, specId, minLevel, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadWorldBuff();
|
||||
LOG_INFO("playerbots", "Loading World Buff Feature...");
|
||||
|
||||
randomBotAccountPrefix = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAccountPrefix", "rndbot");
|
||||
randomBotAccountCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAccountCount", 0);
|
||||
@@ -466,6 +514,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
equipmentPersistence = sConfigMgr->GetOption<bool>("AiPlayerbot.EquipmentPersistence", false);
|
||||
equipmentPersistenceLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.EquipmentPersistenceLevel", 80);
|
||||
groupInvitationPermission = sConfigMgr->GetOption<int32>("AiPlayerbot.GroupInvitationPermission", 1);
|
||||
keepAltsInGroup = sConfigMgr->GetOption<bool>("AiPlayerbot.KeepAltsInGroup", false);
|
||||
allowSummonInCombat = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonInCombat", true);
|
||||
allowSummonWhenMasterIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenMasterIsDead", true);
|
||||
allowSummonWhenBotIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenBotIsDead", true);
|
||||
@@ -483,7 +532,7 @@ bool PlayerbotAIConfig::Initialize()
|
||||
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
|
||||
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
|
||||
|
||||
playerbotsXPrate = sConfigMgr->GetOption<float>("AiPlayerbot.PlayerbotsXPRate", 1.0);
|
||||
randomBotXPRate = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotXPRate", 1.0);
|
||||
randomBotAllianceRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAllianceRatio", 50);
|
||||
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
|
||||
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
|
||||
@@ -530,7 +579,16 @@ bool PlayerbotAIConfig::Initialize()
|
||||
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
|
||||
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
||||
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true);
|
||||
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", false);
|
||||
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", true);
|
||||
|
||||
RpgStatusProbWeight[RPG_WANDER_RANDOM] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderRandom", 15);
|
||||
RpgStatusProbWeight[RPG_WANDER_NPC] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.WanderNpc", 20);
|
||||
RpgStatusProbWeight[RPG_GO_GRIND] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoGrind", 15);
|
||||
RpgStatusProbWeight[RPG_GO_CAMP] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.GoCamp", 10);
|
||||
RpgStatusProbWeight[RPG_DO_QUEST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.DoQuest", 60);
|
||||
RpgStatusProbWeight[RPG_TRAVEL_FLIGHT] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.TravelFlight", 15);
|
||||
RpgStatusProbWeight[RPG_REST] = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgStatusProbWeight.Rest", 5);
|
||||
|
||||
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
|
||||
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
|
||||
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
|
||||
@@ -551,6 +609,9 @@ bool PlayerbotAIConfig::Initialize()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assign account types after accounts are created
|
||||
sRandomPlayerbotMgr->AssignAccountTypes();
|
||||
|
||||
if (sPlayerbotAIConfig->enabled)
|
||||
{
|
||||
sRandomPlayerbotMgr->Init();
|
||||
@@ -562,18 +623,16 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sPlayerbotTextMgr->LoadBotTextChance();
|
||||
PlayerbotFactory::Init();
|
||||
|
||||
if (!sPlayerbotAIConfig->autoDoQuests)
|
||||
{
|
||||
LOG_INFO("server.loading", "Loading Quest Detail Data...");
|
||||
sTravelMgr->LoadQuestTravelTable();
|
||||
}
|
||||
|
||||
AiObjectContext::BuildAllSharedContexts();
|
||||
|
||||
if (sPlayerbotAIConfig->randomBotSuggestDungeons)
|
||||
{
|
||||
sPlayerbotDungeonSuggestionMgr->LoadDungeonSuggestions();
|
||||
}
|
||||
|
||||
excludedHunterPetFamilies.clear();
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.ExcludedHunterPetFamilies", ""), excludedHunterPetFamilies);
|
||||
|
||||
LOG_INFO("server.loading", "---------------------------------------");
|
||||
LOG_INFO("server.loading", " AI Playerbots initialized ");
|
||||
LOG_INFO("server.loading", "---------------------------------------");
|
||||
@@ -606,6 +665,12 @@ bool PlayerbotAIConfig::IsInPvpProhibitedArea(uint32 id)
|
||||
return find(pvpProhibitedAreaIds.begin(), pvpProhibitedAreaIds.end(), id) != pvpProhibitedAreaIds.end();
|
||||
}
|
||||
|
||||
bool PlayerbotAIConfig::IsRestrictedHealerDPSMap(uint32 mapId) const
|
||||
{
|
||||
return restrictHealerDPS &&
|
||||
std::find(restrictedHealerDPSMaps.begin(), restrictedHealerDPSMaps.end(), mapId) != restrictedHealerDPSMaps.end();
|
||||
}
|
||||
|
||||
std::string const PlayerbotAIConfig::GetTimestampStr()
|
||||
{
|
||||
time_t t = time(nullptr);
|
||||
@@ -678,88 +743,62 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
|
||||
void PlayerbotAIConfig::loadWorldBuff()
|
||||
{
|
||||
std::vector<uint32> buffs;
|
||||
std::string matrix = sConfigMgr->GetOption<std::string>("AiPlayerbot.WorldBuffMatrix", "", true);
|
||||
if (matrix.empty())
|
||||
return;
|
||||
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1;
|
||||
std::istringstream entryStream(matrix);
|
||||
std::string entry;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
while (std::getline(entryStream, entry, ';'))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1;
|
||||
entry.erase(0, entry.find_first_not_of(" \t\r\n"));
|
||||
entry.erase(entry.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
size_t firstColon = entry.find(':');
|
||||
size_t secondColon = entry.find(':', firstColon + 1);
|
||||
|
||||
for (auto buff : buffs)
|
||||
if (firstColon == std::string::npos || secondColon == std::string::npos)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
LOG_ERROR("playerbots", "Malformed entry: [{}]", entry);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0 && minLevel1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1;
|
||||
std::string metaPart = entry.substr(firstColon + 1, secondColon - firstColon - 1);
|
||||
std::string spellPart = entry.substr(secondColon + 1);
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
std::vector<uint32> ids;
|
||||
std::istringstream metaStream(metaPart);
|
||||
std::string token;
|
||||
while (std::getline(metaStream, token, ','))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
try {
|
||||
ids.push_back(static_cast<uint32>(std::stoi(token)));
|
||||
} catch (...) {
|
||||
LOG_ERROR("playerbots", "Invalid meta token in [{}]", entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
if (ids.size() != 5)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
LOG_ERROR("playerbots", "Entry [{}] has incomplete meta block", entry);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff." << factionId1;
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
std::istringstream spellStream(spellPart);
|
||||
while (std::getline(spellStream, token, ','))
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
}
|
||||
}
|
||||
|
||||
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "AiPlayerbot.WorldBuff";
|
||||
|
||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
|
||||
|
||||
for (auto buff : buffs)
|
||||
{
|
||||
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
|
||||
worldBuffs.push_back(wb);
|
||||
try {
|
||||
uint32 spellId = static_cast<uint32>(std::stoi(token));
|
||||
worldBuff wb = { spellId, ids[0], ids[1], ids[2], ids[3], ids[4] };
|
||||
worldBuffs.push_back(wb);
|
||||
} catch (...) {
|
||||
LOG_ERROR("playerbots", "Invalid spell ID in [{}]", entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define _PLAYERBOT_PLAYERbotAICONFIG_H
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common.h"
|
||||
#include "DBCEnums.h"
|
||||
@@ -21,7 +22,8 @@ enum class BotCheatMask : uint32
|
||||
health = 4,
|
||||
mana = 8,
|
||||
power = 16,
|
||||
maxMask = 32
|
||||
raid = 32,
|
||||
maxMask = 64
|
||||
};
|
||||
|
||||
enum class HealingManaEfficiency : uint8
|
||||
@@ -34,8 +36,27 @@ enum class HealingManaEfficiency : uint8
|
||||
SUPERIOR = 32
|
||||
};
|
||||
|
||||
enum NewRpgStatus : int
|
||||
{
|
||||
RPG_STATUS_START = 0,
|
||||
// Going to far away place
|
||||
RPG_GO_GRIND = 0,
|
||||
RPG_GO_CAMP = 1,
|
||||
// Exploring nearby
|
||||
RPG_WANDER_RANDOM = 2,
|
||||
RPG_WANDER_NPC = 3,
|
||||
// Do Quest (based on quest status)
|
||||
RPG_DO_QUEST = 4,
|
||||
// Travel
|
||||
RPG_TRAVEL_FLIGHT = 5,
|
||||
// Taking a break
|
||||
RPG_REST = 6,
|
||||
// Initial status
|
||||
RPG_IDLE = 7,
|
||||
RPG_STATUS_END = 8
|
||||
};
|
||||
|
||||
#define MAX_SPECNO 20
|
||||
#define MAX_WORLDBUFF_SPECNO 3
|
||||
|
||||
class PlayerbotAIConfig
|
||||
{
|
||||
@@ -55,6 +76,8 @@ public:
|
||||
bool IsInPvpProhibitedArea(uint32 id);
|
||||
|
||||
bool enabled;
|
||||
bool disabledWithoutRealPlayer;
|
||||
bool EnableICCBuffs;
|
||||
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
|
||||
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
|
||||
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
|
||||
@@ -71,6 +94,7 @@ public:
|
||||
float maxAoeAvoidRadius;
|
||||
std::set<uint32> aoeAvoidSpellWhitelist;
|
||||
bool tellWhenAvoidAoe;
|
||||
std::set<uint32> disallowedGameObjects;
|
||||
|
||||
uint32 openGoSpell;
|
||||
bool randomBotAutologin;
|
||||
@@ -84,6 +108,7 @@ public:
|
||||
std::vector<uint32> randomBotQuestIds;
|
||||
uint32 randomBotTeleportDistance;
|
||||
float randomGearLoweringChance;
|
||||
bool incrementalGearInit;
|
||||
int32 randomGearQualityLimit;
|
||||
int32 randomGearScoreLimit;
|
||||
float randomBotMinLevelChance, randomBotMaxLevelChance;
|
||||
@@ -99,6 +124,7 @@ public:
|
||||
uint32 minRandomBotPvpTime, maxRandomBotPvpTime;
|
||||
uint32 randomBotsPerInterval;
|
||||
uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval;
|
||||
uint32 disabledWithoutRealPlayerLoginDelay, disabledWithoutRealPlayerLogoutDelay;
|
||||
bool randomBotJoinLfg;
|
||||
|
||||
// chat
|
||||
@@ -195,6 +221,7 @@ public:
|
||||
|
||||
bool randomBotLoginAtStartup;
|
||||
uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel;
|
||||
std::map<uint32, std::pair<uint32, uint32>> zoneBrackets;
|
||||
bool logInGroupOnly, logValuesPerTick;
|
||||
bool fleeingEnabled;
|
||||
bool summonAtInnkeepersEnabled;
|
||||
@@ -250,6 +277,7 @@ public:
|
||||
uint32 iterationsPerTick;
|
||||
|
||||
std::mutex m_logMtx;
|
||||
std::vector<std::string> tradeActionExcludedPrefixes;
|
||||
std::vector<std::string> allowedLogFiles;
|
||||
std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles;
|
||||
|
||||
@@ -259,11 +287,11 @@ public:
|
||||
struct worldBuff
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 factionId = 0;
|
||||
uint32 classId = 0;
|
||||
uint32 specId = 0;
|
||||
uint32 minLevel = 0;
|
||||
uint32 maxLevel = 0;
|
||||
uint32 factionId;
|
||||
uint32 classId;
|
||||
uint32 specId;
|
||||
uint32 minLevel;
|
||||
uint32 maxLevel;
|
||||
};
|
||||
|
||||
std::vector<worldBuff> worldBuffs;
|
||||
@@ -275,7 +303,7 @@ public:
|
||||
bool randomBotShowCloak;
|
||||
bool randomBotFixedLevel;
|
||||
bool disableRandomLevels;
|
||||
float playerbotsXPrate;
|
||||
float randomBotXPRate;
|
||||
uint32 randomBotAllianceRatio;
|
||||
uint32 randomBotHordeRatio;
|
||||
bool disableDeathKnightLogin;
|
||||
@@ -308,6 +336,7 @@ public:
|
||||
bool autoLearnTrainerSpells;
|
||||
bool autoDoQuests;
|
||||
bool enableNewRpgStrategy;
|
||||
std::unordered_map<NewRpgStatus, uint32> RpgStatusProbWeight;
|
||||
bool syncLevelWithPlayers;
|
||||
bool freeFood;
|
||||
bool autoLearnQuestSpells;
|
||||
@@ -330,6 +359,8 @@ public:
|
||||
bool equipmentPersistence;
|
||||
int32 equipmentPersistenceLevel;
|
||||
int32 groupInvitationPermission;
|
||||
bool keepAltsInGroup = false;
|
||||
bool KeepAltsInGroup() const { return keepAltsInGroup; }
|
||||
bool allowSummonInCombat;
|
||||
bool allowSummonWhenMasterIsDead;
|
||||
bool allowSummonWhenBotIsDead;
|
||||
@@ -348,6 +379,12 @@ public:
|
||||
uint32 useFlyMountAtMinLevel;
|
||||
uint32 useFastFlyMountAtMinLevel;
|
||||
|
||||
// stagger flightpath takeoff
|
||||
uint32 delayMin;
|
||||
uint32 delayMax;
|
||||
uint32 gapMs;
|
||||
uint32 gapJitterMs;
|
||||
|
||||
std::string const GetTimestampStr();
|
||||
bool hasLog(std::string const fileName)
|
||||
{
|
||||
@@ -361,9 +398,16 @@ public:
|
||||
}
|
||||
void log(std::string const fileName, const char* str, ...);
|
||||
|
||||
void loadWorldBuff(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel);
|
||||
void loadWorldBuff();
|
||||
|
||||
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
|
||||
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
|
||||
|
||||
bool restrictHealerDPS = false;
|
||||
std::vector<uint32> restrictedHealerDPSMaps;
|
||||
bool IsRestrictedHealerDPSMap(uint32 mapId) const;
|
||||
|
||||
std::vector<uint32> excludedHunterPetFamilies;
|
||||
};
|
||||
|
||||
#define sPlayerbotAIConfig PlayerbotAIConfig::instance()
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
#include "BroadcastHelper.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "DatabaseEnv.h" // Added for gender choice
|
||||
#include <algorithm> // Added for gender choice
|
||||
#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
|
||||
class BotInitGuard
|
||||
{
|
||||
@@ -157,7 +161,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
|
||||
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
|
||||
{
|
||||
QueryResult result = PlayerbotsDatabase.Query(
|
||||
"SELECT 1 FROM playerbot_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
|
||||
"SELECT 1 FROM playerbots_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
|
||||
return result != nullptr;
|
||||
}
|
||||
|
||||
@@ -166,7 +170,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
uint32 botAccountId = holder.GetAccountId();
|
||||
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
|
||||
// allows channels to work as intended)
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
|
||||
|
||||
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
|
||||
@@ -473,11 +477,11 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master)
|
||||
if (master)
|
||||
{
|
||||
// Log a warning to indicate that the master is null
|
||||
LOG_DEBUG("mod-playerbots", "Master is null for bot with GUID: {}", bot->GetGUID().GetRawValue());
|
||||
return;
|
||||
ObjectGuid masterGuid = master->GetGUID();
|
||||
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
|
||||
master->GetGroup()->ChangeLeader(masterGuid);
|
||||
}
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
@@ -496,7 +500,10 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// Don't disband alt groups when master goes away
|
||||
// Controlled by config
|
||||
if (sPlayerbotAIConfig->KeepAltsInGroup())
|
||||
{
|
||||
uint32 account = sCharacterCache->GetCharacterAccountIdByGuid(member);
|
||||
if (!sPlayerbotAIConfig->IsInRandomAccountList(account))
|
||||
@@ -581,8 +588,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
}
|
||||
|
||||
bot->SaveToDB(false, false);
|
||||
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter());
|
||||
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
|
||||
bool addClassBot = sRandomPlayerbotMgr->IsAccountType(accountId, 2);
|
||||
if (addClassBot && master && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
|
||||
{
|
||||
// PlayerbotFactory factory(bot, master->GetLevel());
|
||||
// factory.Randomize(false);
|
||||
@@ -834,6 +841,18 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
|
||||
return "unknown command";
|
||||
}
|
||||
|
||||
// Added for gender choice : Returns the gender of an offline character: 0 = male, 1 = female.
|
||||
static uint8 GetOfflinePlayerGender(ObjectGuid guid)
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT gender FROM characters WHERE guid = {}", guid.GetCounter());
|
||||
|
||||
if (result)
|
||||
return (*result)[0].Get<uint8>(); // 0 = male, 1 = female
|
||||
|
||||
return GENDER_MALE; // fallback value
|
||||
}
|
||||
|
||||
bool PlayerbotMgr::HandlePlayerbotMgrCommand(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->enabled)
|
||||
@@ -876,15 +895,17 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
if (!*args)
|
||||
{
|
||||
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n");
|
||||
messages.push_back("usage: addclass CLASSNAME");
|
||||
messages.push_back("usage: addclass CLASSNAME [male|female|0|1]");
|
||||
return messages;
|
||||
}
|
||||
|
||||
char* cmd = strtok((char*)args, " ");
|
||||
char* charname = strtok(nullptr, " ");
|
||||
char* genderArg = strtok(nullptr, " "); // Added for gender choice [male|female|0|1] optionnel
|
||||
|
||||
if (!cmd)
|
||||
{
|
||||
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME");
|
||||
messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME or addclass CLASSNAME [male|female]");
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -1107,6 +1128,24 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
messages.push_back("Error: Invalid Class. Try again.");
|
||||
return messages;
|
||||
}
|
||||
// Added for gender choice : Parsing gender
|
||||
int8 gender = -1; // -1 = gender will be random
|
||||
if (genderArg)
|
||||
{
|
||||
std::string g = genderArg;
|
||||
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
|
||||
|
||||
if (g == "male" || g == "0")
|
||||
gender = GENDER_MALE; // 0
|
||||
else if (g == "female" || g == "1")
|
||||
gender = GENDER_FEMALE; // 1
|
||||
else
|
||||
{
|
||||
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
|
||||
return messages;
|
||||
}
|
||||
} //end
|
||||
|
||||
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
|
||||
{
|
||||
messages.push_back("Your level is too low to summon Deathknight");
|
||||
@@ -1116,6 +1155,9 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
|
||||
for (const ObjectGuid &guid: guidCache)
|
||||
{
|
||||
// If the user requested a specific gender, skip any character that doesn't match.
|
||||
if (gender != -1 && GetOfflinePlayerGender(guid) != gender)
|
||||
continue;
|
||||
if (botLoading.find(guid) != botLoading.end())
|
||||
continue;
|
||||
if (ObjectAccessor::FindConnectedPlayer(guid))
|
||||
@@ -1686,23 +1728,72 @@ void PlayerbotsMgr::RemovePlayerBotData(ObjectGuid const& guid, bool is_AI)
|
||||
|
||||
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
|
||||
{
|
||||
if (!(sPlayerbotAIConfig->enabled) || !player)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
// if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
|
||||
// if (!(sPlayerbotAIConfig->enabled) || !player)
|
||||
// {
|
||||
// return nullptr;
|
||||
// }
|
||||
auto itr = _playerbotsAIMap.find(player->GetGUID());
|
||||
if (itr != _playerbotsAIMap.end())
|
||||
{
|
||||
if (itr->second->IsBotAI())
|
||||
return reinterpret_cast<PlayerbotAI*>(itr->second);
|
||||
// // if (player->GetSession()->isLogingOut() || player->IsDuringRemoveFromWorld()) {
|
||||
// // return nullptr;
|
||||
// // }
|
||||
// auto itr = _playerbotsAIMap.find(player->GetGUID());
|
||||
// if (itr != _playerbotsAIMap.end())
|
||||
// {
|
||||
// if (itr->second->IsBotAI())
|
||||
// return reinterpret_cast<PlayerbotAI*>(itr->second);
|
||||
// }
|
||||
//
|
||||
// return nullptr;
|
||||
|
||||
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
if (!player || !sPlayerbotAIConfig->enabled)
|
||||
return nullptr;
|
||||
|
||||
// First read the GUID into a local variable, but ONLY after the check!
|
||||
ObjectGuid guid = player->GetGUID(); // <-- OK here, we know that player != nullptr
|
||||
{
|
||||
std::shared_lock rlock(_aiMutex);
|
||||
auto it = _playerbotsAIMap.find(guid);
|
||||
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
|
||||
return static_cast<PlayerbotAI*>(it->second);
|
||||
}
|
||||
|
||||
// Transient state: NEVER break the master ⇄ bots relationship here.
|
||||
if (!ObjectAccessor::FindPlayer(guid))
|
||||
{
|
||||
RemovePlayerbotAI(guid, /*removeMgrEntry=*/false);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
PlayerbotAI* PlayerbotsMgr::GetPlayerbotAIByGuid(ObjectGuid guid)
|
||||
{
|
||||
if (!sPlayerbotAIConfig->enabled)
|
||||
return nullptr;
|
||||
|
||||
std::shared_lock rlock(_aiMutex);
|
||||
auto it = _playerbotsAIMap.find(guid);
|
||||
if (it != _playerbotsAIMap.end() && it->second->IsBotAI())
|
||||
return static_cast<PlayerbotAI*>(it->second);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry /*= true*/)
|
||||
{
|
||||
std::unique_lock wlock(_aiMutex);
|
||||
|
||||
if (auto it = _playerbotsAIMap.find(guid); it != _playerbotsAIMap.end())
|
||||
{
|
||||
delete it->second;
|
||||
_playerbotsAIMap.erase(it);
|
||||
LOG_DEBUG("playerbots", "Removed stale AI for GUID {}",
|
||||
static_cast<uint64>(guid.GetRawValue()));
|
||||
}
|
||||
|
||||
if (removeMgrEntry)
|
||||
_playerbotsMgrMap.erase(guid); // we NO longer touch the relation in a "soft" purge
|
||||
}
|
||||
|
||||
PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
|
||||
{
|
||||
if (!(sPlayerbotAIConfig->enabled) || !player)
|
||||
@@ -1734,7 +1825,7 @@ void PlayerbotMgr::HandleSetSecurityKeyCommand(Player* player, const std::string
|
||||
|
||||
// Store the hashed key in the database
|
||||
PlayerbotsDatabase.Execute(
|
||||
"REPLACE INTO playerbot_account_keys (account_id, security_key) VALUES ({}, '{}')",
|
||||
"REPLACE INTO playerbots_account_keys (account_id, security_key) VALUES ({}, '{}')",
|
||||
accountId, hashedKey.str());
|
||||
|
||||
ChatHandler(player->GetSession()).PSendSysMessage("Security key set successfully.");
|
||||
@@ -1752,7 +1843,7 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
|
||||
Field* fields = result->Fetch();
|
||||
uint32 linkedAccountId = fields[0].Get<uint32>();
|
||||
|
||||
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbot_account_keys WHERE account_id = {}", linkedAccountId);
|
||||
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbots_account_keys WHERE account_id = {}", linkedAccountId);
|
||||
if (!result)
|
||||
{
|
||||
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
|
||||
@@ -1778,10 +1869,10 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
|
||||
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
PlayerbotsDatabase.Execute(
|
||||
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
|
||||
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
|
||||
accountId, linkedAccountId);
|
||||
PlayerbotsDatabase.Execute(
|
||||
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
|
||||
"INSERT IGNORE INTO playerbots_account_links (account_id, linked_account_id) VALUES ({}, {})",
|
||||
linkedAccountId, accountId);
|
||||
|
||||
ChatHandler(player->GetSession()).PSendSysMessage("Account linked successfully.");
|
||||
@@ -1790,7 +1881,7 @@ void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& a
|
||||
void PlayerbotMgr::HandleViewLinkedAccountsCommand(Player* player)
|
||||
{
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbot_account_links WHERE account_id = {}", accountId);
|
||||
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbots_account_links WHERE account_id = {}", accountId);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
@@ -1831,7 +1922,7 @@ void PlayerbotMgr::HandleUnlinkAccountCommand(Player* player, const std::string&
|
||||
uint32 linkedAccountId = fields[0].Get<uint32>();
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbot_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
|
||||
accountId, linkedAccountId, linkedAccountId, accountId);
|
||||
|
||||
ChatHandler(player->GetSession()).PSendSysMessage("Account unlinked successfully.");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "PlayerbotAIBase.h"
|
||||
#include "QueryHolder.h"
|
||||
#include "QueryResult.h"
|
||||
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
|
||||
class ChatHandler;
|
||||
class PlayerbotAI;
|
||||
@@ -114,13 +115,38 @@ public:
|
||||
void RemovePlayerBotData(ObjectGuid const& guid, bool is_AI);
|
||||
|
||||
PlayerbotAI* GetPlayerbotAI(Player* player);
|
||||
PlayerbotAI* GetPlayerbotAIByGuid(ObjectGuid guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
// void RemovePlayerbotAI(ObjectGuid const& guid); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
// removeMgrEntry = true => "hard" purge (AI + manager relation), for real logouts
|
||||
// removeMgrEntry = false => "soft" purge (AI only), for detected "stale" cases
|
||||
void RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntry = true);
|
||||
PlayerbotMgr* GetPlayerbotMgr(Player* player);
|
||||
|
||||
private:
|
||||
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsAIMap;
|
||||
std::unordered_map<ObjectGuid, PlayerbotAIBase*> _playerbotsMgrMap;
|
||||
mutable std::shared_mutex _aiMutex; // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
};
|
||||
|
||||
#define sPlayerbotsMgr PlayerbotsMgr::instance()
|
||||
|
||||
// Temporary addition If it keeps crashing, we will use them.
|
||||
// Like
|
||||
// BEFORE : PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||
// AFTER (safe) : PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(bot);
|
||||
// BEFORE : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) { ... }
|
||||
// AFTER (safe) : if (PlayerbotAI* botAI = GET_PLAYERBOT_AI_SAFE(player)) { ... }
|
||||
// --- SAFE helpers (append to PlayerbotMgr.h) ---
|
||||
inline PlayerbotAI* GET_PLAYERBOT_AI_SAFE(Player* p)
|
||||
{
|
||||
// Avoid any dereference during transient states (nullptr, teleport, flight, etc.)
|
||||
return p ? sPlayerbotsMgr->GetPlayerbotAI(p) : nullptr;
|
||||
}
|
||||
|
||||
inline PlayerbotMgr* GET_PLAYERBOT_MGR_SAFE(Player* p)
|
||||
{
|
||||
return p ? sPlayerbotsMgr->GetPlayerbotMgr(p) : nullptr;
|
||||
}
|
||||
// --- end SAFE helpers ---
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "cs_playerbots.h"
|
||||
#include "cmath"
|
||||
#include "BattleGroundTactics.h"
|
||||
|
||||
class PlayerbotsDatabaseScript : public DatabaseScript
|
||||
{
|
||||
@@ -196,33 +197,42 @@ public:
|
||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeCriteriaProgress(Player* player, AchievementCriteriaEntry const* /*criteria*/) override
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||
{
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player) && (achievement->flags == 256 || achievement->flags == 768))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* /*achievement*/) override
|
||||
{
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
|
||||
{
|
||||
if (!player->GetSession()->IsBot())
|
||||
// early return
|
||||
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
|
||||
return;
|
||||
|
||||
if (sPlayerbotAIConfig->playerbotsXPrate != 1.0)
|
||||
|
||||
// no XP multiplier, when player is no bot.
|
||||
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
return;
|
||||
|
||||
// no XP multiplier, when bot has group where leader is a real player.
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->playerbotsXPrate));
|
||||
Player* leader = group->GetLeader();
|
||||
if (leader && leader != player)
|
||||
{
|
||||
if (PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader))
|
||||
{
|
||||
if (leaderBotAI->HasRealPlayerMaster())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise apply bot XP multiplier.
|
||||
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -367,6 +377,10 @@ public:
|
||||
|
||||
void OnPlayerbotLogout(Player* player) override
|
||||
{
|
||||
// immediate purge of the bot's AI upon disconnection
|
||||
if (player && player->GetSession()->IsBot())
|
||||
sPlayerbotsMgr->RemovePlayerbotAI(player->GetGUID()); // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
|
||||
|
||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||
{
|
||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
|
||||
@@ -386,6 +400,43 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class PlayerBotsBGScript : public BGScript
|
||||
{
|
||||
public:
|
||||
PlayerBotsBGScript() : BGScript("PlayerBotsBGScript") {}
|
||||
|
||||
void OnBattlegroundStart(Battleground* bg) override
|
||||
{
|
||||
BGStrategyData data;
|
||||
|
||||
switch (bg->GetBgTypeID())
|
||||
{
|
||||
case BATTLEGROUND_WS:
|
||||
data.allianceStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, WS_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AB:
|
||||
data.allianceStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AB_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_AV:
|
||||
data.allianceStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, AV_STRATEGY_MAX - 1);
|
||||
break;
|
||||
case BATTLEGROUND_EY:
|
||||
data.allianceStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
data.hordeStrategy = urand(0, EY_STRATEGY_MAX - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bgStrategies[bg->GetInstanceID()] = data;
|
||||
}
|
||||
|
||||
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
|
||||
};
|
||||
|
||||
void AddPlayerbotsScripts()
|
||||
{
|
||||
new PlayerbotsDatabaseScript();
|
||||
@@ -394,6 +445,7 @@ void AddPlayerbotsScripts()
|
||||
new PlayerbotsServerScript();
|
||||
new PlayerbotsWorldScript();
|
||||
new PlayerbotsScript();
|
||||
new PlayerBotsBGScript();
|
||||
|
||||
AddSC_playerbots_commandscript();
|
||||
}
|
||||
|
||||
@@ -393,37 +393,118 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
|
||||
return std::move(botName);
|
||||
}
|
||||
|
||||
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
|
||||
// or determining it dynamically based on MaxRandomBots, EnablePeriodicOnlineOffline and its ratio,
|
||||
// and AddClassAccountPoolSize. The system also factors in the types of existing account, as assigned by
|
||||
// AssignAccountTypes()
|
||||
uint32 RandomPlayerbotFactory::CalculateTotalAccountCount()
|
||||
{
|
||||
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
|
||||
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size,
|
||||
// and additional class account pool size.
|
||||
// Reset account types if features are disabled
|
||||
// Reset is done here to precede needed accounts calculations
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0 || sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
{
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0)
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 1");
|
||||
LOG_INFO("playerbots", "MaxRandomBots set to 0, any RNDbot accounts (type 1) will be unassigned (type 0)");
|
||||
}
|
||||
if (sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
{
|
||||
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 0 WHERE account_type = 2");
|
||||
LOG_INFO("playerbots", "AddClassAccountPoolSize set to 0, any AddClass accounts (type 2) will be unassigned (type 0)");
|
||||
}
|
||||
|
||||
// Wait for DB to reflect the change, up to 1 second max. This is needed to make sure other logs don't show wrong info
|
||||
for (int waited = 0; waited < 1000; waited += 50)
|
||||
{
|
||||
QueryResult res = PlayerbotsDatabase.Query("SELECT COUNT(*) FROM playerbots_account_type WHERE account_type IN ({}, {})",
|
||||
sPlayerbotAIConfig->maxRandomBots == 0 ? 1 : -1,
|
||||
sPlayerbotAIConfig->addClassAccountPoolSize == 0 ? 2 : -1);
|
||||
|
||||
if (!res || res->Fetch()[0].Get<uint64>() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Extra 50ms fixed delay for safety.
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if randomBotAccountCount is set, otherwise calculate it dynamically.
|
||||
if (sPlayerbotAIConfig->randomBotAccountCount > 0)
|
||||
return sPlayerbotAIConfig->randomBotAccountCount;
|
||||
|
||||
// Avoid creating accounts if both maxRandom & ClassBots are set to zero.
|
||||
if (sPlayerbotAIConfig->maxRandomBots == 0 &&
|
||||
sPlayerbotAIConfig->addClassAccountPoolSize == 0)
|
||||
return 0;
|
||||
// Check existing account types
|
||||
uint32 existingRndBotAccounts = 0;
|
||||
uint32 existingAddClassAccounts = 0;
|
||||
uint32 existingUnassignedAccounts = 0;
|
||||
|
||||
//bool isWOTLK = sWorld->getIntConfig(CONFIG_EXPANSION) == EXPANSION_WRATH_OF_THE_LICH_KING; //not used, line marked for removal.
|
||||
QueryResult typeCheck = PlayerbotsDatabase.Query("SELECT account_type, COUNT(*) FROM playerbots_account_type GROUP BY account_type");
|
||||
if (typeCheck)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = typeCheck->Fetch();
|
||||
uint8 accountType = fields[0].Get<uint8>();
|
||||
uint32 count = fields[1].Get<uint32>();
|
||||
|
||||
// Determine divisor based on WOTLK condition
|
||||
if (accountType == 0) existingUnassignedAccounts = count;
|
||||
else if (accountType == 1) existingRndBotAccounts = count;
|
||||
else if (accountType == 2) existingAddClassAccounts = count;
|
||||
} while (typeCheck->NextRow());
|
||||
}
|
||||
|
||||
// Determine divisor based on Death Knight login eligibility and requested A&H faction ratio
|
||||
int divisor = CalculateAvailableCharsPerAccount();
|
||||
|
||||
// Calculate max bots
|
||||
int maxBots = sPlayerbotAIConfig->maxRandomBots;
|
||||
// Take perodic online - offline into account
|
||||
// Take periodic online - offline into account
|
||||
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
|
||||
{
|
||||
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
|
||||
}
|
||||
|
||||
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset
|
||||
uint32 baseAccounts = maxBots / divisor;
|
||||
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1;
|
||||
// Calculate number of accounts needed for RNDbots
|
||||
// Result is rounded up for maxBots not cleanly divisible by the divisor
|
||||
uint32 neededRndBotAccounts = (maxBots + divisor - 1) / divisor;
|
||||
uint32 neededAddClassAccounts = sPlayerbotAIConfig->addClassAccountPoolSize;
|
||||
|
||||
// Start with existing total
|
||||
uint32 existingTotal = existingRndBotAccounts + existingAddClassAccounts + existingUnassignedAccounts;
|
||||
|
||||
// Calculate shortfalls after using unassigned accounts
|
||||
uint32 availableUnassigned = existingUnassignedAccounts;
|
||||
uint32 additionalAccountsNeeded = 0;
|
||||
|
||||
// Check RNDbot needs
|
||||
if (neededRndBotAccounts > existingRndBotAccounts)
|
||||
{
|
||||
uint32 rndBotShortfall = neededRndBotAccounts - existingRndBotAccounts;
|
||||
if (rndBotShortfall <= availableUnassigned)
|
||||
availableUnassigned -= rndBotShortfall;
|
||||
else
|
||||
{
|
||||
additionalAccountsNeeded += (rndBotShortfall - availableUnassigned);
|
||||
availableUnassigned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check AddClass needs
|
||||
if (neededAddClassAccounts > existingAddClassAccounts)
|
||||
{
|
||||
uint32 addClassShortfall = neededAddClassAccounts - existingAddClassAccounts;
|
||||
if (addClassShortfall <= availableUnassigned)
|
||||
availableUnassigned -= addClassShortfall;
|
||||
else
|
||||
{
|
||||
additionalAccountsNeeded += (addClassShortfall - availableUnassigned);
|
||||
availableUnassigned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return existing total plus any additional accounts needed
|
||||
return existingTotal + additionalAccountsNeeded;
|
||||
}
|
||||
|
||||
uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount()
|
||||
@@ -475,8 +556,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
|
||||
|
||||
// First execute all the cleanup SQL commands
|
||||
// Clear playerbots_random_bots table
|
||||
// Clear playerbots_random_bots and playerbots_account_type
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots");
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_account_type");
|
||||
|
||||
// Get the database names dynamically
|
||||
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;
|
||||
@@ -486,6 +568,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
CharacterDatabase.Execute("DELETE FROM characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username LIKE '{}%%')",
|
||||
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
|
||||
|
||||
// Wait for the characters to be deleted before proceeding to dependent deletes
|
||||
while (CharacterDatabase.QueueSize())
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
|
||||
|
||||
// Clean up orphaned entries in playerbots_guild_tasks
|
||||
PlayerbotsDatabase.Execute("DELETE FROM playerbots_guild_tasks WHERE owner NOT IN (SELECT guid FROM " + characterDBName + ".characters)");
|
||||
|
||||
@@ -500,11 +589,13 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
CharacterDatabase.Execute("DELETE FROM character_achievement WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_achievement_progress WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_action WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_arena_stats WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_aura WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_entry_point WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_glyphs WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM character_homebind WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
|
||||
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
|
||||
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
|
||||
|
||||
// Clean up pet data
|
||||
CharacterDatabase.Execute("DELETE FROM character_pet WHERE owner NOT IN (SELECT guid FROM characters)");
|
||||
@@ -559,11 +650,23 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
|
||||
uint32 timer = getMSTime();
|
||||
|
||||
// After ALL deletions, make sure data is commited to DB
|
||||
LoginDatabase.Execute("COMMIT");
|
||||
CharacterDatabase.Execute("COMMIT");
|
||||
PlayerbotsDatabase.Execute("COMMIT");
|
||||
|
||||
// Wait for all pending database operations to complete
|
||||
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize())
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Extra 100ms fixed delay for safety.
|
||||
|
||||
// Flush tables to ensure all data in memory are written to disk
|
||||
LoginDatabase.Execute("FLUSH TABLES");
|
||||
CharacterDatabase.Execute("FLUSH TABLES");
|
||||
PlayerbotsDatabase.Execute("FLUSH TABLES");
|
||||
|
||||
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
|
||||
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
|
||||
World::StopNow(SHUTDOWN_EXIT_CODE);
|
||||
@@ -682,7 +785,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
LOG_DEBUG("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1, totalAccountCount);
|
||||
RandomPlayerbotFactory factory(accountId);
|
||||
|
||||
WorldSession* session = new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
WorldSession* session = new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
time_t(0), LOCALE_enUS, 0, false, false, 0, true);
|
||||
sessionBots.push_back(session);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
bool IsEmpty() { return !lastChangeTime; }
|
||||
|
||||
public:
|
||||
uint32 value;
|
||||
uint32 lastChangeTime;
|
||||
uint32 validIn;
|
||||
@@ -104,10 +103,6 @@ public:
|
||||
void LogPlayerLocation();
|
||||
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
|
||||
|
||||
private:
|
||||
//void ScaleBotActivity();
|
||||
|
||||
public:
|
||||
uint32 activeBots = 0;
|
||||
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
|
||||
bool IsRandomBot(Player* bot);
|
||||
@@ -180,6 +175,8 @@ public:
|
||||
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
|
||||
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
|
||||
std::vector<uint32> allianceFlightMasterCache;
|
||||
std::vector<uint32> hordeFlightMasterCache;
|
||||
struct LevelBracket {
|
||||
uint32 low;
|
||||
uint32 high;
|
||||
@@ -187,6 +184,11 @@ public:
|
||||
};
|
||||
std::map<uint32, LevelBracket> zone2LevelBracket;
|
||||
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
|
||||
|
||||
// Account type management
|
||||
void AssignAccountTypes();
|
||||
bool IsAccountType(uint32 accountId, uint8 accountType);
|
||||
|
||||
protected:
|
||||
void OnBotLoginInternal(Player* const bot) override;
|
||||
|
||||
@@ -206,6 +208,8 @@ private:
|
||||
time_t BgCheckTimer;
|
||||
time_t LfgCheckTimer;
|
||||
time_t PlayersCheckTimer;
|
||||
time_t RealPlayerLastTimeSeen = 0;
|
||||
time_t DelayLoginBotsTimer;
|
||||
time_t printStatsTimer;
|
||||
uint32 AddRandomBots();
|
||||
bool ProcessBot(uint32 bot);
|
||||
@@ -214,10 +218,8 @@ private:
|
||||
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
|
||||
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
|
||||
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
|
||||
|
||||
std::vector<Player*> players;
|
||||
uint32 processTicks;
|
||||
|
||||
|
||||
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
|
||||
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
|
||||
@@ -226,6 +228,12 @@ private:
|
||||
std::list<uint32> currentBots;
|
||||
uint32 bgBotsCount;
|
||||
uint32 playersLevel;
|
||||
|
||||
// Account lists
|
||||
std::vector<uint32> rndBotTypeAccounts; // Accounts marked as RNDbot (type 1)
|
||||
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
|
||||
|
||||
//void ScaleBotActivity(); // Deprecated function
|
||||
};
|
||||
|
||||
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()
|
||||
|
||||
@@ -3336,7 +3336,7 @@ void TravelMgr::LoadQuestTravelTable()
|
||||
uint32 accountId = fields[0].Get<uint32>();
|
||||
|
||||
WorldSession* session =
|
||||
new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
new WorldSession(accountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
|
||||
LOCALE_enUS, 0, false, false, 0, true);
|
||||
|
||||
std::vector<std::pair<std::pair<uint32, uint32>, uint32>> classSpecLevel;
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "SpellAuraDefines.h"
|
||||
#include "StatsWeightCalculator.h"
|
||||
#include "World.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
const uint64 diveMask = (1LL << 7) | (1LL << 44) | (1LL << 37) | (1LL << 38) | (1LL << 26) | (1LL << 30) | (1LL << 27) |
|
||||
(1LL << 33) | (1LL << 24) | (1LL << 34);
|
||||
@@ -224,24 +225,22 @@ void PlayerbotFactory::Randomize(bool incremental)
|
||||
{
|
||||
bot->resetTalents(true);
|
||||
}
|
||||
// bot->SaveToDB(false, false);
|
||||
ClearSkills();
|
||||
// bot->SaveToDB(false, false);
|
||||
ClearSpells();
|
||||
// bot->SaveToDB(false, false);
|
||||
if (!incremental)
|
||||
{
|
||||
ClearSkills();
|
||||
ClearSpells();
|
||||
ResetQuests();
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
ClearAllItems();
|
||||
}
|
||||
}
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || level < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
ClearAllItems();
|
||||
}
|
||||
ClearInventory();
|
||||
bot->RemoveAllSpellCooldown();
|
||||
UnbindInstance();
|
||||
|
||||
bot->GiveLevel(level);
|
||||
bot->InitStatsForLevel();
|
||||
bot->InitStatsForLevel(true);
|
||||
CancelAuras();
|
||||
// bot->SaveToDB(false, false);
|
||||
if (pmo)
|
||||
@@ -280,7 +279,6 @@ void PlayerbotFactory::Randomize(bool incremental)
|
||||
LOG_DEBUG("playerbots", "Initializing skills (step 1)...");
|
||||
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Skills1");
|
||||
InitSkills();
|
||||
// InitTradeSkills();
|
||||
if (pmo)
|
||||
pmo->finish();
|
||||
|
||||
@@ -337,7 +335,8 @@ void PlayerbotFactory::Randomize(bool incremental)
|
||||
if (!incremental || !sPlayerbotAIConfig->equipmentPersistence ||
|
||||
bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
|
||||
if (sPlayerbotAIConfig->incrementalGearInit || !incremental)
|
||||
InitEquipment(incremental, incremental ? false : sPlayerbotAIConfig->twoRoundsGearInit);
|
||||
}
|
||||
// bot->SaveToDB(false, false);
|
||||
if (pmo)
|
||||
@@ -415,7 +414,7 @@ void PlayerbotFactory::Randomize(bool incremental)
|
||||
|
||||
pmo = sPerformanceMonitor->start(PERF_MON_RNDBOT, "PlayerbotFactory_Consumable");
|
||||
LOG_DEBUG("playerbots", "Initializing consumables...");
|
||||
AddConsumables();
|
||||
InitConsumables();
|
||||
if (pmo)
|
||||
pmo->finish();
|
||||
|
||||
@@ -483,11 +482,8 @@ void PlayerbotFactory::Refresh()
|
||||
InitAmmo();
|
||||
InitFood();
|
||||
InitReagents();
|
||||
// InitPotions();
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
InitTalentsTree(true, true, true);
|
||||
}
|
||||
InitConsumables();
|
||||
InitPotions();
|
||||
InitPet();
|
||||
InitPetTalents();
|
||||
InitClassSpells();
|
||||
@@ -497,7 +493,10 @@ void PlayerbotFactory::Refresh()
|
||||
InitSpecialSpells();
|
||||
InitMounts();
|
||||
InitKeyring();
|
||||
InitPotions();
|
||||
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel)
|
||||
{
|
||||
InitTalentsTree(true, true, true);
|
||||
}
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->minEnchantingBotLevel)
|
||||
{
|
||||
ApplyEnchantAndGemsNew();
|
||||
@@ -511,151 +510,218 @@ void PlayerbotFactory::Refresh()
|
||||
// bot->SaveToDB(false, false);
|
||||
}
|
||||
|
||||
void PlayerbotFactory::AddConsumables()
|
||||
void PlayerbotFactory::InitConsumables()
|
||||
{
|
||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
std::vector<std::pair<uint32, uint32>> items;
|
||||
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_PRIEST:
|
||||
case CLASS_MAGE:
|
||||
case CLASS_WARLOCK:
|
||||
{
|
||||
if (level >= 5 && level < 20)
|
||||
// Discipline or Holy: Mana Oil
|
||||
if (specTab == 0 || specTab == 1)
|
||||
{
|
||||
StoreItem(CONSUM_ID_MINOR_WIZARD_OIL, 5);
|
||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
for (uint32 itemId : mana_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= 20 && level < 40)
|
||||
// Shadow: Wizard Oil
|
||||
if (specTab == 2)
|
||||
{
|
||||
StoreItem(CONSUM_ID_MINOR_MANA_OIL, 5);
|
||||
StoreItem(CONSUM_ID_MINOR_WIZARD_OIL, 5);
|
||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
for (uint32 itemId : wizard_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= 40 && level < 45)
|
||||
break;
|
||||
}
|
||||
case CLASS_MAGE:
|
||||
{
|
||||
// Always Wizard Oil
|
||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
for (uint32 itemId : wizard_oils)
|
||||
{
|
||||
StoreItem(CONSUM_ID_MINOR_MANA_OIL, 5);
|
||||
StoreItem(CONSUM_ID_WIZARD_OIL, 5);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
|
||||
if (level >= 45 && level < 52)
|
||||
break;
|
||||
}
|
||||
case CLASS_DRUID:
|
||||
{
|
||||
// Balance: Wizard Oil
|
||||
if (specTab == 0)
|
||||
{
|
||||
StoreItem(CONSUM_ID_BRILLIANT_MANA_OIL, 5);
|
||||
StoreItem(CONSUM_ID_BRILLIANT_WIZARD_OIL, 5);
|
||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
for (uint32 itemId : wizard_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (level >= 52 && level < 58)
|
||||
// Feral: Sharpening Stones & Weightstones
|
||||
else if (specTab == 1)
|
||||
{
|
||||
StoreItem(CONSUM_ID_SUPERIOR_MANA_OIL, 5);
|
||||
StoreItem(CONSUM_ID_BRILLIANT_WIZARD_OIL, 5);
|
||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
for (uint32 itemId : sharpening_stones)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
for (uint32 itemId : weightstones)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= 58)
|
||||
// Restoration: Mana Oil
|
||||
else if (specTab == 2)
|
||||
{
|
||||
StoreItem(CONSUM_ID_SUPERIOR_MANA_OIL, 5);
|
||||
StoreItem(CONSUM_ID_SUPERIOR_WIZARD_OIL, 5);
|
||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
for (uint32 itemId : mana_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_PALADIN:
|
||||
{
|
||||
// Holy: Mana Oil
|
||||
if (specTab == 0)
|
||||
{
|
||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
||||
for (uint32 itemId : mana_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Protection: Wizard Oil (Protection prioritizes Superior over Brilliant)
|
||||
else if (specTab == 1)
|
||||
{
|
||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
||||
for (uint32 itemId : wizard_oils)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 4});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Retribution: Sharpening Stones & Weightstones
|
||||
else if (specTab == 2)
|
||||
{
|
||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
for (uint32 itemId : sharpening_stones)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
for (uint32 itemId : weightstones)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_WARRIOR:
|
||||
case CLASS_HUNTER:
|
||||
{
|
||||
if (level >= 1 && level < 5)
|
||||
// Sharpening Stones & Weightstones
|
||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
||||
for (uint32 itemId : sharpening_stones)
|
||||
{
|
||||
StoreItem(CONSUM_ID_ROUGH_SHARPENING_STONE, 5);
|
||||
StoreItem(CONSUM_ID_ROUGH_WEIGHTSTONE, 5);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
|
||||
if (level >= 5 && level < 15)
|
||||
for (uint32 itemId : weightstones)
|
||||
{
|
||||
StoreItem(CONSUM_ID_COARSE_WEIGHTSTONE, 5);
|
||||
StoreItem(CONSUM_ID_COARSE_SHARPENING_STONE, 5);
|
||||
}
|
||||
|
||||
if (level >= 15 && level < 25)
|
||||
{
|
||||
StoreItem(CONSUM_ID_HEAVY_WEIGHTSTONE, 5);
|
||||
StoreItem(CONSUM_ID_HEAVY_SHARPENING_STONE, 5);
|
||||
}
|
||||
|
||||
if (level >= 25 && level < 35)
|
||||
{
|
||||
StoreItem(CONSUM_ID_SOL_SHARPENING_STONE, 5);
|
||||
StoreItem(CONSUM_ID_SOLID_WEIGHTSTONE, 5);
|
||||
}
|
||||
|
||||
if (level >= 35 && level < 50)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DENSE_WEIGHTSTONE, 5);
|
||||
StoreItem(CONSUM_ID_DENSE_SHARPENING_STONE, 5);
|
||||
}
|
||||
|
||||
if (level >= 50 && level < 60)
|
||||
{
|
||||
StoreItem(CONSUM_ID_FEL_SHARPENING_STONE, 5);
|
||||
StoreItem(CONSUM_ID_FEL_WEIGHTSTONE, 5);
|
||||
}
|
||||
|
||||
if (level >= 60)
|
||||
{
|
||||
StoreItem(CONSUM_ID_ADAMANTITE_WEIGHTSTONE, 5);
|
||||
StoreItem(CONSUM_ID_ADAMANTITE_SHARPENING_STONE, 5);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level || level > 75)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLASS_ROGUE:
|
||||
{
|
||||
if (level >= 20 && level < 28)
|
||||
// Poisons
|
||||
std::vector<uint32> instant_poisons = {INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON};
|
||||
std::vector<uint32> deadly_poisons = {DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON};
|
||||
for (uint32 itemId : deadly_poisons)
|
||||
{
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON, 5);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
|
||||
if (level >= 28 && level < 30)
|
||||
for (uint32 itemId : instant_poisons)
|
||||
{
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_II, 5);
|
||||
}
|
||||
|
||||
if (level >= 30 && level < 36)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_II, 5);
|
||||
}
|
||||
|
||||
if (level >= 36 && level < 44)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_II, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_III, 5);
|
||||
}
|
||||
|
||||
if (level >= 44 && level < 52)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_III, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_IV, 5);
|
||||
}
|
||||
if (level >= 52 && level < 60)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_IV, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_V, 5);
|
||||
}
|
||||
|
||||
if (level >= 60 && level < 62)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_V, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_VI, 5);
|
||||
}
|
||||
|
||||
if (level >= 62 && level < 68)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_VI, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_VI, 5);
|
||||
}
|
||||
|
||||
if (level >= 68)
|
||||
{
|
||||
StoreItem(CONSUM_ID_DEADLY_POISON_VII, 5);
|
||||
StoreItem(CONSUM_ID_INSTANT_POISON_VII, 5);
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > level)
|
||||
continue;
|
||||
items.push_back({itemId, 20});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (std::pair<uint32, uint32> item : items)
|
||||
{
|
||||
int count = (int)item.second - (int)bot->GetItemCount(item.first);
|
||||
if (count > 0)
|
||||
StoreItem(item.first, count);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitPetTalents()
|
||||
@@ -813,6 +879,10 @@ void PlayerbotFactory::InitPetTalents()
|
||||
void PlayerbotFactory::InitPet()
|
||||
{
|
||||
Pet* pet = bot->GetPet();
|
||||
|
||||
if (!pet && bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
|
||||
return;
|
||||
|
||||
if (!pet)
|
||||
{
|
||||
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
||||
@@ -825,6 +895,7 @@ void PlayerbotFactory::InitPet()
|
||||
std::vector<uint32> ids;
|
||||
|
||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||
|
||||
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||
{
|
||||
if (!itr->second.IsTameable(bot->CanTameExoticPets()))
|
||||
@@ -840,6 +911,12 @@ void PlayerbotFactory::InitPet()
|
||||
if (onlyWolf && itr->second.family != CREATURE_FAMILY_WOLF)
|
||||
continue;
|
||||
|
||||
// Exclude configured pet families
|
||||
if (std::find(sPlayerbotAIConfig->excludedHunterPetFamilies.begin(),
|
||||
sPlayerbotAIConfig->excludedHunterPetFamilies.end(),
|
||||
itr->second.family) != sPlayerbotAIConfig->excludedHunterPetFamilies.end())
|
||||
continue;
|
||||
|
||||
ids.push_back(itr->first);
|
||||
}
|
||||
|
||||
@@ -861,7 +938,8 @@ void PlayerbotFactory::InitPet()
|
||||
uint32 pet_number = sObjectMgr->GeneratePetNumber();
|
||||
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
|
||||
{
|
||||
bot->GetPetStable()->CurrentPet.value();
|
||||
auto petGuid = bot->GetPetStable()->CurrentPet.value(); // To correct the build warnin in VS
|
||||
// bot->GetPetStable()->CurrentPet.value();
|
||||
// bot->GetPetStable()->CurrentPet.reset();
|
||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
||||
@@ -1019,9 +1097,21 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa
|
||||
/// @todo: match current talent with template
|
||||
specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
/// @todo: fix cat druid hardcode
|
||||
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 &&
|
||||
!bot->HasAura(16931))
|
||||
specTab = 3;
|
||||
if (bot->getClass() == CLASS_DRUID && specTab == DRUID_TAB_FERAL && bot->GetLevel() >= 20)
|
||||
{
|
||||
bool isCat = !bot->HasAura(16931);
|
||||
if (!isCat && bot->GetLevel() == 20)
|
||||
{
|
||||
uint32 bearP = sPlayerbotAIConfig->randomClassSpecProb[cls][1];
|
||||
uint32 catP = sPlayerbotAIConfig->randomClassSpecProb[cls][3];
|
||||
if (urand(1, bearP + catP) <= catP)
|
||||
isCat = true;
|
||||
}
|
||||
if (isCat)
|
||||
{
|
||||
specTab = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1594,9 +1684,51 @@ void Shuffle(std::vector<uint32>& items)
|
||||
|
||||
void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
{
|
||||
if (incremental && !sPlayerbotAIConfig->incrementalGearInit)
|
||||
return;
|
||||
|
||||
if (level < 5) {
|
||||
// original items
|
||||
if (CharStartOutfitEntry const* oEntry = GetCharStartOutfitEntry(bot->getRace(), bot->getClass(), bot->getGender()))
|
||||
{
|
||||
for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j)
|
||||
{
|
||||
if (oEntry->ItemId[j] <= 0)
|
||||
continue;
|
||||
|
||||
uint32 itemId = oEntry->ItemId[j];
|
||||
|
||||
// skip hearthstone
|
||||
if (itemId == 6948)
|
||||
continue;
|
||||
|
||||
// just skip, reported in ObjectMgr::LoadItemTemplates
|
||||
ItemTemplate const* iProto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!iProto)
|
||||
continue;
|
||||
|
||||
// BuyCount by default
|
||||
uint32 count = iProto->BuyCount;
|
||||
|
||||
// special amount for food/drink
|
||||
if (iProto->Class == ITEM_CLASS_CONSUMABLE && iProto->SubClass == ITEM_SUBCLASS_FOOD)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bot->HasItemCount(itemId, count)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bot->StoreNewItemInBestSlots(itemId, count);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<uint8, std::vector<uint32>> items;
|
||||
// int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||
|
||||
|
||||
uint32 blevel = bot->GetLevel();
|
||||
int32 delta = std::min(blevel, 10u);
|
||||
|
||||
@@ -1738,7 +1870,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
|
||||
if (incremental && oldItem)
|
||||
{
|
||||
float old_score = calculator.CalculateItem(oldItem->GetEntry());
|
||||
float old_score = calculator.CalculateItem(oldItem->GetEntry(), oldItem->GetItemRandomPropertyId());
|
||||
if (bestScoreForSlot < 1.2f * old_score)
|
||||
continue;
|
||||
}
|
||||
@@ -2200,33 +2332,15 @@ void PlayerbotFactory::InitSkills()
|
||||
//uint32 maxValue = level * 5; //not used, line marked for removal.
|
||||
bot->UpdateSkillsForLevel();
|
||||
|
||||
auto SafeLearn = [this](uint32 spellId)
|
||||
{
|
||||
if (!bot->HasSpell(spellId))
|
||||
bot->learnSpell(spellId, false, true); // Avoid duplicate attempts in DB
|
||||
};
|
||||
|
||||
// Define Riding skill according to level
|
||||
if (bot->GetLevel() >= 70)
|
||||
bot->SetSkill(SKILL_RIDING, 300, 300, 300);
|
||||
else if (bot->GetLevel() >= 60)
|
||||
bot->SetSkill(SKILL_RIDING, 225, 225, 225);
|
||||
else if (bot->GetLevel() >= 40)
|
||||
bot->SetSkill(SKILL_RIDING, 150, 150, 150);
|
||||
else if (bot->GetLevel() >= 20)
|
||||
bot->SetSkill(SKILL_RIDING, 75, 75, 75);
|
||||
else
|
||||
bot->SetSkill(SKILL_RIDING, 0, 0, 0);
|
||||
|
||||
// Safe learning of mount spells
|
||||
bot->SetSkill(SKILL_RIDING, 0, 0, 0);
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->useGroundMountAtMinLevel)
|
||||
SafeLearn(33388); // Apprentice
|
||||
bot->learnSpell(33388);
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->useFastGroundMountAtMinLevel)
|
||||
SafeLearn(33391); // Journeyman
|
||||
bot->learnSpell(33391);
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->useFlyMountAtMinLevel)
|
||||
SafeLearn(34090); // Expert
|
||||
bot->learnSpell(34090);
|
||||
if (bot->GetLevel() >= sPlayerbotAIConfig->useFastFlyMountAtMinLevel)
|
||||
SafeLearn(34091); // Artisan
|
||||
bot->learnSpell(34091);
|
||||
|
||||
uint32 skillLevel = bot->GetLevel() < 40 ? 0 : 1;
|
||||
uint32 dualWieldLevel = bot->GetLevel() < 20 ? 0 : 1;
|
||||
@@ -3180,102 +3294,97 @@ void PlayerbotFactory::InitFood()
|
||||
|
||||
void PlayerbotFactory::InitReagents()
|
||||
{
|
||||
int level = bot->GetLevel();
|
||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
std::vector<std::pair<uint32, uint32>> items;
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_ROGUE:
|
||||
{
|
||||
std::vector<int> instant_poison_ids = {43231, 43230, 21927, 8928, 8927, 8926, 6950, 6949, 6947};
|
||||
std::vector<int> deadly_poison_ids = {43233, 43232, 22054, 22053, 20844, 8985, 8984, 2893, 2892};
|
||||
for (int& itemId : deadly_poison_ids)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > bot->GetLevel())
|
||||
continue;
|
||||
items.push_back({itemId, 20}); // deadly poison
|
||||
break;
|
||||
}
|
||||
for (int& itemId : instant_poison_ids)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (proto->RequiredLevel > bot->GetLevel())
|
||||
continue;
|
||||
items.push_back({itemId, 20}); // instant poison
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
// items.push_back({46978, 1}); // Totem
|
||||
items.push_back({5175, 1}); // Earth Totem
|
||||
items.push_back({5176, 1}); // Flame Totem
|
||||
items.push_back({5177, 1}); // Water Totem
|
||||
items.push_back({5178, 1}); // Air Totem
|
||||
if (bot->GetLevel() >= 30)
|
||||
items.push_back({17030, 40}); // Ankh
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
items.push_back({6265, 20}); // shard
|
||||
break;
|
||||
case CLASS_PRIEST:
|
||||
if (level >= 48 && level < 60)
|
||||
{
|
||||
items.push_back({17028, 40}); // Wild Berries
|
||||
}
|
||||
else if (level >= 60 && level < 80)
|
||||
{
|
||||
items.push_back({17029, 40}); // Wild Berries
|
||||
}
|
||||
else if (level >= 80)
|
||||
{
|
||||
items.push_back({44615, 40}); // Wild Berries
|
||||
}
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
items.push_back({17020, 40}); // Arcane Powder
|
||||
items.push_back({17031, 40}); // portal
|
||||
items.push_back({17032, 40}); // portal
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (level >= 56)
|
||||
items.push_back({37201, 40}); // Corpse Dust
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
if (level >= 20 && level < 30)
|
||||
items.push_back({17034, 20}); // Maple Seed
|
||||
else if (level >= 30 && level < 40)
|
||||
items.push_back({17035, 20}); // Stranglethorn Seed
|
||||
else if (level >= 40 && level < 50)
|
||||
items.push_back({17036, 20}); // Ashwood Seed
|
||||
else if (level >= 50 && level < 60)
|
||||
{
|
||||
items.push_back({17034, 40});
|
||||
items.push_back({17037, 20}); // Hornbeam Seed
|
||||
items.push_back({17021, 20}); // Wild Berries
|
||||
}
|
||||
if (level >= 30 && level < 40)
|
||||
else if (level >= 60 && level < 69)
|
||||
{
|
||||
items.push_back({17035, 40});
|
||||
items.push_back({17038, 20}); // Ironwood Seed
|
||||
items.push_back({17026, 20}); // Wild Thornroot
|
||||
}
|
||||
if (level >= 40 && level < 50)
|
||||
else if (level == 69)
|
||||
{
|
||||
items.push_back({17036, 40});
|
||||
items.push_back({22147, 20}); // Flintweed Seed
|
||||
items.push_back({17026, 20}); // Wild Thornroot
|
||||
}
|
||||
if (level >= 50 && level < 60)
|
||||
else if (level >= 70 && level < 79)
|
||||
{
|
||||
items.push_back({17037, 40});
|
||||
items.push_back({17021, 40});
|
||||
items.push_back({22147, 20}); // Flintweed Seed
|
||||
items.push_back({22148, 20}); // Wild Quillvine
|
||||
}
|
||||
if (level >= 60 && level < 70)
|
||||
else if (level == 79)
|
||||
{
|
||||
items.push_back({17038, 40});
|
||||
items.push_back({17026, 40});
|
||||
items.push_back({44614, 20}); // Starleaf Seed
|
||||
items.push_back({22148, 20}); // Wild Quillvine
|
||||
}
|
||||
if (level >= 70 && level < 80)
|
||||
else if (level >= 80)
|
||||
{
|
||||
items.push_back({22147, 40});
|
||||
items.push_back({22148, 40});
|
||||
}
|
||||
if (level >= 80)
|
||||
{
|
||||
items.push_back({44614, 40});
|
||||
items.push_back({44605, 40});
|
||||
items.push_back({44614, 20}); // Starleaf Seed
|
||||
items.push_back({44605, 20}); // Wild Spineleaf
|
||||
}
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (level >= 20)
|
||||
items.push_back({17031, 20}); // Rune of Teleportation
|
||||
if (level >= 40)
|
||||
items.push_back({17032, 20}); // Rune of Portals
|
||||
if (level >= 56)
|
||||
items.push_back({17020, 20}); // Arcane Powder
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
items.push_back({21177, 100});
|
||||
if (level >= 52)
|
||||
items.push_back({21177, 80}); // Symbol of Kings
|
||||
break;
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
items.push_back({37201, 40});
|
||||
case CLASS_PRIEST:
|
||||
if (level >= 48 && level < 56)
|
||||
items.push_back({17028, 40}); // Holy Candle
|
||||
else if (level >= 56 && level < 60)
|
||||
{
|
||||
items.push_back({17028, 20}); // Holy Candle
|
||||
items.push_back({17029, 20}); // Sacred Candle
|
||||
}
|
||||
else if (level >= 60 && level < 77)
|
||||
items.push_back({17029, 40}); // Sacred Candle
|
||||
else if (level >= 77 && level < 80)
|
||||
{
|
||||
items.push_back({17029, 20}); // Sacred Candle
|
||||
items.push_back({44615, 20}); // Devout Candle
|
||||
}
|
||||
else if (level >= 80)
|
||||
items.push_back({44615, 40}); // Devout Candle
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (level >= 4)
|
||||
items.push_back({5175, 1}); // Earth Totem
|
||||
if (level >= 10)
|
||||
items.push_back({5176, 1}); // Flame Totem
|
||||
if (level >= 20)
|
||||
items.push_back({5177, 1}); // Water Totem
|
||||
if (level >= 30)
|
||||
{
|
||||
items.push_back({5178, 1}); // Air Totem
|
||||
items.push_back({17030, 20}); // Ankh
|
||||
}
|
||||
break;
|
||||
case CLASS_WARLOCK:
|
||||
items.push_back({6265, 5}); // Soul Shard
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -3288,9 +3397,75 @@ void PlayerbotFactory::InitReagents()
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::CleanupConsumables() // remove old consumables as part of randombot level-up maintenance
|
||||
{
|
||||
std::vector<Item*> itemsToDelete;
|
||||
|
||||
std::vector<Item*> items;
|
||||
for (uint32 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i)
|
||||
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
items.push_back(item);
|
||||
|
||||
for (uint32 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
|
||||
if (Bag* bag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i))
|
||||
for (uint32 j = 0; j < bag->GetBagSize(); ++j)
|
||||
if (Item* item = bag->GetItemByPos(j))
|
||||
items.push_back(item);
|
||||
|
||||
for (Item* item : items)
|
||||
{
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
if (!proto) continue;
|
||||
|
||||
// Remove ammo
|
||||
if (proto->Class == ITEM_CLASS_PROJECTILE)
|
||||
itemsToDelete.push_back(item);
|
||||
|
||||
// Remove food/drink
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE && proto->SubClass == ITEM_SUBCLASS_FOOD)
|
||||
itemsToDelete.push_back(item);
|
||||
|
||||
// Remove potions
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE && proto->SubClass == ITEM_SUBCLASS_POTION)
|
||||
itemsToDelete.push_back(item);
|
||||
|
||||
// Remove reagents
|
||||
if (proto->Class == ITEM_CLASS_REAGENT || (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_REAGENT))
|
||||
itemsToDelete.push_back(item);
|
||||
}
|
||||
|
||||
std::set<uint32> idsToDelete = {
|
||||
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
|
||||
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
|
||||
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE,
|
||||
HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE,
|
||||
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE,
|
||||
INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V,
|
||||
INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON,
|
||||
DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V,
|
||||
DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON
|
||||
};
|
||||
|
||||
for (Item* item : items)
|
||||
{
|
||||
ItemTemplate const* proto = item->GetTemplate();
|
||||
if (!proto) continue;
|
||||
|
||||
if (idsToDelete.find(proto->ItemId) != idsToDelete.end())
|
||||
itemsToDelete.push_back(item);
|
||||
}
|
||||
|
||||
for (Item* item : itemsToDelete)
|
||||
bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitGlyphs(bool increment)
|
||||
{
|
||||
bot->InitGlyphsForLevel();
|
||||
if (!increment && botAI &&
|
||||
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Get())
|
||||
return; // // Added for custom Glyphs - custom glyphs flag test
|
||||
|
||||
if (!increment)
|
||||
{
|
||||
@@ -3349,8 +3524,157 @@ void PlayerbotFactory::InitGlyphs(bool increment)
|
||||
uint8 cls = bot->getClass();
|
||||
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
|
||||
/// @todo: fix cat druid hardcode
|
||||
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
|
||||
tab = 3;
|
||||
|
||||
// Warrior PVP exceptions
|
||||
if (bot->getClass() == CLASS_WARRIOR)
|
||||
{
|
||||
// Arms PvP (spec index 3): If the bot has the Second Wind talent
|
||||
if (bot->HasAura(29838))
|
||||
tab = 3;
|
||||
// Fury PvP (spec index 4): If the bot has the Blood Craze talent
|
||||
else if (bot->HasAura(16492))
|
||||
tab = 4;
|
||||
// Protection PvP (spec index 5): If the bot has the Gag Order talent
|
||||
else if (bot->HasAura(12958))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Paladin PvP exceptions
|
||||
if (bot->getClass() == CLASS_PALADIN)
|
||||
{
|
||||
// Holy PvP (spec index 3): If the bot has the Sacred Cleansing talent
|
||||
if (bot->HasAura(53553))
|
||||
tab = 3;
|
||||
// Protection PvP (spec index 4): If the bot has the Reckoning talent
|
||||
else if (bot->HasAura(20179))
|
||||
tab = 4;
|
||||
// Retribution PvP (spec index 5): If the bot has the Divine Purpose talent
|
||||
else if (bot->HasAura(31872))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Hunter PvP exceptions
|
||||
if (bot->getClass() == CLASS_HUNTER)
|
||||
{
|
||||
// Beast Mastery PvP (spec index 3): If the bot has the Thick Hide talent
|
||||
if (bot->HasAura(19612))
|
||||
tab = 3;
|
||||
// Marksmanship PvP (spec index 4): If the bot has the Concussive Barrage talent
|
||||
else if (bot->HasAura(35102))
|
||||
tab = 4;
|
||||
// Survival PvP (spec index 5): If the bot has the Entrapment talent and does NOT have the Concussive Barrage talent
|
||||
else if (bot->HasAura(19388) && !bot->HasAura(35102))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Rogue PvP exceptions
|
||||
if (bot->getClass() == CLASS_ROGUE)
|
||||
{
|
||||
// Assassination PvP (spec index 3): If the bot has the Deadly Brew talent
|
||||
if (bot->HasAura(51626))
|
||||
tab = 3;
|
||||
// Combat PvP (spec index 4): If the bot has the Throwing Specialization talent
|
||||
else if (bot->HasAura(51679))
|
||||
tab = 4;
|
||||
// Subtlety PvP (spec index 5): If the bot has the Waylay talent
|
||||
else if (bot->HasAura(51696))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Priest PvP exceptions
|
||||
if (bot->getClass() == CLASS_PRIEST)
|
||||
{
|
||||
// Discipline PvP (spec index 3): If the bot has the Improved Mana Burn talent
|
||||
if (bot->HasAura(14772))
|
||||
tab = 3;
|
||||
// Holy PvP (spec index 4): If the bot has the Body and Soul talent
|
||||
else if (bot->HasAura(64129))
|
||||
tab = 4;
|
||||
// Shadow PvP (spec index 5): If the bot has the Improved Vampiric Embrace talent
|
||||
else if (bot->HasAura(27840))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Death Knight PvE/PvP exceptions
|
||||
if (bot->getClass() == CLASS_DEATH_KNIGHT)
|
||||
{
|
||||
// Double Aura Blood PvE (spec index 3): If the bot has both the Abomination's Might and Improved Icy Talons
|
||||
// talents
|
||||
if (bot->HasAura(53138) && bot->HasAura(55610))
|
||||
tab = 3;
|
||||
// Blood PvP (spec index 4): If the bot has the Sudden Doom talent
|
||||
else if (bot->HasAura(49529))
|
||||
tab = 4;
|
||||
// Frost PvP (spec index 5): If the bot has the Acclimation talent
|
||||
else if (bot->HasAura(50152))
|
||||
tab = 5;
|
||||
// Unholy PvP (spec index 6): If the bot has the Magic Suppression talent
|
||||
else if (bot->HasAura(49611))
|
||||
tab = 6;
|
||||
}
|
||||
|
||||
// Shaman PvP exceptions
|
||||
if (bot->getClass() == CLASS_SHAMAN)
|
||||
{
|
||||
// Elemental PvP (spec index 3): If the bot has the Astral Shift talent
|
||||
if (bot->HasAura(51479))
|
||||
tab = 3;
|
||||
// Enhancement PvP (spec index 4): If the bot has the Earthen Power talent
|
||||
else if (bot->HasAura(51524))
|
||||
tab = 4;
|
||||
// Restoration PvP (spec index 5): If the bot has the Focused Mind talent
|
||||
else if (bot->HasAura(30866))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Mage PvE/PvP exceptions
|
||||
if (bot->getClass() == CLASS_MAGE)
|
||||
{
|
||||
// Frostfire PvE (spec index 3): If the bot has both the Burnout talent and the Ice Shards talent
|
||||
if (bot->HasAura(44472) && bot->HasAura(15047))
|
||||
tab = 3;
|
||||
// Arcane PvP (spec index 4): If the bot has the Improved Blink talent
|
||||
else if (bot->HasAura(31570))
|
||||
tab = 4;
|
||||
// Fire PvP (spec index 5): If the bot has the Fiery Payback talent
|
||||
else if (bot->HasAura(64357))
|
||||
tab = 5;
|
||||
// Frost PvP (spec index 6): If the bot has the Shattered Barrier talent
|
||||
else if (bot->HasAura(54787))
|
||||
tab = 6;
|
||||
}
|
||||
|
||||
// Warlock PvP exceptions
|
||||
if (bot->getClass() == CLASS_WARLOCK)
|
||||
{
|
||||
// Affliction PvP (spec index 3): If the bot has the Improved Howl of Terror talent
|
||||
if (bot->HasAura(30057))
|
||||
tab = 3;
|
||||
// Demonology PvP (spec index 4): If the bot has both the Nemesis talent and the Intensity talent
|
||||
else if (bot->HasAura(63123) && bot->HasAura(18136))
|
||||
tab = 4;
|
||||
// Destruction PvP (spec index 5): If the bot has the Nether Protection talent
|
||||
else if (bot->HasAura(30302))
|
||||
tab = 5;
|
||||
}
|
||||
|
||||
// Druid PvE/PvP exceptions
|
||||
if (bot->getClass() == CLASS_DRUID)
|
||||
{
|
||||
// Cat PvE (spec index 3): If the bot is Feral spec, level 20 or higher, and does NOT have the Thick Hide talent
|
||||
if (tab == DRUID_TAB_FERAL && bot->GetLevel() >= 20 && !bot->HasAura(16931))
|
||||
tab = 3;
|
||||
// Balance PvP (spec index 4): If the bot has the Owlkin Frenzy talent
|
||||
else if (bot->HasAura(48393))
|
||||
tab = 4;
|
||||
// Feral PvP (spec index 5): If the bot has the Primal Tenacity talent
|
||||
else if (bot->HasAura(33957))
|
||||
tab = 5;
|
||||
// Resto PvP (spec index 6): If the bot has the Improved Barkskin talent
|
||||
else if (bot->HasAura(63411))
|
||||
tab = 6;
|
||||
}
|
||||
|
||||
std::list<uint32> glyphs;
|
||||
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
|
||||
for (ItemTemplateContainer::const_iterator i = itemTemplates->begin(); i != itemTemplates->end(); ++i)
|
||||
|
||||
@@ -45,63 +45,6 @@ enum spec : uint8
|
||||
ROLE_CDPS = 3
|
||||
};*/
|
||||
|
||||
enum PriorizedConsumables
|
||||
{
|
||||
CONSUM_ID_ROUGH_WEIGHTSTONE = 3239,
|
||||
CONSUM_ID_COARSE_WEIGHTSTONE = 3239,
|
||||
CONSUM_ID_HEAVY_WEIGHTSTONE = 3241,
|
||||
CONSUM_ID_SOLID_WEIGHTSTONE = 7965,
|
||||
CONSUM_ID_DENSE_WEIGHTSTONE = 12643,
|
||||
CONSUM_ID_FEL_WEIGHTSTONE = 28420,
|
||||
CONSUM_ID_ADAMANTITE_WEIGHTSTONE = 28421,
|
||||
CONSUM_ID_ROUGH_SHARPENING_STONE = 2862,
|
||||
CONSUM_ID_COARSE_SHARPENING_STONE = 2863,
|
||||
CONSUM_ID_HEAVY_SHARPENING_STONE = 2871,
|
||||
CONSUM_ID_SOL_SHARPENING_STONE = 7964,
|
||||
CONSUM_ID_DENSE_SHARPENING_STONE = 12404,
|
||||
CONSUM_ID_ELEMENTAL_SHARPENING_STONE = 18262,
|
||||
CONSUM_ID_CONSECRATED_SHARPENING_STONE = 23122,
|
||||
CONSUM_ID_FEL_SHARPENING_STONE = 23528,
|
||||
CONSUM_ID_ADAMANTITE_SHARPENING_STONE = 23529,
|
||||
CONSUM_ID_LINEN_BANDAGE = 1251,
|
||||
CONSUM_ID_HEAVY_LINEN_BANDAGE = 2581,
|
||||
CONSUM_ID_WOOL_BANDAGE = 3530,
|
||||
CONSUM_ID_HEAVY_WOOL_BANDAGE = 3531,
|
||||
CONSUM_ID_SILK_BANDAGE = 6450,
|
||||
CONSUM_ID_HEAVY_SILK_BANDAGE = 6451,
|
||||
CONSUM_ID_MAGEWEAVE_BANDAGE = 8544,
|
||||
CONSUM_ID_HEAVY_MAGEWEAVE_BANDAGE = 8545,
|
||||
CONSUM_ID_RUNECLOTH_BANDAGE = 14529,
|
||||
CONSUM_ID_HEAVY_RUNECLOTH_BANDAGE = 14530,
|
||||
CONSUM_ID_NETHERWEAVE_BANDAGE = 21990,
|
||||
CONSUM_ID_HEAVY_NETHERWEAVE_BANDAGE = 21991,
|
||||
CONSUM_ID_BRILLIANT_MANA_OIL = 20748,
|
||||
CONSUM_ID_MINOR_MANA_OIL = 20745,
|
||||
CONSUM_ID_SUPERIOR_MANA_OIL = 22521,
|
||||
CONSUM_ID_LESSER_MANA_OIL = 20747,
|
||||
CONSUM_ID_BRILLIANT_WIZARD_OIL = 20749,
|
||||
CONSUM_ID_MINOR_WIZARD_OIL = 20744,
|
||||
CONSUM_ID_SUPERIOR_WIZARD_OIL = 22522,
|
||||
CONSUM_ID_WIZARD_OIL = 20750,
|
||||
CONSUM_ID_LESSER_WIZARD_OIL = 20746,
|
||||
CONSUM_ID_INSTANT_POISON = 6947,
|
||||
CONSUM_ID_INSTANT_POISON_II = 6949,
|
||||
CONSUM_ID_INSTANT_POISON_III = 6950,
|
||||
CONSUM_ID_INSTANT_POISON_IV = 8926,
|
||||
CONSUM_ID_INSTANT_POISON_V = 8927,
|
||||
CONSUM_ID_INSTANT_POISON_VI = 8928,
|
||||
CONSUM_ID_INSTANT_POISON_VII = 21927,
|
||||
CONSUM_ID_DEADLY_POISON = 2892,
|
||||
CONSUM_ID_DEADLY_POISON_II = 2893,
|
||||
CONSUM_ID_DEADLY_POISON_III = 8984,
|
||||
CONSUM_ID_DEADLY_POISON_IV = 8985,
|
||||
CONSUM_ID_DEADLY_POISON_V = 20844,
|
||||
CONSUM_ID_DEADLY_POISON_VI = 22053,
|
||||
CONSUM_ID_DEADLY_POISON_VII = 22054
|
||||
};
|
||||
|
||||
#define MAX_CONSUM_ID 28
|
||||
|
||||
class PlayerbotFactory
|
||||
{
|
||||
public:
|
||||
@@ -128,8 +71,10 @@ public:
|
||||
void InitAmmo();
|
||||
static uint32 CalcMixedGearScore(uint32 gs, uint32 quality);
|
||||
void InitPetTalents();
|
||||
|
||||
void CleanupConsumables();
|
||||
void InitReagents();
|
||||
void InitConsumables();
|
||||
void InitPotions();
|
||||
void InitGlyphs(bool increment = false);
|
||||
void InitFood();
|
||||
void InitMounts();
|
||||
@@ -140,7 +85,6 @@ public:
|
||||
void InitKeyring();
|
||||
void InitReputation();
|
||||
void InitAttunementQuests();
|
||||
void InitPotions();
|
||||
|
||||
private:
|
||||
void Prepare();
|
||||
@@ -177,7 +121,6 @@ private:
|
||||
void InitGuild();
|
||||
void InitArenaTeam();
|
||||
void InitImmersive();
|
||||
void AddConsumables();
|
||||
static void AddPrevQuests(uint32 questId, std::list<uint32>& questIds);
|
||||
void LoadEnchantContainer();
|
||||
void ApplyEnchantTemplate();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "DBCStores.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PlayerbotAI.h"
|
||||
@@ -28,12 +29,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
|
||||
{
|
||||
if (proto->IsRangedWeapon())
|
||||
{
|
||||
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
float val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
stats[STATS_TYPE_RANGED_DPS] += val;
|
||||
}
|
||||
else if (proto->IsWeapon())
|
||||
{
|
||||
uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay;
|
||||
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;
|
||||
@@ -205,7 +206,7 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
|
||||
}
|
||||
}
|
||||
|
||||
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant)
|
||||
void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount)
|
||||
{
|
||||
for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s)
|
||||
{
|
||||
@@ -231,6 +232,10 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan
|
||||
}
|
||||
case ITEM_ENCHANTMENT_TYPE_STAT:
|
||||
{
|
||||
// for item random suffix
|
||||
if (!enchant_amount)
|
||||
enchant_amount = default_enchant_amount;
|
||||
|
||||
if (!enchant_amount)
|
||||
{
|
||||
break;
|
||||
@@ -250,7 +255,7 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId)
|
||||
// trinket
|
||||
switch (spellId)
|
||||
{
|
||||
case 60764: // Totem of Splintering
|
||||
case 60764: // Totem of Splintering
|
||||
if (type_ & (CollectorType::SPELL))
|
||||
return true;
|
||||
break;
|
||||
@@ -431,10 +436,10 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
|
||||
switch (itemStatType)
|
||||
{
|
||||
case ITEM_MOD_MANA:
|
||||
stats[STATS_TYPE_MANA_REGENERATION] += val / 10;
|
||||
stats[STATS_TYPE_MANA_REGENERATION] += (float)val / 10;
|
||||
break;
|
||||
case ITEM_MOD_HEALTH:
|
||||
stats[STATS_TYPE_STAMINA] += val / 15;
|
||||
stats[STATS_TYPE_STAMINA] += (float)val / 15;
|
||||
break;
|
||||
case ITEM_MOD_AGILITY:
|
||||
stats[STATS_TYPE_AGILITY] += val;
|
||||
@@ -742,11 +747,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
|
||||
}
|
||||
}
|
||||
|
||||
int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
float StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
{
|
||||
//float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
|
||||
int32 basePoints = effectInfo.BasePoints;
|
||||
int32 randomPoints = int32(effectInfo.DieSides);
|
||||
// float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal.
|
||||
float basePoints = effectInfo.BasePoints;
|
||||
int32 randomPoints = effectInfo.DieSides;
|
||||
|
||||
switch (randomPoints)
|
||||
{
|
||||
@@ -756,7 +761,7 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
|
||||
basePoints += 1;
|
||||
break;
|
||||
default:
|
||||
int32 randvalue = (1 + randomPoints) / 2;
|
||||
float randvalue = (1 + randomPoints) / 2.0f;
|
||||
basePoints += randvalue;
|
||||
break;
|
||||
}
|
||||
@@ -767,7 +772,7 @@ bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFal
|
||||
{
|
||||
if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName)
|
||||
return false;
|
||||
|
||||
|
||||
bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags);
|
||||
// strict to healer
|
||||
if (strict && (type_ & CollectorType::SPELL_HEAL))
|
||||
|
||||
@@ -66,12 +66,12 @@ public:
|
||||
void Reset();
|
||||
void CollectItemStats(ItemTemplate const* proto);
|
||||
void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1);
|
||||
void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant);
|
||||
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:
|
||||
int32 stats[STATS_TYPE_MAX];
|
||||
float stats[STATS_TYPE_MAX];
|
||||
|
||||
private:
|
||||
void CollectByItemStatType(uint32 itemStatType, int32 val);
|
||||
@@ -80,7 +80,7 @@ private:
|
||||
|
||||
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
|
||||
uint32 triggerCooldown);
|
||||
int32 AverageValue(const SpellEffectInfo& effectInfo);
|
||||
float AverageValue(const SpellEffectInfo& effectInfo);
|
||||
|
||||
private:
|
||||
CollectorType type_;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "AiFactory.h"
|
||||
#include "DBCStores.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PlayerbotAI.h"
|
||||
@@ -32,6 +33,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
|
||||
else
|
||||
type_ = CollectorType::RANGED;
|
||||
cls = player->getClass();
|
||||
lvl = player->GetLevel();
|
||||
tab = AiFactory::GetPlayerSpecTab(player);
|
||||
collector_ = std::make_unique<StatsCollector>(type_, cls);
|
||||
|
||||
@@ -59,7 +61,7 @@ void StatsWeightCalculator::Reset()
|
||||
}
|
||||
}
|
||||
|
||||
float StatsWeightCalculator::CalculateItem(uint32 itemId)
|
||||
float StatsWeightCalculator::CalculateItem(uint32 itemId, int32 randomPropertyIds)
|
||||
{
|
||||
ItemTemplate const* proto = &sObjectMgr->GetItemTemplateStore()->at(itemId);
|
||||
|
||||
@@ -70,6 +72,9 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId)
|
||||
|
||||
collector_->CollectItemStats(proto);
|
||||
|
||||
if (randomPropertyIds != 0)
|
||||
CalculateRandomProperty(randomPropertyIds, itemId);
|
||||
|
||||
if (enable_overflow_penalty_)
|
||||
ApplyOverflowPenalty(player_);
|
||||
|
||||
@@ -116,6 +121,53 @@ float StatsWeightCalculator::CalculateEnchant(uint32 enchantId)
|
||||
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);
|
||||
@@ -130,6 +182,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
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_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
|
||||
{
|
||||
@@ -293,8 +346,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
|
||||
}
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
|
||||
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
|
||||
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
|
||||
@@ -303,7 +356,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
|
||||
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
|
||||
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
|
||||
}
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
|
||||
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
|
||||
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
|
||||
{
|
||||
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
|
||||
@@ -464,9 +517,9 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
// {
|
||||
// weight_ *= 1.0;
|
||||
// }
|
||||
// double hand
|
||||
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));
|
||||
@@ -474,29 +527,37 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
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 (isDoubleHand &&
|
||||
((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
|
||||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_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 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 == DEATHKNIGHT_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 &&
|
||||
((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 == DEATHKNIGHT_TAB_BLOOD) ||
|
||||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield())))
|
||||
if (!isDoubleHand)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
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 == DEATHKNIGHT_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 ||
|
||||
@@ -505,15 +566,18 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
|
||||
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
|
||||
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
|
||||
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
{
|
||||
weight_ *= 0.5;
|
||||
weight_ *= 1.5;
|
||||
}
|
||||
|
||||
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
|
||||
(proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE))
|
||||
{
|
||||
@@ -559,12 +623,13 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
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->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
|
||||
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
|
||||
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
|
||||
hit_current += 3;
|
||||
|
||||
hit_overflow = SPELL_HIT_OVERFLOW;
|
||||
@@ -593,7 +658,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
}
|
||||
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_HIT] = std::min(collector_->stats[STATS_TYPE_HIT], validPoints);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -610,8 +675,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_EXPERTISE] =
|
||||
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], validPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +692,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
else
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], (int)validPoints);
|
||||
collector_->stats[STATS_TYPE_DEFENSE] = std::min(collector_->stats[STATS_TYPE_DEFENSE], validPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +711,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
|
||||
validPoints = 0;
|
||||
|
||||
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
|
||||
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
|
||||
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], validPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,7 +721,7 @@ void StatsWeightCalculator::ApplyWeightFinetune(Player* player)
|
||||
{
|
||||
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
|
||||
{
|
||||
float armor_penetration_current/*, armor_penetration_overflow*/; //not used, line marked for removal.
|
||||
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;
|
||||
|
||||
@@ -28,18 +28,19 @@ class StatsWeightCalculator
|
||||
public:
|
||||
StatsWeightCalculator(Player* player);
|
||||
void Reset();
|
||||
float CalculateItem(uint32 itemId);
|
||||
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:
|
||||
|
||||
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);
|
||||
|
||||
@@ -56,6 +57,7 @@ private:
|
||||
CollectorType hitOverflowType_;
|
||||
std::unique_ptr<StatsCollector> collector_;
|
||||
uint8 cls;
|
||||
uint8 lvl;
|
||||
int tab;
|
||||
bool enable_overflow_penalty_;
|
||||
bool enable_item_set_bonus_;
|
||||
|
||||
@@ -8,39 +8,89 @@
|
||||
#include "ActionContext.h"
|
||||
#include "ChatActionContext.h"
|
||||
#include "ChatTriggerContext.h"
|
||||
#include "DKAiObjectContext.h"
|
||||
#include "DruidAiObjectContext.h"
|
||||
#include "HunterAiObjectContext.h"
|
||||
#include "MageAiObjectContext.h"
|
||||
#include "PaladinAiObjectContext.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RaidUlduarTriggerContext.h"
|
||||
#include "PriestAiObjectContext.h"
|
||||
#include "RaidUlduarActionContext.h"
|
||||
#include "RaidUlduarTriggerContext.h"
|
||||
#include "RogueAiObjectContext.h"
|
||||
#include "ShamanAiObjectContext.h"
|
||||
#include "SharedValueContext.h"
|
||||
#include "StrategyContext.h"
|
||||
#include "TriggerContext.h"
|
||||
#include "ValueContext.h"
|
||||
#include "WarlockAiObjectContext.h"
|
||||
#include "WarriorAiObjectContext.h"
|
||||
#include "WorldPacketActionContext.h"
|
||||
#include "WorldPacketTriggerContext.h"
|
||||
#include "RaidStrategyContext.h"
|
||||
#include "RaidBwlActionContext.h"
|
||||
#include "RaidBwlTriggerContext.h"
|
||||
#include "RaidNaxxActionContext.h"
|
||||
#include "RaidNaxxTriggerContext.h"
|
||||
#include "RaidIccActionContext.h"
|
||||
#include "RaidIccTriggerContext.h"
|
||||
#include "RaidOsActionContext.h"
|
||||
#include "RaidOsTriggerContext.h"
|
||||
#include "RaidEoEActionContext.h"
|
||||
#include "RaidVoATriggerContext.h"
|
||||
#include "RaidOnyxiaActionContext.h"
|
||||
#include "RaidOnyxiaTriggerContext.h"
|
||||
#include "RaidVoAActionContext.h"
|
||||
#include "RaidEoETriggerContext.h"
|
||||
#include "RaidMcActionContext.h"
|
||||
#include "RaidMcTriggerContext.h"
|
||||
#include "RaidAq20ActionContext.h"
|
||||
#include "RaidAq20TriggerContext.h"
|
||||
#include "DungeonStrategyContext.h"
|
||||
#include "WotlkDungeonActionContext.h"
|
||||
#include "WotlkDungeonTriggerContext.h"
|
||||
#include "dungeons/DungeonStrategyContext.h"
|
||||
#include "dungeons/wotlk/WotlkDungeonActionContext.h"
|
||||
#include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
|
||||
#include "raids/RaidStrategyContext.h"
|
||||
#include "raids/aq20/RaidAq20ActionContext.h"
|
||||
#include "raids/aq20/RaidAq20TriggerContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlActionContext.h"
|
||||
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoEActionContext.h"
|
||||
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
|
||||
#include "raids/icecrown/RaidIccActionContext.h"
|
||||
#include "raids/icecrown/RaidIccTriggerContext.h"
|
||||
#include "raids/moltencore/RaidMcActionContext.h"
|
||||
#include "raids/moltencore/RaidMcTriggerContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxActionContext.h"
|
||||
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsActionContext.h"
|
||||
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaActionContext.h"
|
||||
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
|
||||
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
|
||||
|
||||
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
|
||||
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
|
||||
SharedNamedObjectContextList<Trigger> AiObjectContext::sharedTriggerContexts;
|
||||
SharedNamedObjectContextList<UntypedValue> AiObjectContext::sharedValueContexts;
|
||||
|
||||
AiObjectContext::AiObjectContext(PlayerbotAI* botAI, SharedNamedObjectContextList<Strategy>& sharedStrategyContext,
|
||||
SharedNamedObjectContextList<Action>& sharedActionContext,
|
||||
SharedNamedObjectContextList<Trigger>& sharedTriggerContext,
|
||||
SharedNamedObjectContextList<UntypedValue>& sharedValueContext)
|
||||
: PlayerbotAIAware(botAI),
|
||||
strategyContexts(sharedStrategyContext),
|
||||
actionContexts(sharedActionContext),
|
||||
triggerContexts(sharedTriggerContext),
|
||||
valueContexts(sharedValueContext)
|
||||
{
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildAllSharedContexts()
|
||||
{
|
||||
AiObjectContext::BuildSharedContexts();
|
||||
PriestAiObjectContext::BuildSharedContexts();
|
||||
MageAiObjectContext::BuildSharedContexts();
|
||||
WarlockAiObjectContext::BuildSharedContexts();
|
||||
WarriorAiObjectContext::BuildSharedContexts();
|
||||
ShamanAiObjectContext::BuildSharedContexts();
|
||||
PaladinAiObjectContext::BuildSharedContexts();
|
||||
DruidAiObjectContext::BuildSharedContexts();
|
||||
HunterAiObjectContext::BuildSharedContexts();
|
||||
RogueAiObjectContext::BuildSharedContexts();
|
||||
DKAiObjectContext::BuildSharedContexts();
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedContexts()
|
||||
{
|
||||
BuildSharedStrategyContexts(sharedStrategyContexts);
|
||||
BuildSharedActionContexts(sharedActionContexts);
|
||||
BuildSharedTriggerContexts(sharedTriggerContexts);
|
||||
BuildSharedValueContexts(sharedValueContexts);
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
|
||||
{
|
||||
strategyContexts.Add(new StrategyContext());
|
||||
strategyContexts.Add(new MovementStrategyContext());
|
||||
@@ -48,7 +98,10 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
strategyContexts.Add(new QuestStrategyContext());
|
||||
strategyContexts.Add(new RaidStrategyContext());
|
||||
strategyContexts.Add(new DungeonStrategyContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
|
||||
{
|
||||
actionContexts.Add(new ActionContext());
|
||||
actionContexts.Add(new ChatActionContext());
|
||||
actionContexts.Add(new WorldPacketActionContext());
|
||||
@@ -75,8 +128,12 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
actionContexts.Add(new WotlkDungeonUPActionContext());
|
||||
actionContexts.Add(new WotlkDungeonCoSActionContext());
|
||||
actionContexts.Add(new WotlkDungeonFoSActionContext());
|
||||
actionContexts.Add(new WotlkDungeonPoSActionContext());
|
||||
actionContexts.Add(new WotlkDungeonToCActionContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
|
||||
{
|
||||
triggerContexts.Add(new TriggerContext());
|
||||
triggerContexts.Add(new ChatTriggerContext());
|
||||
triggerContexts.Add(new WorldPacketTriggerContext());
|
||||
@@ -102,28 +159,14 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
|
||||
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonFosTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
|
||||
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
|
||||
}
|
||||
|
||||
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
|
||||
{
|
||||
valueContexts.Add(new ValueContext());
|
||||
|
||||
valueContexts.Add(sSharedValueContext);
|
||||
}
|
||||
|
||||
void AiObjectContext::Update()
|
||||
{
|
||||
strategyContexts.Update();
|
||||
triggerContexts.Update();
|
||||
actionContexts.Update();
|
||||
valueContexts.Update();
|
||||
}
|
||||
|
||||
void AiObjectContext::Reset()
|
||||
{
|
||||
strategyContexts.Reset();
|
||||
triggerContexts.Reset();
|
||||
actionContexts.Reset();
|
||||
valueContexts.Reset();
|
||||
}
|
||||
|
||||
std::vector<std::string> AiObjectContext::Save()
|
||||
@@ -216,5 +259,3 @@ std::string const AiObjectContext::FormatValues()
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void AiObjectContext::AddShared(NamedObjectContext<UntypedValue>* sharedValues) { valueContexts.Add(sharedValues); }
|
||||
|
||||
@@ -19,10 +19,20 @@
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
typedef Strategy* (*StrategyCreator)(PlayerbotAI* botAI);
|
||||
typedef Action* (*ActionCreator)(PlayerbotAI* botAI);
|
||||
typedef Trigger* (*TriggerCreator)(PlayerbotAI* botAI);
|
||||
typedef UntypedValue* (*ValueCreator)(PlayerbotAI* botAI);
|
||||
|
||||
class AiObjectContext : public PlayerbotAIAware
|
||||
{
|
||||
public:
|
||||
AiObjectContext(PlayerbotAI* botAI);
|
||||
static BoolCalculatedValue* custom_glyphs(PlayerbotAI* ai); // Added for cutom glyphs
|
||||
AiObjectContext(PlayerbotAI* botAI,
|
||||
SharedNamedObjectContextList<Strategy>& sharedStrategyContext = sharedStrategyContexts,
|
||||
SharedNamedObjectContextList<Action>& sharedActionContext = sharedActionContexts,
|
||||
SharedNamedObjectContextList<Trigger>& sharedTriggerContext = sharedTriggerContexts,
|
||||
SharedNamedObjectContextList<UntypedValue>& sharedValueContext = sharedValueContexts);
|
||||
virtual ~AiObjectContext() {}
|
||||
|
||||
virtual Strategy* GetStrategy(std::string const name);
|
||||
@@ -56,20 +66,30 @@ public:
|
||||
std::set<std::string> GetSupportedActions();
|
||||
std::string const FormatValues();
|
||||
|
||||
virtual void Update();
|
||||
virtual void Reset();
|
||||
virtual void AddShared(NamedObjectContext<UntypedValue>* sharedValues);
|
||||
|
||||
std::vector<std::string> Save();
|
||||
void Load(std::vector<std::string> data);
|
||||
|
||||
std::vector<std::string> performanceStack;
|
||||
|
||||
static void BuildAllSharedContexts();
|
||||
|
||||
static void BuildSharedContexts();
|
||||
static void BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts);
|
||||
static void BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts);
|
||||
static void BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts);
|
||||
static void BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts);
|
||||
|
||||
protected:
|
||||
NamedObjectContextList<Strategy> strategyContexts;
|
||||
NamedObjectContextList<Action> actionContexts;
|
||||
NamedObjectContextList<Trigger> triggerContexts;
|
||||
NamedObjectContextList<UntypedValue> valueContexts;
|
||||
|
||||
private:
|
||||
static SharedNamedObjectContextList<Strategy> sharedStrategyContexts;
|
||||
static SharedNamedObjectContextList<Action> sharedActionContexts;
|
||||
static SharedNamedObjectContextList<Trigger> sharedTriggerContexts;
|
||||
static SharedNamedObjectContextList<UntypedValue> sharedValueContexts;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Playerbots.h"
|
||||
#include "Queue.h"
|
||||
#include "Strategy.h"
|
||||
#include "Timer.h"
|
||||
|
||||
Engine::Engine(PlayerbotAI* botAI, AiObjectContext* factory) : PlayerbotAIAware(botAI), aiObjectContext(factory)
|
||||
{
|
||||
@@ -108,6 +109,8 @@ void Engine::Reset()
|
||||
}
|
||||
|
||||
multipliers.clear();
|
||||
|
||||
actionNodeFactories.creators.clear();
|
||||
}
|
||||
|
||||
void Engine::Init()
|
||||
@@ -120,9 +123,10 @@ void Engine::Init()
|
||||
strategyTypeMask |= strategy->GetType();
|
||||
strategy->InitMultipliers(multipliers);
|
||||
strategy->InitTriggers(triggers);
|
||||
|
||||
Event emptyEvent;
|
||||
MultiplyAndPush(strategy->getDefaultActions(), 0.0f, false, emptyEvent, "default");
|
||||
for (auto &iter : strategy->actionNodeFactories.creators)
|
||||
{
|
||||
actionNodeFactories.creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
|
||||
if (testMode)
|
||||
@@ -248,11 +252,9 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
|
||||
|
||||
ActionNode* Engine::CreateActionNode(std::string const name)
|
||||
{
|
||||
for (std::map<std::string, Strategy*>::iterator i = strategies.begin(); i != strategies.end(); i++)
|
||||
{
|
||||
if (ActionNode* node = i->second->GetAction(name))
|
||||
return node;
|
||||
}
|
||||
ActionNode* node = actionNodeFactories.GetContextObject(name, botAI);
|
||||
if (node)
|
||||
return node;
|
||||
|
||||
return new ActionNode(name,
|
||||
/*P*/ nullptr,
|
||||
@@ -432,6 +434,7 @@ bool Engine::HasStrategy(std::string const name) { return strategies.find(name)
|
||||
void Engine::ProcessTriggers(bool minimal)
|
||||
{
|
||||
std::unordered_map<Trigger*, Event> fires;
|
||||
uint32 now = getMSTime();
|
||||
for (std::vector<TriggerNode*>::iterator i = triggers.begin(); i != triggers.end(); i++)
|
||||
{
|
||||
TriggerNode* node = *i;
|
||||
@@ -451,7 +454,7 @@ void Engine::ProcessTriggers(bool minimal)
|
||||
if (fires.find(trigger) != fires.end())
|
||||
continue;
|
||||
|
||||
if (testMode || trigger->needCheck())
|
||||
if (testMode || trigger->needCheck(now))
|
||||
{
|
||||
if (minimal && node->getFirstRelevance() < 100)
|
||||
continue;
|
||||
@@ -114,6 +114,7 @@ protected:
|
||||
float lastRelevance;
|
||||
std::string lastAction;
|
||||
uint32 strategyTypeMask;
|
||||
NamedObjectFactoryList<ActionNode> actionNodeFactories;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -232,6 +232,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
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:
|
||||
@@ -29,7 +29,8 @@ public:
|
||||
|
||||
std::string const getQualifier() { return qualifier; }
|
||||
|
||||
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets = "{}");
|
||||
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator,
|
||||
const std::string_view brackets = "{}");
|
||||
static std::vector<std::string> getMultiQualifiers(std::string const qualifier1);
|
||||
static int32 getMultiQualifier(std::string const qualifier1, uint32 pos);
|
||||
|
||||
@@ -40,9 +41,9 @@ protected:
|
||||
template <class T>
|
||||
class NamedObjectFactory
|
||||
{
|
||||
protected:
|
||||
typedef T* (*ActionCreator)(PlayerbotAI* botAI);
|
||||
std::unordered_map<std::string, ActionCreator> creators;
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
public:
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
@@ -58,7 +59,7 @@ public:
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ActionCreator creator = creators[name];
|
||||
ObjectCreator creator = creators[name];
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
std::set<std::string> supports()
|
||||
{
|
||||
std::set<std::string> keys;
|
||||
for (typename std::unordered_map<std::string, ActionCreator>::iterator it = creators.begin();
|
||||
for (typename std::unordered_map<std::string, ObjectCreator>::iterator it = creators.begin();
|
||||
it != creators.end(); it++)
|
||||
keys.insert(it->first);
|
||||
|
||||
@@ -111,24 +112,6 @@ public:
|
||||
created.clear();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
i->second->Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
i->second->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsShared() { return shared; }
|
||||
bool IsSupportsSiblings() { return supportsSiblings; }
|
||||
|
||||
@@ -147,53 +130,93 @@ protected:
|
||||
bool supportsSiblings;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedNamedObjectContextList
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
std::vector<NamedObjectContext<T>*> contexts;
|
||||
|
||||
~SharedNamedObjectContextList()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void Add(NamedObjectContext<T>* context)
|
||||
{
|
||||
contexts.push_back(context);
|
||||
for (const auto& iter : context->creators)
|
||||
{
|
||||
creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NamedObjectContextList
|
||||
{
|
||||
public:
|
||||
virtual ~NamedObjectContextList()
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
const std::unordered_map<std::string, ObjectCreator>& creators;
|
||||
const std::vector<NamedObjectContext<T>*>& contexts;
|
||||
std::unordered_map<std::string, T*> created;
|
||||
|
||||
NamedObjectContextList(const SharedNamedObjectContextList<T>& shared)
|
||||
: creators(shared.creators), contexts(shared.contexts)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
NamedObjectContext<T>* context = *i;
|
||||
if (!context->IsShared())
|
||||
delete context;
|
||||
}
|
||||
}
|
||||
|
||||
void Add(NamedObjectContext<T>* context) { contexts.push_back(context); }
|
||||
~NamedObjectContextList()
|
||||
{
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
if (i->second)
|
||||
delete i->second;
|
||||
}
|
||||
|
||||
created.clear();
|
||||
}
|
||||
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
qualifier = name.substr(found + 2);
|
||||
name = name.substr(0, found);
|
||||
}
|
||||
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ObjectCreator creator = creators.at(name);
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
if (created.find(name) == created.end())
|
||||
{
|
||||
if (T* object = (*i)->create(name, botAI))
|
||||
return object;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
if (!(*i)->IsShared())
|
||||
(*i)->Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
(*i)->Reset();
|
||||
if (T* object = create(name, botAI))
|
||||
return created[name] = object;
|
||||
}
|
||||
return created[name];
|
||||
}
|
||||
|
||||
std::set<std::string> GetSiblings(std::string const name)
|
||||
{
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
if (!(*i)->IsSupportsSiblings())
|
||||
continue;
|
||||
@@ -213,7 +236,7 @@ public:
|
||||
std::set<std::string> supports()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||
{
|
||||
std::set<std::string> supported = (*i)->supports();
|
||||
|
||||
@@ -227,46 +250,69 @@ public:
|
||||
std::set<std::string> GetCreated()
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
||||
{
|
||||
std::set<std::string> createdKeys = (*i)->GetCreated();
|
||||
|
||||
for (std::set<std::string>::iterator j = createdKeys.begin(); j != createdKeys.end(); j++)
|
||||
result.insert(*j);
|
||||
result.insert(i->first);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<NamedObjectContext<T>*> contexts;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NamedObjectFactoryList
|
||||
{
|
||||
public:
|
||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
||||
std::vector<NamedObjectFactory<T>*> factories;
|
||||
std::unordered_map<std::string, ObjectCreator> creators;
|
||||
|
||||
virtual ~NamedObjectFactoryList()
|
||||
{
|
||||
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
for (typename std::vector<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
delete *i;
|
||||
}
|
||||
|
||||
void Add(NamedObjectFactory<T>* context) { factories.push_front(context); }
|
||||
|
||||
T* GetContextObject(std::string const& name, PlayerbotAI* botAI)
|
||||
T* create(std::string name, PlayerbotAI* botAI)
|
||||
{
|
||||
for (typename std::list<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
||||
size_t found = name.find("::");
|
||||
std::string qualifier;
|
||||
if (found != std::string::npos)
|
||||
{
|
||||
if (T* object = (*i)->create(name, botAI))
|
||||
return object;
|
||||
qualifier = name.substr(found + 2);
|
||||
name = name.substr(0, found);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (creators.find(name) == creators.end())
|
||||
return nullptr;
|
||||
|
||||
ObjectCreator creator = creators[name];
|
||||
if (!creator)
|
||||
return nullptr;
|
||||
|
||||
T* object = (*creator)(botAI);
|
||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||
if (q && found != std::string::npos)
|
||||
q->Qualify(qualifier);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<NamedObjectFactory<T>*> factories;
|
||||
void Add(NamedObjectFactory<T>* context)
|
||||
{
|
||||
factories.push_back(context);
|
||||
for (const auto& iter : context->creators)
|
||||
{
|
||||
creators[iter.first] = iter.second;
|
||||
}
|
||||
}
|
||||
|
||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
||||
{
|
||||
if (T* object = create(name, botAI))
|
||||
return object;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,7 @@ PassiveMultiplier::PassiveMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "pa
|
||||
allowedActions.push_back("nc");
|
||||
allowedActions.push_back("reset botAI");
|
||||
allowedActions.push_back("check mount state");
|
||||
allowedActions.push_back("lfg");
|
||||
}
|
||||
|
||||
if (allowedParts.empty())
|
||||
@@ -41,6 +41,7 @@ enum StrategyType : uint32
|
||||
// };
|
||||
|
||||
static float ACTION_IDLE = 0.0f;
|
||||
static float ACTION_BG = 1.0f;
|
||||
static float ACTION_DEFAULT = 5.0f;
|
||||
static float ACTION_NORMAL = 10.0f;
|
||||
static float ACTION_HIGH = 20.0f;
|
||||
@@ -68,7 +69,7 @@ public:
|
||||
void Update() {}
|
||||
void Reset() {}
|
||||
|
||||
protected:
|
||||
public:
|
||||
NamedObjectFactoryList<ActionNode> actionNodeFactories;
|
||||
};
|
||||
|
||||
@@ -32,12 +32,11 @@ Value<Unit*>* Trigger::GetTargetValue() { return context->GetValue<Unit*>(GetTar
|
||||
|
||||
Unit* Trigger::GetTarget() { return GetTargetValue()->Get(); }
|
||||
|
||||
bool Trigger::needCheck()
|
||||
bool Trigger::needCheck(uint32 now)
|
||||
{
|
||||
if (checkInterval < 2)
|
||||
return true;
|
||||
|
||||
uint32 now = getMSTime();
|
||||
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
|
||||
{
|
||||
lastCheckTime = now;
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
virtual Value<Unit*>* GetTargetValue();
|
||||
virtual std::string const GetTargetName() { return "self target"; }
|
||||
|
||||
bool needCheck();
|
||||
bool needCheck(uint32 now);
|
||||
|
||||
protected:
|
||||
int32 checkInterval;
|
||||
@@ -13,7 +13,7 @@ class PlayerbotAI;
|
||||
class AcceptBgInvitationAction : public Action
|
||||
{
|
||||
public:
|
||||
AcceptBgInvitationAction(PlayerbotAI* botAI) : Action(botAI, "accept bg invitatio") {}
|
||||
AcceptBgInvitationAction(PlayerbotAI* botAI) : Action(botAI, "accept bg invitation") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
@@ -22,8 +22,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
||||
std::string name;
|
||||
packet >> flag >> name;
|
||||
|
||||
// Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID());
|
||||
Player* inviter = ObjectAccessor::FindPlayerByName(name, true);
|
||||
Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID());
|
||||
if (!inviter)
|
||||
return false;
|
||||
|
||||
@@ -36,11 +35,17 @@ bool AcceptInvitationAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot->isAFK())
|
||||
bot->ToggleAFK();
|
||||
|
||||
WorldPacket p;
|
||||
uint32 roles_mask = 0;
|
||||
p << roles_mask;
|
||||
bot->GetSession()->HandleGroupAcceptOpcode(p);
|
||||
|
||||
if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID()))
|
||||
return false;
|
||||
|
||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
botAI->SetMaster(inviter);
|
||||
// else
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "InviteToGroupAction.h"
|
||||
#include "LeaveGroupAction.h"
|
||||
#include "LootAction.h"
|
||||
#include "LootRollAction.h"
|
||||
#include "MoveToRpgTargetAction.h"
|
||||
#include "MoveToTravelTargetAction.h"
|
||||
#include "MovementActions.h"
|
||||
@@ -63,6 +64,7 @@
|
||||
#include "WorldBuffAction.h"
|
||||
#include "XpGainAction.h"
|
||||
#include "NewRpgAction.h"
|
||||
#include "CancelChannelAction.h"
|
||||
|
||||
class PlayerbotAI;
|
||||
|
||||
@@ -189,10 +191,13 @@ public:
|
||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||
creators["roll"] = &ActionContext::roll_action;
|
||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||
|
||||
// BG Tactics
|
||||
creators["bg tactics"] = &ActionContext::bg_tactics;
|
||||
creators["bg move to start"] = &ActionContext::bg_move_to_start;
|
||||
creators["bg reset objective force"] = &ActionContext::bg_reset_objective_force;
|
||||
creators["bg move to objective"] = &ActionContext::bg_move_to_objective;
|
||||
creators["bg select objective"] = &ActionContext::bg_select_objective;
|
||||
creators["bg check objective"] = &ActionContext::bg_check_objective;
|
||||
@@ -245,10 +250,11 @@ public:
|
||||
|
||||
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
|
||||
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
|
||||
creators["new rpg go innkeeper"] = &ActionContext::new_rpg_go_innkeeper;
|
||||
creators["new rpg move random"] = &ActionContext::new_rpg_move_random;
|
||||
creators["new rpg move npc"] = &ActionContext::new_rpg_move_npc;
|
||||
creators["new rpg go camp"] = &ActionContext::new_rpg_go_camp;
|
||||
creators["new rpg wander random"] = &ActionContext::new_rpg_wander_random;
|
||||
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
|
||||
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
|
||||
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -296,6 +302,7 @@ private:
|
||||
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
|
||||
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
|
||||
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
|
||||
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
|
||||
|
||||
static Action* emote(PlayerbotAI* botAI) { return new EmoteAction(botAI); }
|
||||
static Action* talk(PlayerbotAI* botAI) { return new TalkAction(botAI); }
|
||||
@@ -372,10 +379,12 @@ private:
|
||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
// BG Tactics
|
||||
static Action* bg_tactics(PlayerbotAI* botAI) { return new BGTactics(botAI); }
|
||||
static Action* bg_move_to_start(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to start"); }
|
||||
static Action* bg_reset_objective_force(PlayerbotAI* botAI) { return new BGTactics(botAI, "reset objective force"); }
|
||||
static Action* bg_move_to_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "move to objective"); }
|
||||
static Action* bg_select_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "select objective"); }
|
||||
static Action* bg_check_objective(PlayerbotAI* botAI) { return new BGTactics(botAI, "check objective"); }
|
||||
@@ -428,10 +437,11 @@ private:
|
||||
|
||||
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
|
||||
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
||||
static Action* new_rpg_go_innkeeper(PlayerbotAI* ai) { return new NewRpgGoInnKeeperAction(ai); }
|
||||
static Action* new_rpg_move_random(PlayerbotAI* ai) { return new NewRpgMoveRandomAction(ai); }
|
||||
static Action* new_rpg_move_npc(PlayerbotAI* ai) { return new NewRpgMoveNpcAction(ai); }
|
||||
static Action* new_rpg_go_camp(PlayerbotAI* ai) { return new NewRpgGoCampAction(ai); }
|
||||
static Action* new_rpg_wander_random(PlayerbotAI* ai) { return new NewRpgWanderRandomAction(ai); }
|
||||
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
|
||||
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
|
||||
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user