Merge branch 'master' into pmon-enhancements

This commit is contained in:
Fuzz
2024-07-17 09:44:49 +10:00
77 changed files with 2086 additions and 895 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -274,11 +274,12 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
{ {
engine->addStrategy("avoid aoe"); engine->addStrategy("avoid aoe");
} }
engine->addStrategy("combat formation");
switch (player->getClass()) switch (player->getClass())
{ {
case CLASS_PRIEST: case CLASS_PRIEST:
if (tab == 2) { if (tab == 2) {
engine->addStrategies("dps", "shadow debuff", "shadow aoe", "threat", nullptr); engine->addStrategies("dps", "shadow debuff", "shadow aoe", nullptr);
} else if (tab == PRIEST_TAB_DISIPLINE) { } else if (tab == PRIEST_TAB_DISIPLINE) {
engine->addStrategies("heal", nullptr); engine->addStrategies("heal", nullptr);
} else { } else {
@@ -289,11 +290,11 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break; break;
case CLASS_MAGE: case CLASS_MAGE:
if (tab == 0) if (tab == 0)
engine->addStrategies("arcane", "arcane aoe", "threat", nullptr); engine->addStrategies("arcane", "arcane aoe", nullptr);
else if (tab == 1) else if (tab == 1)
engine->addStrategies("fire", "fire aoe", "threat", nullptr); engine->addStrategies("fire", "fire aoe", nullptr);
else else
engine->addStrategies("frost", "frost aoe", "threat", nullptr); engine->addStrategies("frost", "frost aoe", nullptr);
engine->addStrategies("dps", "dps assist", "cure", nullptr); engine->addStrategies("dps", "dps assist", "cure", nullptr);
break; break;
@@ -301,17 +302,17 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (tab == 2) if (tab == 2)
engine->addStrategies("tank", "tank assist", "aoe", "mark rti", nullptr); engine->addStrategies("tank", "tank assist", "aoe", "mark rti", nullptr);
else if (player->getLevel() < 36 || tab == 0) else if (player->getLevel() < 36 || tab == 0)
engine->addStrategies("arms", "aoe", "dps assist", "threat", /*"behind",*/ nullptr); engine->addStrategies("arms", "aoe", "dps assist",/*"behind",*/ nullptr);
else else
engine->addStrategies("fury", "aoe", "dps assist", "threat", /*"behind",*/ nullptr); engine->addStrategies("fury", "aoe", "dps assist",/*"behind",*/ nullptr);
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == 0) if (tab == 0)
engine->addStrategies("caster", "caster aoe", "bmana", "threat", nullptr); engine->addStrategies("caster", "caster aoe", "bmana",nullptr);
else if (tab == 2) else if (tab == 2)
engine->addStrategies("heal", "bmana", nullptr); engine->addStrategies("heal", "bmana", nullptr);
else else
engine->addStrategies("melee", "melee aoe", "bdps", "threat", nullptr); engine->addStrategies("melee", "melee aoe", "bdps", nullptr);
engine->addStrategies("dps assist", "cure", "totems", nullptr); engine->addStrategies("dps assist", "cure", "totems", nullptr);
break; break;
@@ -327,38 +328,41 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_DRUID: case CLASS_DRUID:
if (tab == 0) if (tab == 0)
{ {
engine->addStrategies("caster", "cure", "caster aoe", "threat", "dps assist", nullptr); engine->addStrategies("caster", "cure", "caster aoe", "dps assist", nullptr);
engine->addStrategy("caster debuff"); engine->addStrategy("caster debuff");
} }
else if (tab == 2) else if (tab == 2)
engine->addStrategies("heal", "cure", "dps assist", nullptr); engine->addStrategies("heal", "cure", "dps assist", nullptr);
else else
{ {
engine->removeStrategy("flee"); if (player->GetLevel() >= 20 && !player->HasAura(16931)/*thick hide*/) {
engine->addStrategies("cat", "dps assist", nullptr);
} else {
engine->addStrategies("bear", "tank assist", nullptr); engine->addStrategies("bear", "tank assist", nullptr);
} }
}
break; break;
case CLASS_HUNTER: case CLASS_HUNTER:
engine->addStrategies("dps", "aoe", "bdps", "threat", "dps assist", nullptr); engine->addStrategies("dps", "aoe", "bdps", "dps assist", nullptr);
engine->addStrategy("dps debuff"); engine->addStrategy("dps debuff");
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION) { if (tab == ROGUE_TAB_ASSASSINATION) {
engine->addStrategies("melee", "threat", "dps assist", "aoe", /*"behind",*/ nullptr); engine->addStrategies("melee", "dps assist", "aoe", /*"behind",*/ nullptr);
} else { } else {
engine->addStrategies("dps", "threat", "dps assist", "aoe", /*"behind",*/ nullptr); engine->addStrategies("dps", "dps assist", "aoe", /*"behind",*/ nullptr);
} }
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
engine->addStrategies("dps assist", "dps", "dps debuff", "aoe", "threat", nullptr); engine->addStrategies("dps assist", "dps", "dps debuff", "aoe", nullptr);
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (tab == 0) if (tab == 0)
engine->addStrategies("blood", "tank assist", nullptr); engine->addStrategies("blood", "tank assist", nullptr);
else if (tab == 1) else if (tab == 1)
engine->addStrategies("frost", "frost aoe", "dps assist", "threat", nullptr); engine->addStrategies("frost", "frost aoe", "dps assist", nullptr);
else else
engine->addStrategies("unholy", "unholy aoe", "dps assist", "threat", nullptr); engine->addStrategies("unholy", "unholy aoe", "dps assist", nullptr);
break; break;
} }
@@ -504,8 +508,13 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategies("dps assist", "cure", nullptr); nonCombatEngine->addStrategies("dps assist", "cure", nullptr);
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (tab == 1) if (tab == 1) {
if (player->GetLevel() >= 20 && !player->HasAura(16931)/*thick hide*/) {
nonCombatEngine->addStrategy("dps assist");
} else {
nonCombatEngine->addStrategy("tank assist"); nonCombatEngine->addStrategy("tank assist");
}
}
else else
nonCombatEngine->addStrategies("dps assist", "cure", nullptr); nonCombatEngine->addStrategies("dps assist", "cure", nullptr);
break; break;

View File

@@ -15,6 +15,7 @@
#include <sstream> #include <sstream>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <algorithm>
void split(std::vector<std::string>& dest, std::string const str, char const* delim) void split(std::vector<std::string>& dest, std::string const str, char const* delim)
{ {

View File

@@ -351,12 +351,18 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
std::string const command = holder.GetCommand(); std::string const command = holder.GetCommand();
Player* owner = holder.GetOwner(); Player* owner = holder.GetOwner();
if (!helper.ParseChatCommand(command, owner) && holder.GetType() == CHAT_MSG_WHISPER) if (!helper.ParseChatCommand(command, owner) && holder.GetType() == CHAT_MSG_WHISPER)
{
// To prevent spam caused by WIM
if (!(command.rfind("WIM", 0) == 0) &&
!(command.rfind("QHpr", 0) == 0)
)
{ {
std::ostringstream out; std::ostringstream out;
out << "Unknown command " << command; out << "Unknown command " << command;
TellMaster(out); TellMaster(out);
helper.ParseChatCommand("help"); helper.ParseChatCommand("help");
} }
}
chatCommands.pop(); chatCommands.pop();
} }
@@ -1810,7 +1816,7 @@ Player* PlayerbotAI::GetPlayer(ObjectGuid guid)
uint32 GetCreatureIdForCreatureTemplateId(uint32 creatureTemplateId) uint32 GetCreatureIdForCreatureTemplateId(uint32 creatureTemplateId)
{ {
QueryResult results = WorldDatabase.Query("SELECT guid FROM `acore_world`.`creature` WHERE id1 = {} LIMIT 1;", creatureTemplateId); QueryResult results = WorldDatabase.Query("SELECT guid FROM `creature` WHERE id1 = {} LIMIT 1;", creatureTemplateId);
if (results) { if (results) {
Field* fields = results->Fetch(); Field* fields = results->Fetch();
return fields[0].Get<uint32>(); return fields[0].Get<uint32>();
@@ -1900,9 +1906,9 @@ bool PlayerbotAI::TellMasterNoFacing(std::string const text, PlayerbotSecurityLe
return false; return false;
time_t lastSaid = whispers[text]; time_t lastSaid = whispers[text];
// Yunfan: Remove tell cooldown
// if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000) if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
// { {
whispers[text] = time(nullptr); whispers[text] = time(nullptr);
ChatMsg type = CHAT_MSG_WHISPER; ChatMsg type = CHAT_MSG_WHISPER;
@@ -1912,7 +1918,7 @@ bool PlayerbotAI::TellMasterNoFacing(std::string const text, PlayerbotSecurityLe
WorldPacket data; WorldPacket data;
ChatHandler::BuildChatPacket(data, type == CHAT_MSG_ADDON ? CHAT_MSG_PARTY : type, type == CHAT_MSG_ADDON ? LANG_ADDON : LANG_UNIVERSAL, bot, nullptr, text.c_str()); ChatHandler::BuildChatPacket(data, type == CHAT_MSG_ADDON ? CHAT_MSG_PARTY : type, type == CHAT_MSG_ADDON ? LANG_ADDON : LANG_UNIVERSAL, bot, nullptr, text.c_str());
master->SendDirectMessage(&data); master->SendDirectMessage(&data);
// } }
return true; return true;
} }

View File

@@ -53,12 +53,13 @@ bool PlayerbotAIConfig::Initialize()
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 1500); globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 1500);
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000); maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
maxMovementSearchTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxMovementSearchTime", 3); maxMovementSearchTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxMovementSearchTime", 3);
expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000); expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000);
dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 7000); dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 7000);
reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 500); reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 500);
passiveDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.PassiveDelay", 10000); passiveDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.PassiveDelay", 10000);
repeatDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.RepeatDelay", 5000); repeatDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.RepeatDelay", 2000);
errorDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ErrorDelay", 5000); errorDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ErrorDelay", 5000);
rpgDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgDelay", 10000); rpgDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgDelay", 10000);
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000); sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
@@ -94,7 +95,10 @@ bool PlayerbotAIConfig::Initialize()
autoAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoAvoidAoe", true); autoAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoAvoidAoe", true);
tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", true); tellWhenAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.TellWhenAvoidAoe", true);
randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.15f); randomGearLoweringChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomGearLoweringChance", 0.0f);
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.15f); randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.15f);
randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f); randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f);
@@ -123,7 +127,7 @@ bool PlayerbotAIConfig::Initialize()
minRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotInWorldTime", 2 * HOUR); minRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotInWorldTime", 2 * HOUR);
maxRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotInWorldTime", 12 * HOUR); maxRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotInWorldTime", 12 * HOUR);
minRandomBotRandomizeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotRandomizeTime", 2 * HOUR); minRandomBotRandomizeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotRandomizeTime", 2 * HOUR);
maxRandomBotRandomizeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomRandomizeTime", 14 * 24 * HOUR); maxRandomBotRandomizeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotRandomizeTime", 14 * 24 * HOUR);
minRandomBotChangeStrategyTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotChangeStrategyTime", 30 * MINUTE); minRandomBotChangeStrategyTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotChangeStrategyTime", 30 * MINUTE);
maxRandomBotChangeStrategyTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotChangeStrategyTime", 2 * HOUR); maxRandomBotChangeStrategyTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotChangeStrategyTime", 2 * HOUR);
minRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotReviveTime", MINUTE); minRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotReviveTime", MINUTE);
@@ -272,7 +276,11 @@ bool PlayerbotAIConfig::Initialize()
equipmentPersistence = sConfigMgr->GetOption<bool>("AiPlayerbot.EquipmentPersistence", false); equipmentPersistence = sConfigMgr->GetOption<bool>("AiPlayerbot.EquipmentPersistence", false);
equipmentPersistenceLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.EquipmentPersistenceLevel", 80); equipmentPersistenceLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.EquipmentPersistenceLevel", 80);
groupInvitationPermission = sConfigMgr->GetOption<int32>("AiPlayerbot.GroupInvitationPermission", 1); groupInvitationPermission = sConfigMgr->GetOption<int32>("AiPlayerbot.GroupInvitationPermission", 1);
botReviveWhenSummon = sConfigMgr->GetOption<int>("AiPlayerbot.BotReviveWhenSummon", 1); allowSummonInCombat = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonInCombat", true);
allowSummonWhenMasterIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenMasterIsDead", true);
allowSummonWhenBotIsDead = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowSummonWhenBotIsDead", true);
reviveBotWhenSummoned = sConfigMgr->GetOption<bool>("AiPlayerbot.ReviveBotWhenSummoned", true);
botRepairWhenSummon = sConfigMgr->GetOption<bool>("AiPlayerbot.BotRepairWhenSummon", true);
autoInitOnly = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoInitOnly", false); autoInitOnly = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoInitOnly", false);
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0); autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1); addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
@@ -292,7 +300,8 @@ bool PlayerbotAIConfig::Initialize()
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
minEnchantingBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.MinEnchantingBotLevel", 60); minEnchantingBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.MinEnchantingBotLevel", 60);
limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 1); limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 0);
limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 0);
randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5); randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5);
enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false); enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500); rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
@@ -327,6 +336,9 @@ bool PlayerbotAIConfig::Initialize()
selfBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.SelfBotLevel", 1); selfBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.SelfBotLevel", 1);
RandomPlayerbotFactory::CreateRandomBots(); RandomPlayerbotFactory::CreateRandomBots();
if (World::IsStopped()) {
return true;
}
PlayerbotFactory::Init(); PlayerbotFactory::Init();
sRandomItemMgr->Init(); sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot(); sRandomItemMgr->InitAfterAhBot();

View File

@@ -54,8 +54,8 @@ class PlayerbotAIConfig
bool enabled; bool enabled;
bool allowGuildBots, allowPlayerBots; bool allowGuildBots, allowPlayerBots;
uint32 globalCoolDown, reactDelay, maxWaitForMove, maxMovementSearchTime, expireActionTime, uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime,
dispelAuraDuration, passiveDelay, repeatDelay, expireActionTime, dispelAuraDuration, passiveDelay, repeatDelay,
errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay; errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance,
fleeDistance, tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, fleeDistance, tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance,
@@ -79,6 +79,8 @@ class PlayerbotAIConfig
std::vector<uint32> randomBotQuestIds; std::vector<uint32> randomBotQuestIds;
uint32 randomBotTeleportDistance; uint32 randomBotTeleportDistance;
float randomGearLoweringChance; float randomGearLoweringChance;
int32 randomGearQualityLimit;
int32 randomGearScoreLimit;
float randomBotMaxLevelChance; float randomBotMaxLevelChance;
float randomBotRpgChance; float randomBotRpgChance;
uint32 minRandomBots, maxRandomBots; uint32 minRandomBots, maxRandomBots;
@@ -134,6 +136,7 @@ class PlayerbotAIConfig
bool randombotsWalkingRPGInDoors; bool randombotsWalkingRPGInDoors;
uint32 minEnchantingBotLevel; uint32 minEnchantingBotLevel;
uint32 limitEnchantExpansion; uint32 limitEnchantExpansion;
uint32 limitGearExpansion;
uint32 randombotStartingLevel; uint32 randombotStartingLevel;
bool enableRotation; bool enableRotation;
uint32 rotationPoolSize; uint32 rotationPoolSize;
@@ -214,7 +217,11 @@ class PlayerbotAIConfig
bool equipmentPersistence; bool equipmentPersistence;
int32 equipmentPersistenceLevel; int32 equipmentPersistenceLevel;
int32 groupInvitationPermission; int32 groupInvitationPermission;
int32 botReviveWhenSummon; bool allowSummonInCombat;
bool allowSummonWhenMasterIsDead;
bool allowSummonWhenBotIsDead;
bool reviveBotWhenSummoned;
bool botRepairWhenSummon;
bool autoInitOnly; bool autoInitOnly;
float autoInitEquipLevelLimitRatio; float autoInitEquipLevelLimitRatio;
int32 addClassCommand; int32 addClassCommand;

View File

@@ -149,7 +149,10 @@ void PlayerbotFactory::Prepare()
{ {
if (!itemQuality) if (!itemQuality)
{ {
itemQuality = ITEM_QUALITY_RARE; uint32 gs = sPlayerbotAIConfig->randomGearScoreLimit == 0 ? 0 :
PlayerbotFactory::CalcMixedGearScore(sPlayerbotAIConfig->randomGearScoreLimit, sPlayerbotAIConfig->randomGearQualityLimit);
itemQuality = sPlayerbotAIConfig->randomGearQualityLimit;
gearScoreLimit = gs;
} }
if (bot->isDead()) if (bot->isDead())
@@ -1453,6 +1456,14 @@ void PlayerbotFactory::InitEquipment(bool incremental)
if (itemId == 46978) { // shaman earth ring totem if (itemId == 46978) { // shaman earth ring totem
continue; continue;
} }
// disable next expansion gear
if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 60 && itemId >= 23728)
continue;
if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 70 && itemId >= 35570 && itemId != 36737 && itemId != 37739 && itemId != 37740)//transition point from TBC -> WOTLK isn't as clear, and there are other wearable TBC items above 35570 but nothing of significance
continue;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto) if (!proto)
continue; continue;
@@ -3451,15 +3462,12 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
continue; continue;
} }
// disable next expansion // disable next expansion enchantments
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 69 && enchantSpell >= 25072) { if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 25072)
continue; continue;
}
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 79 && enchantSpell > 48557) { if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell > 48557)
continue; continue;
}
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{ {
@@ -3821,9 +3829,10 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
score += (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block + score += (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block +
resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration + resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration +
spell_penetration + armor + rangeDps + meleeDps) * 0.001; spell_penetration + armor + rangeDps + meleeDps) * 0.001;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) { if (cls == CLASS_HUNTER) {
// AGILITY only // AGILITY only
score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2.5 + crit * 2 + haste * 2.5 + intellect; score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2 + crit * 2 + haste * 2 + intellect;
} else if (cls == CLASS_WARLOCK || } else if (cls == CLASS_WARLOCK ||
cls == CLASS_MAGE || cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow (cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -3832,7 +3841,7 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
) { ) {
// SPELL DPS // SPELL DPS
score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration
+ hit * 1.2 + crit * 0.7 + haste * 1 + rangeDps; + hit * 1 + crit * 0.7 + haste * 1 + rangeDps;
} else if ((cls == CLASS_PALADIN && tab == 0) || // holy } else if ((cls == CLASS_PALADIN && tab == 0) || // holy
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy (cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal (cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -3840,9 +3849,9 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
) { ) {
// HEALER // HEALER
score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps; score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps;
} else if (cls == CLASS_ROGUE) { } else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) {
// AGILITY mainly (STRENGTH also) // AGILITY mainly (STRENGTH also)
score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2.5; score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2.5;
} else if ((cls == CLASS_PALADIN && tab == 2) || // retribution } else if ((cls == CLASS_PALADIN && tab == 2) || // retribution
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury (cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy
@@ -3852,20 +3861,20 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
} else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement } else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement
// STRENGTH mainly (AGILITY, INTELLECT also) // STRENGTH mainly (AGILITY, INTELLECT also)
score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + meleeDps * 5 score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + meleeDps * 5
+ hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2; + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2;
} else if ((cls == CLASS_WARRIOR && tab == 2) || } else if ((cls == CLASS_WARRIOR && tab == 2) ||
(cls == CLASS_PALADIN && tab == 1)) { (cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD // TANK WITH SHIELD
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3
+ hit * 1 + crit * 0.2 + haste * 0.5 + expertise * 3; + hit * 0.5 + crit * 0.2 + haste * 0.5 + expertise * 3;
} else if (cls == CLASS_DEATH_KNIGHT && tab == 0){ } else if (cls == CLASS_DEATH_KNIGHT && tab == 0){
// BLOOD DK TANK // BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5 + defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5
+ hit * 2 + crit * 0.5 + haste * 0.5 + expertise * 3.5; + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5;
} else { } else {
// BEAR DRUID TANK (AND FERAL DRUID...?) // BEAR DRUID TANK
score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2 score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ hit * 1 + crit * 1 + haste * 0.5 + expertise * 3; + hit * 1 + crit * 1 + haste * 0.5 + expertise * 3;
@@ -4242,9 +4251,10 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
float score = (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block + float score = (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block +
resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration + resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration +
spell_penetration + armor + dps) * 0.001; spell_penetration + armor + dps) * 0.001;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) { if (cls == CLASS_HUNTER) {
// AGILITY only // AGILITY only
score += agility * 2.5 + attack_power + armor_penetration * 2 + dps * 5 + hit * 2.5 + crit * 2 + haste * 2.5 + intellect; score += agility * 2.5 + attack_power + armor_penetration * 2 + dps * 5 + hit * 2 + crit * 2 + haste * 2.5 + intellect;
} else if (cls == CLASS_WARLOCK || } else if (cls == CLASS_WARLOCK ||
cls == CLASS_MAGE || cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow (cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -4253,7 +4263,7 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
) { ) {
// SPELL DPS // SPELL DPS
score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration
+ hit * 1.2 + crit * 0.7 + haste * 1; + hit * 1 + crit * 0.7 + haste * 1;
} else if ((cls == CLASS_PALADIN && tab == 0) || // holy } else if ((cls == CLASS_PALADIN && tab == 0) || // holy
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy (cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal (cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -4261,9 +4271,9 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
) { ) {
// HEALER // HEALER
score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1; score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1;
} else if (cls == CLASS_ROGUE) { } else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) {
// AGILITY mainly (STRENGTH also) // AGILITY mainly (STRENGTH also)
score += agility * 2 + strength + attack_power + armor_penetration * 1 + dps * 5 + hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2.5; score += agility * 2 + strength + attack_power + armor_penetration * 1 + dps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2.5;
} else if ((cls == CLASS_PALADIN && tab == 2) || // retribution } else if ((cls == CLASS_PALADIN && tab == 2) || // retribution
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury (cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy
@@ -4273,20 +4283,20 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
} else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement } else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement
// STRENGTH mainly (AGILITY, INTELLECT also) // STRENGTH mainly (AGILITY, INTELLECT also)
score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + dps * 5 score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + dps * 5
+ hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2; + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2;
} else if ((cls == CLASS_WARRIOR && tab == 2) || } else if ((cls == CLASS_WARRIOR && tab == 2) ||
(cls == CLASS_PALADIN && tab == 1)) { (cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD // TANK WITH SHIELD
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3
+ hit * 1 + crit * 0.2 + haste * 0.5 + expertise * 3; + hit * 0.5 + crit * 0.2 + haste * 0.5 + expertise * 3;
} else if (cls == CLASS_DEATH_KNIGHT && tab == 0){ } else if (cls == CLASS_DEATH_KNIGHT && tab == 0){
// BLOOD DK TANK // BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5 + defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5
+ hit * 2 + crit * 0.5 + haste * 0.5 + expertise * 3.5; + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5;
} else { } else {
// BEAR DRUID TANK (AND FERAL DRUID...?) // BEAR DRUID TANK
score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + dps * 2 score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + dps * 2
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ hit * 1 + crit * 1 + haste * 0.5 + expertise * 3; + hit * 1 + crit * 1 + haste * 0.5 + expertise * 3;
@@ -4521,9 +4531,10 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
} }
} }
float score = 0; float score = 0;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) { if (cls == CLASS_HUNTER) {
// AGILITY only // AGILITY only
score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2.5 + crit * 2 + haste * 2.5 + intellect; score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2 + crit * 2 + haste * 2.5 + intellect;
} else if (cls == CLASS_WARLOCK || } else if (cls == CLASS_WARLOCK ||
cls == CLASS_MAGE || cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow (cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -4532,7 +4543,7 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
) { ) {
// SPELL DPS // SPELL DPS
score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration
+ hit * 1.2 + crit * 0.7 + haste * 1 + rangeDps; + hit * 1 + crit * 0.7 + haste * 1 + rangeDps;
} else if ((cls == CLASS_PALADIN && tab == 0) || // holy } else if ((cls == CLASS_PALADIN && tab == 0) || // holy
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy (cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal (cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -4540,9 +4551,9 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
) { ) {
// HEALER // HEALER
score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps; score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps;
} else if (cls == CLASS_ROGUE) { } else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) {
// AGILITY mainly (STRENGTH also) // AGILITY mainly (STRENGTH also)
score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2.5; score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2.5;
} else if ((cls == CLASS_PALADIN && tab == 2) || // retribution } else if ((cls == CLASS_PALADIN && tab == 2) || // retribution
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury (cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy
@@ -4552,20 +4563,20 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
} else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement } else if ((cls == CLASS_SHAMAN && tab == 1)) { // enhancement
// STRENGTH mainly (AGILITY, INTELLECT also) // STRENGTH mainly (AGILITY, INTELLECT also)
score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + meleeDps * 5 score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + armor_penetration * 0.5 + meleeDps * 5
+ hit * 2 + crit * 1.5 + haste * 1.5 + expertise * 2; + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2;
} else if ((cls == CLASS_WARRIOR && tab == 2) || } else if ((cls == CLASS_WARRIOR && tab == 2) ||
(cls == CLASS_PALADIN && tab == 1)) { (cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD // TANK WITH SHIELD
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + defense * 2.5 + parry * 2 + dodge * 2 + resilience * 2 + block * 2 + armor * 0.3 + stamina * 3
+ hit * 1 + crit * 0.2 + haste * 0.5 + expertise * 3; + hit * 0.5 + crit * 0.2 + haste * 0.5 + expertise * 3;
} else if (cls == CLASS_DEATH_KNIGHT && tab == 0){ } else if (cls == CLASS_DEATH_KNIGHT && tab == 0){
// BLOOD DK TANK // BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2 score += strength * 1 + agility * 2 + attack_power * 0.2
+ defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5 + defense * 3.5 + parry * 2 + dodge * 2 + resilience * 2 + armor * 0.3 + stamina * 2.5
+ hit * 2 + crit * 0.5 + haste * 0.5 + expertise * 3.5; + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5;
} else { } else {
// BEAR DRUID TANK (AND FERAL DRUID...?) // BEAR DRUID TANK
score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2 score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ hit * 1 + crit * 1 + haste * 0.5 + expertise * 3; + hit * 1 + crit * 1 + haste * 0.5 + expertise * 3;

View File

@@ -74,6 +74,12 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder) void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder)
{ {
// has bot already been added?
Player* loginBot = ObjectAccessor::FindConnectedPlayer(holder.GetGuid());
if (loginBot && loginBot->IsInWorld()) {
return;
}
uint32 botAccountId = holder.GetAccountId(); uint32 botAccountId = holder.GetAccountId();
WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0), LOCALE_enUS, 0, false, false, 0, true); WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0), LOCALE_enUS, 0, false, false, 0, true);
@@ -363,6 +369,11 @@ Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const
void PlayerbotHolder::OnBotLogin(Player* const bot) void PlayerbotHolder::OnBotLogin(Player* const bot)
{ {
// Prevent duplicate login
if (playerBots.find(bot->GetGUID()) != playerBots.end()) {
return;
}
sPlayerbotsMgr->AddPlayerbotData(bot, true); sPlayerbotsMgr->AddPlayerbotData(bot, true);
playerBots[bot->GetGUID()] = bot; playerBots[bot->GetGUID()] = bot;
OnBotLoginInternal(bot); OnBotLoginInternal(bot);
@@ -564,6 +575,13 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
return "ERROR: You can not use this command on non-summoned random bot."; return "ERROR: You can not use this command on non-summoned random bot.";
} }
if (!admin) {
Player* master = ObjectAccessor::FindConnectedPlayer(masterguid);
if (master && (master->IsInCombat() || bot->IsInCombat())) {
return "ERROR: You can not use this command during combat.";
}
}
if (GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)) {
if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster())
{ {

View File

@@ -34,6 +34,8 @@ int strcmpi(char const* s1, char const* s2);
#define AI_VALUE_LAZY(type, name) context->GetValue<type>(name)->LazyGet() #define AI_VALUE_LAZY(type, name) context->GetValue<type>(name)->LazyGet()
#define AI_VALUE2_LAZY(type, name, param) context->GetValue<type>(name, param)->LazyGet() #define AI_VALUE2_LAZY(type, name, param) context->GetValue<type>(name, param)->LazyGet()
#define AI_VALUE_REF(type, name) context->GetValue<type>(name)->RefGet()
#define SET_AI_VALUE(type, name, value) context->GetValue<type>(name)->Set(value) #define SET_AI_VALUE(type, name, value) context->GetValue<type>(name)->Set(value)
#define SET_AI_VALUE2(type, name, param, value) context->GetValue<type>(name, param)->Set(value) #define SET_AI_VALUE2(type, name, param, value) context->GetValue<type>(name, param)->Set(value)
#define RESET_AI_VALUE(type, name) context->GetValue<type>(name)->Reset() #define RESET_AI_VALUE(type, name) context->GetValue<type>(name)->Reset()

View File

@@ -55,9 +55,9 @@ RandomPlayerbotFactory::RandomPlayerbotFactory(uint32 accountId) : accountId(acc
availableRaces[CLASS_PRIEST].push_back(RACE_NIGHTELF); availableRaces[CLASS_PRIEST].push_back(RACE_NIGHTELF);
availableRaces[CLASS_PRIEST].push_back(RACE_TROLL); availableRaces[CLASS_PRIEST].push_back(RACE_TROLL);
availableRaces[CLASS_PRIEST].push_back(RACE_UNDEAD_PLAYER); availableRaces[CLASS_PRIEST].push_back(RACE_UNDEAD_PLAYER);
availableRaces[CLASS_PRIEST].push_back(RACE_DRAENEI);
if(expansion >= EXPANSION_THE_BURNING_CRUSADE) if(expansion >= EXPANSION_THE_BURNING_CRUSADE)
{ {
availableRaces[CLASS_PRIEST].push_back(RACE_DRAENEI);
availableRaces[CLASS_PRIEST].push_back(RACE_BLOODELF); availableRaces[CLASS_PRIEST].push_back(RACE_BLOODELF);
} }

View File

@@ -384,6 +384,12 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
activateCheckBgQueueThread(); activateCheckBgQueueThread();
} }
if (sPlayerbotAIConfig->randomBotJoinLfg/* && !players.empty()*/)
{
if (time(nullptr) > (LfgCheckTimer + 30))
activateCheckLfgQueueThread();
}
uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100; uint32 updateBots = sPlayerbotAIConfig->randomBotsPerInterval * onlineBotFocus / 100;
uint32 maxNewBots = onlineBotCount < maxAllowedBotCount ? maxAllowedBotCount - onlineBotCount : 0; uint32 maxNewBots = onlineBotCount < maxAllowedBotCount ? maxAllowedBotCount - onlineBotCount : 0;
uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots); uint32 loginBots = std::min(sPlayerbotAIConfig->randomBotsPerInterval - updateBots, maxNewBots);

View File

@@ -112,6 +112,7 @@ class StrategyContext : public NamedObjectContext<Strategy>
creators["grind"] = &StrategyContext::grind; creators["grind"] = &StrategyContext::grind;
creators["avoid aoe"] = &StrategyContext::avoid_aoe; creators["avoid aoe"] = &StrategyContext::avoid_aoe;
creators["move random"] = &StrategyContext::move_random; creators["move random"] = &StrategyContext::move_random;
creators["combat formation"] = &StrategyContext::combat_formation;
} }
private: private:
@@ -174,6 +175,7 @@ class StrategyContext : public NamedObjectContext<Strategy>
static Strategy* grind(PlayerbotAI* botAI) { return new GrindingStrategy(botAI); } static Strategy* grind(PlayerbotAI* botAI) { return new GrindingStrategy(botAI); }
static Strategy* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeStrategy(botAI); } static Strategy* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeStrategy(botAI); }
static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); } static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); }
static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); }
}; };
class MovementStrategyContext : public NamedObjectContext<Strategy> class MovementStrategyContext : public NamedObjectContext<Strategy>

View File

@@ -16,6 +16,16 @@
class PlayerbotAI; class PlayerbotAI;
class Unit; class Unit;
class FleeInfo
{
public:
Position fromPos;
float radius;
float angle;
uint32 timestamp;
int GetAngleRangeIndex() { return (angle + 2 * M_PI) / (M_PI / 2); } // [0, 7)
};
struct CreatureData; struct CreatureData;
class UntypedValue : public AiNamedObject class UntypedValue : public AiNamedObject
@@ -37,6 +47,7 @@ class Value
virtual ~Value() { } virtual ~Value() { }
virtual T Get() = 0; virtual T Get() = 0;
virtual T LazyGet() = 0; virtual T LazyGet() = 0;
virtual T& RefGet() = 0;
virtual void Reset() { } virtual void Reset() { }
virtual void Set(T value) = 0; virtual void Set(T value) = 0;
operator T() { return Get(); } operator T() { return Get(); }
@@ -79,7 +90,26 @@ class CalculatedValue : public UntypedValue, public Value<T>
return value; return value;
} }
T& RefGet() override
{
if (checkInterval < 2) {
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(), this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
// pmo->finish();
} else {
time_t now = getMSTime();
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
{
lastCheckTime = now;
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(), this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
// pmo->finish();
}
}
return value;
}
void Set(T val) override { value = val; } void Set(T val) override { value = val; }
void Update() override {} void Update() override {}
void Reset() override { lastCheckTime = 0; } void Reset() override { lastCheckTime = 0; }
@@ -304,6 +334,7 @@ class ManualSetValue : public UntypedValue, public Value<T>
T Get() override { return value; } T Get() override { return value; }
T LazyGet() override { return value; } T LazyGet() override { return value; }
T& RefGet() override { return value; }
void Set(T val) override { value = val; } void Set(T val) override { value = val; }
void Update() override {} void Update() override {}
void Reset() override void Reset() override
@@ -326,4 +357,32 @@ class UnitManualSetValue : public ManualSetValue<Unit*>
Unit* Get() override; Unit* Get() override;
}; };
class DisperseDistanceValue : public ManualSetValue<float>
{
public:
DisperseDistanceValue(PlayerbotAI* botAI, float defaultValue = -1.0f, std::string const name = "disperse distance") :
ManualSetValue<float>(botAI, defaultValue, name) { }
};
class LastFleeAngleValue : public ManualSetValue<float>
{
public:
LastFleeAngleValue(PlayerbotAI* botAI, float defaultValue = 0.0f, std::string const name = "last flee angle") :
ManualSetValue<float>(botAI, defaultValue, name) { }
};
class LastFleeTimestampValue : public ManualSetValue<uint32>
{
public:
LastFleeTimestampValue(PlayerbotAI* botAI, uint32 defaultValue = 0, std::string const name = "last flee timestamp") :
ManualSetValue<uint32>(botAI, defaultValue, name) { }
};
class RecentlyFleeInfo : public ManualSetValue<std::list<FleeInfo>>
{
public:
RecentlyFleeInfo(PlayerbotAI* botAI, std::list<FleeInfo> defaultValue = {}, std::string const name = "recently flee info") :
ManualSetValue<std::list<FleeInfo>>(botAI, defaultValue, name) { }
};
#endif #endif

View File

@@ -89,6 +89,8 @@ class ActionContext : public NamedObjectContext<Action>
creators["flee"] = &ActionContext::flee; creators["flee"] = &ActionContext::flee;
creators["flee with pet"] = &ActionContext::flee_with_pet; creators["flee with pet"] = &ActionContext::flee_with_pet;
creators["avoid aoe"] = &ActionContext::avoid_aoe; creators["avoid aoe"] = &ActionContext::avoid_aoe;
creators["combat formation move"] = &ActionContext::combat_formation_move;
creators["disperse set"] = &ActionContext::disperse_set;
creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru;
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
creators["lifeblood"] = &ActionContext::lifeblood; creators["lifeblood"] = &ActionContext::lifeblood;
@@ -150,6 +152,7 @@ class ActionContext : public NamedObjectContext<Action>
creators["auto talents"] = &ActionContext::auto_talents; creators["auto talents"] = &ActionContext::auto_talents;
creators["auto learn spell"] = &ActionContext::auto_learn_spell; creators["auto learn spell"] = &ActionContext::auto_learn_spell;
creators["auto teleport for level"] = &ActionContext::auto_teleport_for_level; creators["auto teleport for level"] = &ActionContext::auto_teleport_for_level;
creators["auto upgrade equip"] = &ActionContext::auto_upgrade_equip;
creators["xp gain"] = &ActionContext::xp_gain; creators["xp gain"] = &ActionContext::xp_gain;
creators["invite nearby"] = &ActionContext::invite_nearby; creators["invite nearby"] = &ActionContext::invite_nearby;
creators["invite guild"] = &ActionContext::invite_guild; creators["invite guild"] = &ActionContext::invite_guild;
@@ -265,6 +268,8 @@ class ActionContext : public NamedObjectContext<Action>
static Action* flee(PlayerbotAI* botAI) { return new FleeAction(botAI); } static Action* flee(PlayerbotAI* botAI) { return new FleeAction(botAI); }
static Action* flee_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(botAI); } static Action* flee_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(botAI); }
static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); } static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); }
static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); }
static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); }
static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); }
static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); }
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); } static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
@@ -315,6 +320,7 @@ class ActionContext : public NamedObjectContext<Action>
static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); } static Action* auto_talents(PlayerbotAI* botAI) { return new AutoSetTalentsAction(botAI); }
static Action* auto_learn_spell(PlayerbotAI* botAI) { return new AutoLearnSpellAction(botAI); } static Action* auto_learn_spell(PlayerbotAI* botAI) { return new AutoLearnSpellAction(botAI); }
static Action* auto_teleport_for_level(PlayerbotAI* botAI) { return new AutoTeleportForLevelAction(botAI); } static Action* auto_teleport_for_level(PlayerbotAI* botAI) { return new AutoTeleportForLevelAction(botAI); }
static Action* auto_upgrade_equip(PlayerbotAI* botAI) { return new AutoUpgradeEquipAction(botAI); }
static Action* xp_gain(PlayerbotAI* botAI) { return new XpGainAction(botAI); } static Action* xp_gain(PlayerbotAI* botAI) { return new XpGainAction(botAI); }
static Action* invite_nearby(PlayerbotAI* botAI) { return new InviteNearbyToGroupAction(botAI); } static Action* invite_nearby(PlayerbotAI* botAI) { return new InviteNearbyToGroupAction(botAI); }
static Action* invite_guild(PlayerbotAI* botAI) { return new InviteGuildToGroupAction(botAI); } static Action* invite_guild(PlayerbotAI* botAI) { return new InviteGuildToGroupAction(botAI); }

View File

@@ -95,7 +95,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
return false; return false;
} }
if (bot->IsMounted() && bot->IsWithinLOSInMap(target) && sServerFacade->GetDistance2d(bot, target) < 40.0f) if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
{ {
WorldPacket emptyPacket; WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);

View File

@@ -24,7 +24,6 @@ bool AutoLearnSpellAction::Execute(Event event)
out << "."; out << ".";
botAI->TellMaster(out); botAI->TellMaster(out);
} }
return true; return true;
} }
@@ -181,3 +180,15 @@ void AutoLearnSpellAction::LearnSpell(uint32 spellId, std::ostringstream* out)
} }
} }
} }
bool AutoUpgradeEquipAction::Execute(Event event) {
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot)) {
return false;
}
PlayerbotFactory factory(bot, bot->GetLevel(), ITEM_QUALITY_RARE);
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) {
factory.InitEquipment(true);
}
factory.InitAmmo();
return true;
}

View File

@@ -23,4 +23,12 @@ class AutoLearnSpellAction : public Action
void LearnSpell(uint32 spellId, std::ostringstream* out); void LearnSpell(uint32 spellId, std::ostringstream* out);
}; };
class AutoUpgradeEquipAction : public Action
{
public:
AutoUpgradeEquipAction(PlayerbotAI* botAI, std::string const name = "auto upgrade equip") : Action(botAI, name) { }
bool Execute(Event event);
};
#endif #endif

View File

@@ -6,8 +6,6 @@
#include "SharedDefines.h" #include "SharedDefines.h"
bool AutoTeleportForLevelAction::Execute(Event event) { bool AutoTeleportForLevelAction::Execute(Event event) {
AutoUpgradeEquip();
if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot)) { if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot)) {
return false; return false;
} }
@@ -17,14 +15,3 @@ bool AutoTeleportForLevelAction::Execute(Event event) {
sRandomPlayerbotMgr->RandomTeleportForLevel(bot); sRandomPlayerbotMgr->RandomTeleportForLevel(bot);
return true; return true;
} }
void AutoTeleportForLevelAction::AutoUpgradeEquip() {
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot)) {
return;
}
PlayerbotFactory factory(bot, bot->GetLevel(), ITEM_QUALITY_RARE);
if (!sPlayerbotAIConfig->equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig->equipmentPersistenceLevel) {
factory.InitEquipment(true);
}
factory.InitAmmo();
}

View File

@@ -15,8 +15,6 @@ class AutoTeleportForLevelAction : public Action
AutoTeleportForLevelAction(PlayerbotAI* botAI, std::string const name = "auto teleport for level") : Action(botAI, name) { } AutoTeleportForLevelAction(PlayerbotAI* botAI, std::string const name = "auto teleport for level") : Action(botAI, name) { }
bool Execute(Event event); bool Execute(Event event);
private:
void AutoUpgradeEquip();
}; };
#endif #endif

View File

@@ -459,7 +459,7 @@ bool BGJoinAction::JoinQueue(uint32 type)
// get BG MapId // get BG MapId
uint32 bgTypeId_ = bgTypeId; uint32 bgTypeId_ = bgTypeId;
uint32 instanceId = 0; // 0 = First Available uint32 instanceId = 0; // 0 = First Available
bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID();
bool isPremade = false; bool isPremade = false;
bool isArena = false; bool isArena = false;
bool isRated = false; bool isRated = false;
@@ -483,6 +483,11 @@ bool BGJoinAction::JoinQueue(uint32 type)
return false; return false;
} }
// refresh food/regs
sRandomPlayerbotMgr->Refresh(bot);
bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID();
// in wotlk only arena requires battlemaster guid // in wotlk only arena requires battlemaster guid
ObjectGuid guid = isArena ? unit->GetGUID() : bot->GetGUID(); ObjectGuid guid = isArena ? unit->GetGUID() : bot->GetGUID();
@@ -546,8 +551,6 @@ bool BGJoinAction::JoinQueue(uint32 type)
bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->getLevel(), bot->GetName().c_str(), _bgType.c_str(), bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->getLevel(), bot->GetName().c_str(), _bgType.c_str(),
isRated ? "Rated Arena" : isArena ? "Arena" : ""); isRated ? "Rated Arena" : isArena ? "Arena" : "");
// refresh food/regs
sRandomPlayerbotMgr->Refresh(bot);
if (isArena) if (isArena)
{ {

View File

@@ -40,12 +40,16 @@ Position const WS_FLAG_HIDE_ALLIANCE_2 = { 1540.286f, 1476.026f, 352.692f, 2.91f
Position const WS_FLAG_HIDE_ALLIANCE_3 = { 1495.807f, 1466.774f, 352.350f, 1.50f }; Position const WS_FLAG_HIDE_ALLIANCE_3 = { 1495.807f, 1466.774f, 352.350f, 1.50f };
std::vector<Position> const WS_FLAG_HIDE_HORDE = { WS_FLAG_HIDE_HORDE_1 , WS_FLAG_HIDE_HORDE_2, WS_FLAG_HIDE_HORDE_3 }; std::vector<Position> const WS_FLAG_HIDE_HORDE = { WS_FLAG_HIDE_HORDE_1 , WS_FLAG_HIDE_HORDE_2, WS_FLAG_HIDE_HORDE_3 };
std::vector<Position> const WS_FLAG_HIDE_ALLIANCE = { WS_FLAG_HIDE_ALLIANCE_1 , WS_FLAG_HIDE_ALLIANCE_2, WS_FLAG_HIDE_ALLIANCE_3 }; std::vector<Position> const WS_FLAG_HIDE_ALLIANCE = { WS_FLAG_HIDE_ALLIANCE_1 , WS_FLAG_HIDE_ALLIANCE_2, WS_FLAG_HIDE_ALLIANCE_3 };
Position const AB_WAITING_POS_HORDE = { 702.884f, 703.045f, -16.115f, 0.77f }; Position const AB_WAITING_POS_HORDE = { 702.884f, 703.045f, -16.115f, 0.77f };
Position const AB_WAITING_POS_ALLIANCE = { 1286.054f, 1282.500f, -15.697f, 3.95f }; Position const AB_WAITING_POS_ALLIANCE = { 1286.054f, 1282.500f, -15.697f, 3.95f };
Position const AV_WAITING_POS_ALLIANCE = { 793.627f, -493.814f, 99.689f, 3.09f }; Position const AV_WAITING_POS_ALLIANCE = { 793.627f, -493.814f, 99.689f, 3.09f };
Position const AV_WAITING_POS_HORDE = { -1381.865f, -544.872f, 54.773f, 0.76f }; Position const AV_WAITING_POS_HORDE = { -1381.865f, -544.872f, 54.773f, 0.76f };
Position const AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE = { -492.17f, -187.077f, 57.1342f, 2.77f }; Position const AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE = { -523.105f, -182.178f, 57.956f, 2.77f };
Position const AV_STONEHEARTH_WAITING_HORDE = { 28.1264f, -302.593f, 15.076f, 2.96f }; Position const AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE = { -545.288f, -167.932f, 57.012f, 2.77f };
Position const AV_STONEHEARTH_WAITING_HORDE = { -36.399f, -306.403f, 15.565f, 2.96f };
Position const AV_STONEHEARTH_ATTACKING_HORDE = { -55.210f, -288.546f, 15.578f, 2.96f };
Position const EY_WAITING_POS_HORDE = { 1809.102f, 1540.854f, 1267.142f, 6.18f }; Position const EY_WAITING_POS_HORDE = { 1809.102f, 1540.854f, 1267.142f, 6.18f };
Position const EY_WAITING_POS_ALLIANCE = { 2526.020f, 1596.787f, 1270.127f, 3.14f }; Position const EY_WAITING_POS_ALLIANCE = { 2526.020f, 1596.787f, 1270.127f, 3.14f };
@@ -799,7 +803,16 @@ BattleBotPath vPath_AB_Farm_to_LumberMill =
BattleBotPath vPath_AV_Horde_Cave_to_Tower_Point_Crossroad = BattleBotPath vPath_AV_Horde_Cave_to_Tower_Point_Crossroad =
{ {
{ -885.928f, -536.612f, 55.1936f, nullptr }, { -1362.395f, -529.615f, 52.636f, nullptr },
{ -1327.036f, -511.374f, 51.138f, nullptr },
{ -1277.047f, -516.327f, 50.667f, nullptr },
{ -1214.901f, -529.350f, 52.251f, nullptr },
{ -1151.129f, -545.598f, 51.990f, nullptr },
{ -1085.775f, -538.719f, 47.905f, nullptr },
{ -1038.552f, -517.946f, 43.876f, nullptr },
{ -981.371f, -494.593f, 41.127f, nullptr },
{ -930.598f, -463.751f, 43.060f, nullptr },
{ -887.138f, -475.816f, 44.374f, nullptr },
{ -880.957f, -525.119f, 53.6791f, nullptr }, { -880.957f, -525.119f, 53.6791f, nullptr },
{ -839.408f, -499.746f, 49.7505f, nullptr }, { -839.408f, -499.746f, 49.7505f, nullptr },
{ -820.21f, -469.193f, 49.4085f, nullptr }, { -820.21f, -469.193f, 49.4085f, nullptr },
@@ -2262,48 +2275,48 @@ std::vector<BattleBotPath*> const vPaths_HordeMine =
&vPath_AV_Coldtooth_Mine_Entrance_to_Coldtooth_Mine_Boss, &vPath_AV_Coldtooth_Mine_Entrance_to_Coldtooth_Mine_Boss,
}; };
static uint32 AV_HordeAttackObjectives[] = static std::pair<uint32, uint32> AV_HordeAttackObjectives[] =
{ {
// Attack // Attack
{ BG_AV_NODES_STONEHEART_BUNKER }, { BG_AV_NODES_STONEHEART_BUNKER, BG_AV_OBJECT_FLAG_A_STONEHEART_BUNKER },
{ BG_AV_NODES_STONEHEART_GRAVE }, { BG_AV_NODES_STONEHEART_GRAVE, BG_AV_OBJECT_FLAG_A_STONEHEART_GRAVE },
{ BG_AV_NODES_ICEWING_BUNKER }, { BG_AV_NODES_ICEWING_BUNKER, BG_AV_OBJECT_FLAG_A_ICEWING_BUNKER },
{ BG_AV_NODES_STORMPIKE_GRAVE }, { BG_AV_NODES_STORMPIKE_GRAVE, BG_AV_OBJECT_FLAG_A_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH }, { BG_AV_NODES_DUNBALDAR_SOUTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH }, { BG_AV_NODES_DUNBALDAR_NORTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_NORTH },
{ BG_AV_NODES_FIRSTAID_STATION } { BG_AV_NODES_FIRSTAID_STATION, BG_AV_OBJECT_FLAG_A_FIRSTAID_STATION }
}; };
static uint32 AV_HordeDefendObjectives[] = static std::pair<uint32, uint32> AV_HordeDefendObjectives[] =
{ {
// Defend // Defend
{ BG_AV_NODES_FROSTWOLF_GRAVE }, { BG_AV_NODES_FROSTWOLF_GRAVE, BG_AV_OBJECT_FLAG_H_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER }, { BG_AV_NODES_FROSTWOLF_ETOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER }, { BG_AV_NODES_FROSTWOLF_WTOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_WTOWER },
{ BG_AV_NODES_TOWER_POINT }, { BG_AV_NODES_TOWER_POINT, BG_AV_OBJECT_FLAG_H_TOWER_POINT },
{ BG_AV_NODES_ICEBLOOD_TOWER }, { BG_AV_NODES_ICEBLOOD_TOWER, BG_AV_OBJECT_FLAG_H_ICEBLOOD_TOWER },
}; };
static uint32 AV_AllianceAttackObjectives[] = static std::pair<uint32, uint32> AV_AllianceAttackObjectives[] =
{ {
// Attack // Attack
{ BG_AV_NODES_ICEBLOOD_TOWER }, { BG_AV_NODES_ICEBLOOD_TOWER, BG_AV_OBJECT_FLAG_H_ICEBLOOD_TOWER},
{ BG_AV_NODES_ICEBLOOD_GRAVE }, { BG_AV_NODES_ICEBLOOD_GRAVE, BG_AV_OBJECT_FLAG_H_ICEBLOOD_GRAVE},
{ BG_AV_NODES_TOWER_POINT }, { BG_AV_NODES_TOWER_POINT, BG_AV_OBJECT_FLAG_H_TOWER_POINT },
{ BG_AV_NODES_FROSTWOLF_GRAVE }, { BG_AV_NODES_FROSTWOLF_GRAVE, BG_AV_OBJECT_FLAG_H_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER }, { BG_AV_NODES_FROSTWOLF_ETOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER }, { BG_AV_NODES_FROSTWOLF_WTOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_WTOWER },
{ BG_AV_NODES_FROSTWOLF_HUT }, { BG_AV_NODES_FROSTWOLF_HUT, BG_AV_OBJECT_FLAG_H_FROSTWOLF_HUT },
}; };
static uint32 AV_AllianceDefendObjectives[] = static std::pair<uint32, uint32> AV_AllianceDefendObjectives[] =
{ {
// Defend // Defend
{ BG_AV_NODES_STORMPIKE_GRAVE }, { BG_AV_NODES_STORMPIKE_GRAVE, BG_AV_OBJECT_FLAG_A_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH }, { BG_AV_NODES_DUNBALDAR_SOUTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH }, { BG_AV_NODES_DUNBALDAR_NORTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_NORTH },
{ BG_AV_NODES_ICEWING_BUNKER }, { BG_AV_NODES_ICEWING_BUNKER, BG_AV_OBJECT_FLAG_A_ICEWING_BUNKER },
{ BG_AV_NODES_STONEHEART_BUNKER }, { BG_AV_NODES_STONEHEART_BUNKER, BG_AV_OBJECT_FLAG_A_STONEHEART_BUNKER },
}; };
static uint32 AB_AttackObjectives[] = static uint32 AB_AttackObjectives[] =
@@ -2937,7 +2950,7 @@ bool BGTactics::selectObjective(bool reset)
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_STONEHEART_BUNKER).TotalOwnerId != TEAM_ALLIANCE && alterValleyBG->GetAVNodeInfo(BG_AV_NODES_STONEHEART_BUNKER).TotalOwnerId != TEAM_ALLIANCE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FIRSTAID_STATION).TotalOwnerId != TEAM_ALLIANCE) alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FIRSTAID_STATION).TotalOwnerId != TEAM_ALLIANCE)
{ {
if (Creature* pVanndar = bg->GetBGCreature(AV_NPC_A_BOSS)) if (Creature* pVanndar = bg->GetBGCreature(AV_CPLACE_TRIGGER17))
{ {
BgObjective = pVanndar; BgObjective = pVanndar;
endBoss = true; endBoss = true;
@@ -2952,8 +2965,9 @@ bool BGTactics::selectObjective(bool reset)
// Only go to Snowfall Graveyard if already close to it. // Only go to Snowfall Graveyard if already close to it.
// Need to fix AV script // Need to fix AV script
if (!BgObjective && supporter && (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_ALLIANCE || if (!BgObjective && supporter &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE || alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_OTHER)) (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_ALLIANCE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_OTHER))
{ {
if (GameObject* pGO = bg->GetBGObject(BG_AV_NODES_SNOWFALL_GRAVE)) if (GameObject* pGO = bg->GetBGObject(BG_AV_NODES_SNOWFALL_GRAVE))
if (bot->IsWithinDist(pGO, 200.f)) if (bot->IsWithinDist(pGO, 200.f))
@@ -2967,19 +2981,20 @@ bool BGTactics::selectObjective(bool reset)
if (!BgObjective && alterValleyBG->IsCaptainAlive(0)) if (!BgObjective && alterValleyBG->IsCaptainAlive(0))
{ {
if (Creature* pBalinda = bg->GetBGCreature(AV_NPC_A_CAPTAIN)) if (Creature* pBalinda = bg->GetBGCreature(AV_CPLACE_TRIGGER16))
{ {
if (pBalinda->getDeathState() != DeathState::Dead) if (pBalinda->getDeathState() != DeathState::Dead)
{ {
uint32 attackCount = 0; uint32 attackCount = getDefendersCount(AV_STONEHEARTH_WAITING_HORDE, 10.0f, false) + getDefendersCount(AV_STONEHEARTH_ATTACKING_HORDE, 10.0f, false);
attackCount += getDefendersCount(AV_STONEHEARTH_WAITING_HORDE, 10.0f, false);
// prepare to attack Captain // prepare to attack Captain
if (attackCount < 10 && !pBalinda->IsInCombat()) if (attackCount < 10 && !pBalinda->IsInCombat())
{ {
// get in position to attack Captain // get in position to attack Captain
pos.Set(AV_STONEHEARTH_WAITING_HORDE.GetPositionX(), AV_STONEHEARTH_WAITING_HORDE.GetPositionY(), pos.Set(AV_STONEHEARTH_WAITING_HORDE.GetPositionX(),
AV_STONEHEARTH_WAITING_HORDE.GetPositionZ(), bg->GetMapId()); AV_STONEHEARTH_WAITING_HORDE.GetPositionY(),
AV_STONEHEARTH_WAITING_HORDE.GetPositionZ(),
bg->GetMapId());
std::ostringstream out; std::ostringstream out;
out << "Taking position at Stonehearth!"; out << "Taking position at Stonehearth!";
@@ -2987,6 +3002,12 @@ bool BGTactics::selectObjective(bool reset)
} }
else else
{ {
// they need help getting there (or did before I fixed the target creature, will leave in anyway, as it probably makes it more robust)
pos.Set(AV_STONEHEARTH_ATTACKING_HORDE.GetPositionX(),
AV_STONEHEARTH_ATTACKING_HORDE.GetPositionY(),
AV_STONEHEARTH_ATTACKING_HORDE.GetPositionZ(),
bg->GetMapId());
std::ostringstream out; std::ostringstream out;
out << "Attacking Balinda!"; out << "Attacking Balinda!";
//bot->Say(out.str(), LANG_UNIVERSAL); //bot->Say(out.str(), LANG_UNIVERSAL);
@@ -3007,9 +3028,9 @@ bool BGTactics::selectObjective(bool reset)
{ {
for (const auto& objective : AV_HordeDefendObjectives) for (const auto& objective : AV_HordeDefendObjectives)
{ {
if (!BgObjective && alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_ALLIANCE) if (!BgObjective && alterValleyBG->GetAVNodeInfo(objective.first).OwnerId == TEAM_ALLIANCE)
{ {
if (GameObject* pGO = bg->GetBGObject(objective)) if (GameObject* pGO = bg->GetBGObject(objective.second))
if (bot->IsWithinDist(pGO, 400.0f)) if (bot->IsWithinDist(pGO, 400.0f))
{ {
BgObjective = pGO; BgObjective = pGO;
@@ -3022,7 +3043,8 @@ bool BGTactics::selectObjective(bool reset)
} }
// Mine capture (need paths & script fix) // Mine capture (need paths & script fix)
if (!BgObjective && supporter && !endBoss && (alterValleyBG->GetMineOwner(AV_NORTH_MINE) == TEAM_ALLIANCE || alterValleyBG->GetMineOwner(AV_NORTH_MINE) == TEAM_OTHER) && if (!BgObjective && supporter && !endBoss &&
(alterValleyBG->GetMineOwner(AV_NORTH_MINE) == TEAM_ALLIANCE || alterValleyBG->GetMineOwner(AV_NORTH_MINE) == TEAM_OTHER) &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_STORMPIKE_GRAVE).OwnerId != TEAM_ALLIANCE) alterValleyBG->GetAVNodeInfo(BG_AV_NODES_STORMPIKE_GRAVE).OwnerId != TEAM_ALLIANCE)
{ {
if (Creature* mBossNeutral = bg->GetBGCreature(AV_CPLACE_MINE_N_3)) if (Creature* mBossNeutral = bg->GetBGCreature(AV_CPLACE_MINE_N_3))
@@ -3055,10 +3077,9 @@ bool BGTactics::selectObjective(bool reset)
for (const auto& objective : AV_HordeAttackObjectives) for (const auto& objective : AV_HordeAttackObjectives)
{ {
if ((!BgObjective/* || (supporter && objective.first == BG_AV_NODES_STONEHEART_BUNKER && !bg->IsActiveEvent(BG_AV_NODE_CAPTAIN_DEAD_A, 0))*/) && if ((!BgObjective/* || (supporter && objective.first == BG_AV_NODES_STONEHEART_BUNKER && !bg->IsActiveEvent(BG_AV_NODE_CAPTAIN_DEAD_A, 0))*/) &&
(alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_ALLIANCE || alterValleyBG->GetAVNodeInfo(objective).TotalOwnerId == TEAM_ALLIANCE || alterValleyBG->GetAVNodeInfo(objective.first).TotalOwnerId == TEAM_ALLIANCE)//need to check TotalOwnerId for attack objectives
alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_OTHER))
{ {
if (GameObject* pGO = bg->GetBGObject(objective)) if (GameObject* pGO = bg->GetBGObject(objective.second))
{ {
BgObjective = pGO; BgObjective = pGO;
//std::ostringstream out; //std::ostringstream out;
@@ -3073,11 +3094,13 @@ bool BGTactics::selectObjective(bool reset)
{ {
bool endBoss = false; bool endBoss = false;
// End boss // End boss
if (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_ICEBLOOD_TOWER).OwnerId != TEAM_HORDE && alterValleyBG->GetAVNodeInfo(BG_AV_NODES_TOWER_POINT).OwnerId != TEAM_HORDE && if (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_ICEBLOOD_TOWER).TotalOwnerId != TEAM_HORDE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_ETOWER).OwnerId != TEAM_HORDE && alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_WTOWER).OwnerId != TEAM_HORDE && alterValleyBG->GetAVNodeInfo(BG_AV_NODES_TOWER_POINT).TotalOwnerId != TEAM_HORDE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_HUT).OwnerId != TEAM_HORDE) alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_ETOWER).TotalOwnerId != TEAM_HORDE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_WTOWER).TotalOwnerId != TEAM_HORDE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_HUT).TotalOwnerId != TEAM_HORDE)
{ {
if (Creature* pDrek = bg->GetBGCreature(AV_NPC_H_BOSS)) if (Creature* pDrek = bg->GetBGCreature(AV_CPLACE_TRIGGER19))
{ {
BgObjective = pDrek; BgObjective = pDrek;
endBoss = true; endBoss = true;
@@ -3091,9 +3114,9 @@ bool BGTactics::selectObjective(bool reset)
bool supporter = role < 3; bool supporter = role < 3;
// Only go to Snowfall Graveyard if already close to it. // Only go to Snowfall Graveyard if already close to it.
if (!BgObjective && supporter && (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE || if (!BgObjective && supporter &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).TotalOwnerId == TEAM_HORDE || (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).TotalOwnerId == TEAM_OTHER)) alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_OTHER))
{ {
if (GameObject* pGO = bg->GetBGObject(BG_AV_NODES_SNOWFALL_GRAVE)) if (GameObject* pGO = bg->GetBGObject(BG_AV_NODES_SNOWFALL_GRAVE))
if (bot->IsWithinDist(pGO, 200.f)) if (bot->IsWithinDist(pGO, 200.f))
@@ -3110,9 +3133,9 @@ bool BGTactics::selectObjective(bool reset)
{ {
for (const auto& objective : AV_AllianceDefendObjectives) for (const auto& objective : AV_AllianceDefendObjectives)
{ {
if (!BgObjective && alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_HORDE) if (!BgObjective && alterValleyBG->GetAVNodeInfo(objective.first).OwnerId == TEAM_HORDE)
{ {
if (GameObject* pGO = bg->GetBGObject(objective)) if (GameObject* pGO = bg->GetBGObject(objective.second))
{ {
BgObjective = pGO; BgObjective = pGO;
//std::ostringstream out; out << "Defending Node #" << objective.first; //std::ostringstream out; out << "Defending Node #" << objective.first;
@@ -3123,8 +3146,8 @@ bool BGTactics::selectObjective(bool reset)
} }
// Mine capture (need paths & script fix) // Mine capture (need paths & script fix)
if (!BgObjective && supporter && !endBoss && (alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_HORDE || if (!BgObjective && supporter && !endBoss &&
alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_OTHER) && (alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_HORDE || alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_OTHER) &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_GRAVE).TotalOwnerId != TEAM_HORDE) alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_GRAVE).TotalOwnerId != TEAM_HORDE)
{ {
if (Creature* mBossNeutral = bg->GetBGCreature(AV_CPLACE_MINE_S_3)) if (Creature* mBossNeutral = bg->GetBGCreature(AV_CPLACE_MINE_S_3))
@@ -3158,17 +3181,18 @@ bool BGTactics::selectObjective(bool reset)
if (alterValleyBG->IsCaptainAlive(1)) if (alterValleyBG->IsCaptainAlive(1))
{ {
if (Creature* pGalvangar = bg->GetBGCreature(AV_NPC_H_CAPTAIN)) if (Creature* pGalvangar = bg->GetBGCreature(AV_CPLACE_TRIGGER18))
{ {
uint32 attackCount = 0; uint32 attackCount = getDefendersCount(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE, 10.0f, false) + getDefendersCount(AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE, 10.0f, false);
attackCount += getDefendersCount(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE, 10.0f, false);
// prepare to attack Captain // prepare to attack Captain
if (attackCount < 10 && !pGalvangar->IsInCombat()) if (attackCount < 10 && !pGalvangar->IsInCombat())
{ {
// get in position to attack Captain // get in position to attack Captain
pos.Set(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionX(), AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionY(), pos.Set(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionX(),
AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionZ(), bg->GetMapId()); AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionY(),
AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionZ(),
bg->GetMapId());
//std::ostringstream out; //std::ostringstream out;
//out << "Taking position at Iceblood Outpost!"; //out << "Taking position at Iceblood Outpost!";
@@ -3176,6 +3200,12 @@ bool BGTactics::selectObjective(bool reset)
} }
else else
{ {
// they need help getting there (or did before I fixed the target creature, will leave in anyway, as it probably makes it more robust)
pos.Set(AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE.GetPositionX(),
AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE.GetPositionY(),
AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE.GetPositionZ(),
bg->GetMapId());
//std::ostringstream out; //std::ostringstream out;
// out << "Attacking Galvangar!"; // out << "Attacking Galvangar!";
//bot->Say(out.str(), LANG_UNIVERSAL); //bot->Say(out.str(), LANG_UNIVERSAL);
@@ -3187,11 +3217,9 @@ bool BGTactics::selectObjective(bool reset)
for (const auto& objective : AV_AllianceAttackObjectives) for (const auto& objective : AV_AllianceAttackObjectives)
{ {
if (alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_HORDE || if (alterValleyBG->GetAVNodeInfo(objective.first).TotalOwnerId == TEAM_HORDE)//need to check TotalOwnerId for attack objectives
alterValleyBG->GetAVNodeInfo(objective).TotalOwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(objective).TotalOwnerId == TEAM_OTHER)
{ {
if (GameObject* pGO = bg->GetBGObject(objective)) if (GameObject* pGO = bg->GetBGObject(objective.second))
{ {
float const distance = sqrt(bot->GetDistance(pGO)); float const distance = sqrt(bot->GetDistance(pGO));
if (attackObjectiveDistance > distance) if (attackObjectiveDistance > distance)
@@ -4087,7 +4115,8 @@ bool BGTactics::selectObjective(bool reset)
} }
break; break;
} }
default:
break;
} }
return false; return false;
@@ -4124,7 +4153,7 @@ bool BGTactics::moveToObjective()
} }
// don't try to move if already close // don't try to move if already close
if (sqrt(bot->GetDistance(pos.x, pos.y, pos.z)) < 5.0f) if (sqrt(bot->GetDistance(pos.x, pos.y, pos.z)) < 2.0f)
{ {
resetObjective(); resetObjective();
@@ -4134,8 +4163,8 @@ bool BGTactics::moveToObjective()
//std::ostringstream out; out << "Moving to objective " << pos.x << ", " << pos.y << ", Distance: " << sServerFacade->GetDistance2d(bot, pos.x, pos.y); //std::ostringstream out; out << "Moving to objective " << pos.x << ", " << pos.y << ", Distance: " << sServerFacade->GetDistance2d(bot, pos.x, pos.y);
//bot->Say(out.str(), LANG_UNIVERSAL); //bot->Say(out.str(), LANG_UNIVERSAL);
// more precise position for wsg // more precise position for wsg and AV (flags in AV towers require precision)
if (bgType == BATTLEGROUND_WS) if (bgType == BATTLEGROUND_WS || bgType == BATTLEGROUND_AV)
return MoveTo(bot->GetMapId(), pos.x, pos.y, pos.z); return MoveTo(bot->GetMapId(), pos.x, pos.y, pos.z);
else else
return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 3.0f); return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 3.0f);
@@ -4162,99 +4191,84 @@ bool BGTactics::selectObjectiveWp(std::vector<BattleBotPath*> const& vPaths)
if (bgType == BATTLEGROUND_WS /* && (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG))*/) if (bgType == BATTLEGROUND_WS /* && (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG))*/)
return wsgPaths(); return wsgPaths();
BattleBotPath* pClosestPath = nullptr; float chosenPathScore = FLT_MAX;//lower score is better
uint32 closestPoint = 0; BattleBotPath* chosenPath = nullptr;
float closestDistanceToTarget = FLT_MAX; uint32 chosenPathPoint = 0;
bool reverse = false; bool chosenPathReverse = false;
float maxDistanceToPoint = 50.0f;
float botDistanceLimit = 50.0f; // limit for how far path can be from bot
float botDistanceScoreSubtract = 8.0f; // path score modifier - lower = less likely to chose a further path (it's basically the distance from bot that's ignored)
float botDistanceScoreMultiply = 3.0f; // path score modifier - higher = less likely to chose a further path (it's basically a multiplier on distance from bot - makes distance from bot more signifcant than distance from destination)
if (bgType == BATTLEGROUND_IC) if (bgType == BATTLEGROUND_IC)
maxDistanceToPoint = 80.0f; botDistanceLimit = 80.0f;
else if (bgType == BATTLEGROUND_AB)
{
botDistanceScoreSubtract = 2.0f;
botDistanceScoreMultiply = 4.0f;
}
for (auto const& pPath : vPaths) for (auto const& path : vPaths)
{ {
// skip mine paths of own faction // skip mine paths of own faction
if (bot->GetTeamId() == TEAM_ALLIANCE && std::find(vPaths_AllyMine.begin(), vPaths_AllyMine.end(), pPath) != vPaths_AllyMine.end()) if (bot->GetTeamId() == TEAM_ALLIANCE && std::find(vPaths_AllyMine.begin(), vPaths_AllyMine.end(), path) != vPaths_AllyMine.end())
continue; continue;
if (bot->GetTeamId() == TEAM_HORDE && std::find(vPaths_HordeMine.begin(), vPaths_HordeMine.end(), pPath) != vPaths_HordeMine.end()) if (bot->GetTeamId() == TEAM_HORDE && std::find(vPaths_HordeMine.begin(), vPaths_HordeMine.end(), path) != vPaths_HordeMine.end())
continue; continue;
BattleBotWaypoint& lastPoint = ((*pPath)[pPath->size() - 1]); BattleBotWaypoint& startPoint = ((*path)[0]);
float const distanceFromPathEndToTarget = sqrt(Position(pos.x, pos.y, pos.z, 0.f).GetExactDist(lastPoint.x, lastPoint.y, lastPoint.z)); float const startPointDistToDestination = sqrt(Position(pos.x, pos.y, pos.z, 0.f).GetExactDist(startPoint.x, startPoint.y, startPoint.z));
if (closestDistanceToTarget > distanceFromPathEndToTarget) BattleBotWaypoint& endPoint = ((*path)[path->size() - 1]);
{ float const endPointDistToDestination = sqrt(Position(pos.x, pos.y, pos.z, 0.f).GetExactDist(endPoint.x, endPoint.y, endPoint.z));
float closestDistanceFromMeToPoint = FLT_MAX;
for (uint32 i = 0; i < pPath->size(); i++) bool reverse = startPointDistToDestination < endPointDistToDestination;
{
BattleBotWaypoint& waypoint = ((*pPath)[i]);
float const distanceFromMeToPoint = sqrt(bot->GetDistance(waypoint.x, waypoint.y, waypoint.z));
if (distanceFromMeToPoint < maxDistanceToPoint && closestDistanceFromMeToPoint > distanceFromMeToPoint)
{
reverse = false;
pClosestPath = pPath;
closestPoint = i;
closestDistanceToTarget = distanceFromPathEndToTarget;
closestDistanceFromMeToPoint = distanceFromMeToPoint;
}
}
}
// skip no reverse paths // dont travel reverse if it's a reverse paths
if (std::find(vPaths_NoReverseAllowed.begin(), vPaths_NoReverseAllowed.end(), pPath) != vPaths_NoReverseAllowed.end()) if (reverse && std::find(vPaths_NoReverseAllowed.begin(), vPaths_NoReverseAllowed.end(), path) != vPaths_NoReverseAllowed.end())
continue; continue;
// skip mine paths of own faction int closestPointIndex = -1;
if (bot->GetTeamId() == TEAM_ALLIANCE && std::find(vPaths_AllyMine.begin(), vPaths_AllyMine.end(), pPath) != vPaths_AllyMine.end()) float closestPointDistToBot = FLT_MAX;
for (uint32 i = 0; i < path->size(); i++)
{
BattleBotWaypoint& waypoint = ((*path)[i]);
float const distToBot = sqrt(bot->GetDistance(waypoint.x, waypoint.y, waypoint.z));
if (closestPointDistToBot > distToBot)
{
closestPointDistToBot = distToBot;
closestPointIndex = i;
}
}
// don't pick path where bot is already closest to the paths closest point to target (it means path cant lead it anywhere)
// don't pick path where closest point is too far away
if (closestPointIndex == (reverse ? 0 : path->size() - 1) || closestPointDistToBot > botDistanceLimit)
continue; continue;
if (bot->GetTeamId() == TEAM_HORDE && std::find(vPaths_HordeMine.begin(), vPaths_HordeMine.end(), pPath) != vPaths_HordeMine.end())
continue;
{ // creates a score based on dist-to-bot and dist-to-destination, where lower is better, and dist-to-bot is more important (when its beyond a certain distance)
BattleBotWaypoint& firstPoint = ((*pPath)[0]); // dist-to-bot is more important because otherwise they cant reach it at all (or will fly through air with MM::MovePoint()), also bot may need to use multiple
float const distanceFromPathBeginToTarget = sqrt(Position(pos.x, pos.y, pos.z, 0).GetExactDist(firstPoint.x, firstPoint.y, firstPoint.z)); // paths (one after another) anyway
if (closestDistanceToTarget > distanceFromPathBeginToTarget) float distToDestination = reverse ? startPointDistToDestination : endPointDistToDestination;
{ float pathScore = (closestPointDistToBot < botDistanceScoreSubtract ? 0.0f : ((closestPointDistToBot - botDistanceScoreSubtract) * botDistanceScoreMultiply)) + distToDestination;
float closestDistanceFromMeToPoint = FLT_MAX;
for (uint32 i = 0; i < pPath->size(); i++) //LOG_INFO("playerbots", "bot={}\t{:6.1f}\t{:4.1f}\t{:4.1f}\t{}", bot->GetName(), pathScore, closestPointDistToBot, distToDestination, vPaths_AB_name[pathNum]);
{
BattleBotWaypoint& waypoint = ((*pPath)[i]); if (chosenPathScore > pathScore) {
float const distanceFromMeToPoint = sqrt(bot->GetDistance(waypoint.x, waypoint.y, waypoint.z)); chosenPathScore = pathScore;
if (distanceFromMeToPoint < maxDistanceToPoint && closestDistanceFromMeToPoint > distanceFromMeToPoint) chosenPath = path;
{ chosenPathPoint = closestPointIndex;
reverse = true; chosenPathReverse = reverse;
pClosestPath = pPath;
closestPoint = i;
closestDistanceToTarget = distanceFromPathBeginToTarget;
closestDistanceFromMeToPoint = distanceFromMeToPoint;
}
}
}
} }
} }
if (!pClosestPath) if (!chosenPath)
return false; return false;
// Prevent picking last point of path. //LOG_INFO("playerbots", "bot={} {}", bot->GetName(), vPaths_AB_name[chosenPathNum]);
// It means we are already there.
if (reverse)
{
if (closestPoint == 0)
return false;
}
else
{
if (closestPoint == pClosestPath->size() - 1)
return false;
}
BattleBotPath* currentPath = pClosestPath; return moveToObjectiveWp(chosenPath, chosenPathPoint, chosenPathReverse);
uint32 currentPoint = reverse ? closestPoint + 1 : closestPoint - 1;
return moveToObjectiveWp(currentPath, currentPoint, reverse);
return false; return false;
} }
@@ -4490,7 +4504,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
if (f == vFlagIds.end()) if (f == vFlagIds.end())
continue; continue;
if (!go->isSpawned() || !go->GetGoState() == GO_STATE_READY) if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
continue; continue;
if (!bot->CanUseBattlegroundObject(go) && bgType != BATTLEGROUND_WS) if (!bot->CanUseBattlegroundObject(go) && bgType != BATTLEGROUND_WS)
@@ -4631,6 +4645,8 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
} }
break; break;
} }
default:
break;
} }
} }
@@ -4961,9 +4977,9 @@ bool ArenaTactics::moveToCenter(Battleground* bg)
case BATTLEGROUND_DS: case BATTLEGROUND_DS:
if (!MoveTo(bg->GetMapId(), 1291.58f + frand(-5, +5), 790.87f + frand(-5, +5), 7.8f, false, true)) { if (!MoveTo(bg->GetMapId(), 1291.58f + frand(-5, +5), 790.87f + frand(-5, +5), 7.8f, false, true)) {
// they like to hang around at the tip of the pipes doing nothing, so we just teleport them down // they like to hang around at the tip of the pipes doing nothing, so we just teleport them down
if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 2) if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4)
bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation());
if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 2) if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4)
bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation());
} }
break; break;

View File

@@ -91,6 +91,8 @@ bool BuyAction::Execute(Event event)
case ITEM_USAGE_SKILL: case ITEM_USAGE_SKILL:
needMoneyFor = NeedMoneyFor::tradeskill; needMoneyFor = NeedMoneyFor::tradeskill;
break; break;
default:
break;
} }
if (needMoneyFor == NeedMoneyFor::none) if (needMoneyFor == NeedMoneyFor::none)

View File

@@ -11,7 +11,7 @@
uint32 FindLastSeparator(std::string const text, std::string const sep) uint32 FindLastSeparator(std::string const text, std::string const sep)
{ {
uint32 pos = text.rfind(sep); size_t pos = text.rfind(sep);
if (pos == std::string::npos) if (pos == std::string::npos)
return pos; return pos;
@@ -64,7 +64,7 @@ bool CastCustomSpellAction::Execute(Event event)
Item* itemTarget = nullptr; Item* itemTarget = nullptr;
uint32 pos = FindLastSeparator(text, " "); size_t pos = FindLastSeparator(text, " ");
uint32 castCount = 1; uint32 castCount = 1;
if (pos != std::string::npos) if (pos != std::string::npos)
{ {

View File

@@ -44,9 +44,11 @@ bool ChangeTalentsAction::Execute(Event event)
} else if (param.find("spec ") != std::string::npos) { } else if (param.find("spec ") != std::string::npos) {
param = param.substr(5); param = param.substr(5);
out << SpecPick(param); out << SpecPick(param);
botAI->ResetStrategies();
} else if (param.find("apply ") != std::string::npos) { } else if (param.find("apply ") != std::string::npos) {
param = param.substr(6); param = param.substr(6);
out << SpecApply(param); out << SpecApply(param);
botAI->ResetStrategies();
} else { } else {
out << "Unknown command."; out << "Unknown command.";
} }

View File

@@ -46,19 +46,19 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
foundTarget = SetNpcFlagTarget(newTarget, { UNIT_NPC_FLAG_BANKER,UNIT_NPC_FLAG_BATTLEMASTER,UNIT_NPC_FLAG_AUCTIONEER }); foundTarget = SetNpcFlagTarget(newTarget, { UNIT_NPC_FLAG_BANKER,UNIT_NPC_FLAG_BATTLEMASTER,UNIT_NPC_FLAG_AUCTIONEER });
//Grind for money //Grind for money
if (!foundTarget && AI_VALUE(bool, "should get money")) if (!foundTarget && AI_VALUE(bool, "should get money")) {
if (urand(1, 100) > 66) if (urand(1, 100) > 66)
{ {
foundTarget = SetQuestTarget(newTarget, true); //Turn in quests for money. foundTarget = SetQuestTarget(newTarget, true); //Turn in quests for money.
if (!foundTarget) if (!foundTarget)
foundTarget = SetQuestTarget(newTarget); //Do low level quests foundTarget = SetQuestTarget(newTarget); //Do low level quests
} } else if (urand(1, 100) > 50) {
else if (urand(1, 100) > 50)
foundTarget = SetGrindTarget(newTarget); //Go grind mobs for money foundTarget = SetGrindTarget(newTarget); //Go grind mobs for money
else } else {
foundTarget = SetNewQuestTarget(newTarget); //Find a low level quest to do foundTarget = SetNewQuestTarget(newTarget); //Find a low level quest to do
}
}
//Continue //Continue
if (!foundTarget && urand(1, 100) > 10) //90% chance if (!foundTarget && urand(1, 100) > 10) //90% chance

View File

@@ -10,7 +10,7 @@
bool CustomStrategyEditAction::Execute(Event event) bool CustomStrategyEditAction::Execute(Event event)
{ {
std::string text = event.getParam(); std::string text = event.getParam();
uint32 pos = text.find(" "); size_t pos = text.find(" ");
if (pos == std::string::npos) if (pos == std::string::npos)
return PrintHelp(); return PrintHelp();

View File

@@ -147,6 +147,8 @@ RollVote LootRollAction::CalculateRollVote(ItemTemplate const* proto)
case ITEM_USAGE_VENDOR: case ITEM_USAGE_VENDOR:
needVote = GREED; needVote = GREED;
break; break;
default:
break;
} }
return StoreLootAction::IsLootAllowed(proto->ItemId, GET_PLAYERBOT_AI(bot)) ? needVote : PASS; return StoreLootAction::IsLootAllowed(proto->ItemId, GET_PLAYERBOT_AI(bot)) ? needVote : PASS;

View File

@@ -4,6 +4,7 @@
#include "MovementActions.h" #include "MovementActions.h"
#include "GameObject.h" #include "GameObject.h"
#include "Geometry.h"
#include "Map.h" #include "Map.h"
#include "MotionMaster.h" #include "MotionMaster.h"
#include "MoveSplineInitArgs.h" #include "MoveSplineInitArgs.h"
@@ -12,6 +13,7 @@
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PathGenerator.h" #include "PathGenerator.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "Position.h"
#include "Random.h" #include "Random.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SpellAuraEffects.h" #include "SpellAuraEffects.h"
@@ -25,10 +27,15 @@
#include "LootObjectStack.h" #include "LootObjectStack.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "Timer.h"
#include "Transport.h" #include "Transport.h"
#include "Unit.h" #include "Unit.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "WaypointMovementGenerator.h" #include "WaypointMovementGenerator.h"
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <string>
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
{ {
@@ -156,7 +163,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// } // }
bool generatePath = !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && bool generatePath = !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) &&
!bot->IsFlying() && !bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !bot->IsInWater(); !bot->IsFlying() && !bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !bot->IsInWater();
if (!generatePath) { bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
if (disableMoveSplinePath || !generatePath) {
float distance = bot->GetExactDist(x, y, z); float distance = bot->GetExactDist(x, y, z);
if (distance > sPlayerbotAIConfig->contactDistance) if (distance > sPlayerbotAIConfig->contactDistance)
{ {
@@ -1495,6 +1504,9 @@ bool FleeWithPetAction::Execute(Event event)
bool AvoidAoeAction::isUseful() bool AvoidAoeAction::isUseful()
{ {
if (getMSTime() - moveInterval < lastMoveTimer) {
return false;
}
GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage"); GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage");
GuidVector triggers = AI_VALUE(GuidVector, "possible triggers"); GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
return AI_VALUE(Aura*, "area debuff") || !traps.empty() || !triggers.empty(); return AI_VALUE(Aura*, "area debuff") || !traps.empty() || !triggers.empty();
@@ -1523,6 +1535,9 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
if (!aura || aura->IsRemoved() || aura->IsExpired()) { if (!aura || aura->IsRemoved() || aura->IsExpired()) {
return false; return false;
} }
if (!aura->GetOwner() || !aura->GetOwner()->IsInWorld()) {
return false;
}
// Crash fix: maybe change owner due to check interval // Crash fix: maybe change owner due to check interval
if (aura->GetType() != DYNOBJ_AURA_TYPE) { if (aura->GetType() != DYNOBJ_AURA_TYPE) {
return false; return false;
@@ -1540,8 +1555,15 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
return false; return false;
} }
std::ostringstream name; std::ostringstream name;
name << spellInfo->SpellName[0]; // << "] (aura)"; name << spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; // << "] (aura)";
if (FleePosition(dynOwner->GetPosition(), radius, name.str())) { if (FleePosition(dynOwner->GetPosition(), radius)) {
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) {
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
bot->Say(out.str(), LANG_UNIVERSAL);
}
return true; return true;
} }
return false; return false;
@@ -1591,8 +1613,15 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
continue; continue;
} }
std::ostringstream name; std::ostringstream name;
name << spellInfo->SpellName[0]; // << "] (object)"; name << spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; // << "] (object)";
if (FleePosition(go->GetPosition(), radius, name.str())) { if (FleePosition(go->GetPosition(), radius)) {
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) {
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
bot->Say(out.str(), LANG_UNIVERSAL);
}
return true; return true;
} }
@@ -1633,9 +1662,15 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
break; break;
} }
std::ostringstream name; std::ostringstream name;
name << triggerSpellInfo->SpellName[0]; //<< "] (unit)"; name << triggerSpellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; //<< "] (unit)";
if (FleePosition(unit->GetPosition(), radius, name.str())) { if (FleePosition(unit->GetPosition(), radius)) {
return true; if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) {
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
bot->Say(out.str(), LANG_UNIVERSAL);
}
} }
} }
} }
@@ -1645,7 +1680,7 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
return false; return false;
} }
Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius) Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
{ {
Unit* currentTarget = AI_VALUE(Unit*, "current target"); Unit* currentTarget = AI_VALUE(Unit*, "current target");
std::vector<CheckAngle> possibleAngles; std::vector<CheckAngle> possibleAngles;
@@ -1670,13 +1705,18 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius)
Position bestPos; Position bestPos;
for (CheckAngle &checkAngle : possibleAngles) { for (CheckAngle &checkAngle : possibleAngles) {
float angle = checkAngle.angle; float angle = checkAngle.angle;
auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
if (!CheckLastFlee(angle, infoList)) {
continue;
}
bool strict = checkAngle.strict; bool strict = checkAngle.strict;
float fleeDis = sPlayerbotAIConfig->fleeDistance; float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis,
bot->GetPositionY() + sin(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()}; bot->GetPositionZ()};
if (strict && currentTarget if (strict && currentTarget
&& fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->tooCloseDistance) { && fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->tooCloseDistance
&& bot->IsWithinMeleeRange(currentTarget)) {
continue; continue;
} }
if (pos.GetExactDist(fleePos) > farestDis) { if (pos.GetExactDist(fleePos) > farestDis) {
@@ -1690,7 +1730,7 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius)
return Position(); return Position();
} }
Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius) Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius)
{ {
Unit* currentTarget = AI_VALUE(Unit*, "current target"); Unit* currentTarget = AI_VALUE(Unit*, "current target");
std::vector<CheckAngle> possibleAngles; std::vector<CheckAngle> possibleAngles;
@@ -1713,8 +1753,12 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius)
Position bestPos; Position bestPos;
for (CheckAngle &checkAngle : possibleAngles) { for (CheckAngle &checkAngle : possibleAngles) {
float angle = checkAngle.angle; float angle = checkAngle.angle;
auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
if (!CheckLastFlee(angle, infoList)) {
continue;
}
bool strict = checkAngle.strict; bool strict = checkAngle.strict;
float fleeDis = sPlayerbotAIConfig->fleeDistance; float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis,
bot->GetPositionY() + sin(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()}; bot->GetPositionZ()};
@@ -1737,28 +1781,197 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius)
return Position(); return Position();
} }
bool AvoidAoeAction::FleePosition(Position pos, float radius, std::string name) bool MovementAction::FleePosition(Position pos, float radius)
{ {
Position bestPos; Position bestPos;
if (botAI->IsMelee(bot)) { if (botAI->IsMelee(bot)) {
bestPos = BestPositionForMelee(pos, radius); bestPos = BestPositionForMeleeToFlee(pos, radius);
} else { } else {
bestPos = BestPositionForRanged(pos, radius); bestPos = BestPositionForRangedToFlee(pos, radius);
} }
if (bestPos != Position()) { if (bestPos != Position()) {
if (MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, true)) { if (MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, true)) {
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) { auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
lastTellTimer = time(NULL); uint32 curTS = getMSTime();
std::ostringstream out; while (!infoList.empty()) {
out << "I'm avoiding " << name << "..."; if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS) {
bot->Say(out.str(), LANG_UNIVERSAL); infoList.pop_front();
} else {
break;
} }
}
infoList.push_back({pos, radius, bot->GetAngle(&bestPos), curTS});
return true; return true;
} }
} }
return false; return false;
} }
bool MovementAction::CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList)
{
uint32 curTS = getMSTime();
curAngle = fmod(curAngle, 2 * M_PI);
while (!infoList.empty()) {
if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS) {
infoList.pop_front();
} else {
break;
}
}
for (FleeInfo& info : infoList) {
// more than 5 sec
if (info.timestamp + 5000 < curTS) {
continue;
}
float revAngle = fmod(info.angle + M_PI, 2 * M_PI);
// angle too close
if (fabs(revAngle - curAngle) < M_PI / 4) {
return false;
}
}
return true;
}
bool CombatFormationMoveAction::isUseful()
{
if (getMSTime() - moveInterval < lastMoveTimer) {
return false;
}
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr) {
return false;
}
float dis = AI_VALUE(float, "disperse distance");
return dis > 0.0f;
}
bool CombatFormationMoveAction::Execute(Event event)
{
float dis = AI_VALUE(float, "disperse distance");
Player* playerToLeave = NearestGroupMember(dis);
if (playerToLeave && bot->GetExactDist(playerToLeave) < dis) {
if (FleePosition(playerToLeave->GetPosition(), dis)) {
lastMoveTimer = getMSTime();
}
}
return false;
}
Position CombatFormationMoveAction::AverageGroupPos(float dis)
{
float averageX = 0, averageY = 0, averageZ = 0;
int cnt = 0;
Group* group = bot->GetGroup();
if (!group) {
return Position();
}
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player *member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis)
continue;
cnt++;
averageX += member->GetPositionX();
averageY += member->GetPositionY();
averageZ += member->GetPositionZ();
}
averageX /= cnt;
averageY /= cnt;
averageZ /= cnt;
return Position(averageX, averageY, averageZ);
}
Player* CombatFormationMoveAction::NearestGroupMember(float dis)
{
float nearestDis = 10000.0f;
Player* result = nullptr;
Group* group = bot->GetGroup();
if (!group) {
return result;
}
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player *member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member == bot || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis)
continue;
if (nearestDis > bot->GetExactDist(member)) {
result = member;
nearestDis = bot->GetExactDist(member);
}
}
return result;
}
bool DisperseSetAction::Execute(Event event)
{
std::string const text = event.getParam();
if (text == "disable") {
RESET_AI_VALUE(float, "disperse distance");
botAI->TellMasterNoFacing("Disable disperse");
return true;
}
if (text == "enable" || text == "reset") {
if (botAI->IsMelee(bot)) {
SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_MELEE);
} else {
SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_RANGED);
}
float dis = AI_VALUE(float, "disperse distance");
std::ostringstream out;
out << "Enable disperse distance " << std::setprecision(2) << dis;
botAI->TellMasterNoFacing(out.str());
return true;
}
if (text == "increase") {
float dis = AI_VALUE(float, "disperse distance");
std::ostringstream out;
if (dis <= 0.0f) {
out << "Enable disperse first";
botAI->TellMasterNoFacing(out.str());
return true;
}
dis += 1.0f;
SET_AI_VALUE(float, "disperse distance", dis);
out << "Increase disperse distance to " << std::setprecision(2) << dis;
botAI->TellMasterNoFacing(out.str());
return true;
}
if (text == "decrease") {
float dis = AI_VALUE(float, "disperse distance");
dis -= 1.0f;
if (dis <= 0.0f) {
dis += 1.0f;
}
SET_AI_VALUE(float, "disperse distance", dis);
std::ostringstream out;
out << "Increase disperse distance to " << std::setprecision(2) << dis;
botAI->TellMasterNoFacing(out.str());
return true;
}
if (text.starts_with("set")) {
float dis = -1.0f;;
sscanf(text.c_str(), "set %f", &dis);
std::ostringstream out;
if (dis < 0 || dis > 100.0f) {
out << "Invalid disperse distance " << std::setprecision(2) << dis;
} else {
SET_AI_VALUE(float, "disperse distance", dis);
out << "Set disperse distance to " << std::setprecision(2) << dis;
}
botAI->TellMasterNoFacing(out.str());
return true;
}
std::ostringstream out;
out << "Usage: disperse [enable | disable | increase | decrease | set {distance}]";
float dis = AI_VALUE(float, "disperse distance");
if (dis > 0.0f) {
out << "(Current disperse distance: " << std::setprecision(2) << dis << ")";
}
botAI->TellMasterNoFacing(out.str());
return true;
}
bool RunAwayAction::Execute(Event event) bool RunAwayAction::Execute(Event event)
{ {
return Flee(AI_VALUE(Unit*, "master target")); return Flee(AI_VALUE(Unit*, "master target"));

View File

@@ -13,6 +13,7 @@ class Player;
class PlayerbotAI; class PlayerbotAI;
class Unit; class Unit;
class WorldObject; class WorldObject;
class Position;
class MovementAction : public Action class MovementAction : public Action
{ {
@@ -41,6 +42,15 @@ class MovementAction : public Action
bool MoveAway(Unit* target); bool MoveAway(Unit* target);
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance); bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance);
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false); void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
Position BestPositionForMeleeToFlee(Position pos, float radius);
Position BestPositionForRangedToFlee(Position pos, float radius);
bool FleePosition(Position pos, float radius);
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
protected:
struct CheckAngle {
float angle;
bool strict;
};
private: private:
// float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool normal_only = false, float step = 8.0f); // float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool normal_only = false, float step = 8.0f);
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float &modified_z, int maxSearchCount = 5, bool normal_only = false, float step = 8.0f); const Movement::PointsArray SearchForBestPath(float x, float y, float z, float &modified_z, int maxSearchCount = 5, bool normal_only = false, float step = 8.0f);
@@ -69,7 +79,8 @@ class FleeWithPetAction : public MovementAction
class AvoidAoeAction : public MovementAction class AvoidAoeAction : public MovementAction
{ {
public: public:
AvoidAoeAction(PlayerbotAI* botAI) : MovementAction(botAI, "avoid aoe") { } AvoidAoeAction(PlayerbotAI* botAI, int moveInterval = 1000) : MovementAction(botAI, "avoid aoe"),
moveInterval(moveInterval) { }
bool isUseful() override; bool isUseful() override;
bool Execute(Event event) override; bool Execute(Event event) override;
@@ -78,14 +89,36 @@ class AvoidAoeAction : public MovementAction
bool AvoidAuraWithDynamicObj(); bool AvoidAuraWithDynamicObj();
bool AvoidGameObjectWithDamage(); bool AvoidGameObjectWithDamage();
bool AvoidUnitWithDamageAura(); bool AvoidUnitWithDamageAura();
Position BestPositionForMelee(Position pos, float radius);
Position BestPositionForRanged(Position pos, float radius);
bool FleePosition(Position pos, float radius, std::string name);
time_t lastTellTimer = 0; time_t lastTellTimer = 0;
struct CheckAngle { int lastMoveTimer = 0;
float angle; int moveInterval;
bool strict;
}; };
class CombatFormationMoveAction : public MovementAction
{
public:
CombatFormationMoveAction(PlayerbotAI* botAI, int moveInterval = 1000) : MovementAction(botAI, "combat formation move"),
moveInterval(moveInterval) { }
bool isUseful() override;
bool Execute(Event event) override;
protected:
Position AverageGroupPos(float dis = sPlayerbotAIConfig->sightDistance);
Player* NearestGroupMember(float dis = sPlayerbotAIConfig->sightDistance);
int lastMoveTimer = 0;
int moveInterval;
};
class DisperseSetAction : public Action
{
public:
DisperseSetAction(PlayerbotAI* botAI, std::string const name = "disperse set") : Action(botAI, name) { }
bool Execute(Event event) override;
float DEFAULT_DISPERSE_DISTANCE_RANGED = 5.0f;
float DEFAULT_DISPERSE_DISTANCE_MELEE = 2.0f;
}; };
class RunAwayAction : public MovementAction class RunAwayAction : public MovementAction

View File

@@ -17,7 +17,7 @@ bool RangeAction::Execute(Event event)
PrintRange("flee"); PrintRange("flee");
} }
uint32 pos = param.find(" "); size_t pos = param.find(" ");
if (pos == std::string::npos) if (pos == std::string::npos)
return false; return false;

View File

@@ -13,7 +13,6 @@ bool StayActionBase::Stay()
//if (!urand(0, 10)) //if (!urand(0, 10))
//botAI->PlaySound(TEXT_EMOTE_YAWN); //botAI->PlaySound(TEXT_EMOTE_YAWN);
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE) if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE)
return false; return false;
@@ -26,12 +25,13 @@ bool StayActionBase::Stay()
context->GetValue<time_t>("stay time")->Set(stayTime); context->GetValue<time_t>("stay time")->Set(stayTime);
} }
if (!bot->isMoving()) // Stop the bot from moving immediately when action is called
return false; if (bot->isMoving())
{
bot->StopMoving(); bot->StopMoving();
bot->ClearUnitState(UNIT_STATE_CHASE); bot->ClearUnitState(UNIT_STATE_CHASE);
bot->ClearUnitState(UNIT_STATE_FOLLOW); bot->ClearUnitState(UNIT_STATE_FOLLOW);
}
return true; return true;
} }
@@ -43,7 +43,8 @@ bool StayAction::Execute(Event event)
bool StayAction::isUseful() bool StayAction::isUseful()
{ {
return !AI_VALUE2(bool, "moving", "self target"); // Only useful if the bot is currently moving
return AI_VALUE2(bool, "moving", "self target");
} }
bool SitAction::Execute(Event event) bool SitAction::Execute(Event event)

View File

@@ -53,6 +53,8 @@ void TalkToQuestGiverAction::ProcessQuest(Quest const* quest, Object* questGiver
case QUEST_STATUS_FAILED: case QUEST_STATUS_FAILED:
out << "|cffff0000Failed|r"; out << "|cffff0000Failed|r";
break; break;
default:
break;
} }
out << ": " << chat->FormatQuest(quest); out << ": " << chat->FormatQuest(quest);
@@ -257,6 +259,8 @@ bool TurnInQueryQuestAction::Execute(Event event)
case QUEST_STATUS_REWARDED: case QUEST_STATUS_REWARDED:
out << "|cffff0000Rewarded|r"; out << "|cffff0000Rewarded|r";
break; break;
default:
break;
} }
out << ": " << chat->FormatQuest(quest); out << ": " << chat->FormatQuest(quest);

View File

@@ -198,6 +198,7 @@ bool AutoGearAction::Execute(Event event)
sPlayerbotAIConfig->autoGearQualityLimit, sPlayerbotAIConfig->autoGearQualityLimit,
gs); gs);
factory.InitEquipment(true); factory.InitEquipment(true);
factory.InitAmmo();
if (bot->getLevel() >= sPlayerbotAIConfig->minEnchantingBotLevel) { if (bot->getLevel() >= sPlayerbotAIConfig->minEnchantingBotLevel) {
factory.ApplyEnchantAndGemsNew(); factory.ApplyEnchantAndGemsNew();
} }

View File

@@ -82,7 +82,8 @@ bool SummonAction::Execute(Event event)
} }
if (master->GetSession()->GetSecurity() >= SEC_PLAYER) { if (master->GetSession()->GetSecurity() >= SEC_PLAYER) {
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({}); // botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({});
SET_AI_VALUE(std::list<FleeInfo>, "recently flee info", {});
return Teleport(master, bot); return Teleport(master, bot);
} }
@@ -175,11 +176,30 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
if (summoner->IsWithinLOS(x, y, z)) if (summoner->IsWithinLOS(x, y, z))
{ {
bool allowed = sPlayerbotAIConfig->botReviveWhenSummon == 2 || (sPlayerbotAIConfig->botReviveWhenSummon == 1 && !master->IsInCombat() && master->IsAlive()); if (sPlayerbotAIConfig->botRepairWhenSummon) // .conf option to repair bot gear when summoned 0 = off, 1 = on
if (allowed && bot->isDead()) bot->DurabilityRepairAll(false, 1.0f, false);
if (master->IsInCombat() && !sPlayerbotAIConfig->allowSummonInCombat)
{
botAI->TellError("You cannot summon me while you're in combat");
return false;
}
if (!master->IsAlive() && !sPlayerbotAIConfig->allowSummonWhenMasterIsDead)
{
botAI->TellError("You cannot summon me while you're dead");
return false;
}
if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) && !sPlayerbotAIConfig->allowSummonWhenBotIsDead)
{
botAI->TellError("You cannot summon me while I'm dead, you need to release my spirit first");
return false;
}
if (bot->isDead() && sPlayerbotAIConfig->reviveBotWhenSummoned)
{ {
bot->ResurrectPlayer(1.0f, false); bot->ResurrectPlayer(1.0f, false);
bot->DurabilityRepairAll(false, 1.0f, false);
botAI->TellMasterNoFacing("I live, again!"); botAI->TellMasterNoFacing("I live, again!");
} }

View File

@@ -11,7 +11,7 @@
#ifndef WIN32 #ifndef WIN32
inline int strcmpi(char const* s1, char const* s2) inline int strcmpi(char const* s1, char const* s2)
{ {
for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2); for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2) {}
return *s1 - *s2; return *s1 - *s2;
} }
#endif #endif

View File

@@ -234,8 +234,6 @@ class CastDeathAndDecayAction : public CastSpellAction
{ {
public: public:
CastDeathAndDecayAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "death and decay") { } CastDeathAndDecayAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "death and decay") { }
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
class CastHornOfWinterAction : public CastSpellAction class CastHornOfWinterAction : public CastSpellAction

View File

@@ -67,7 +67,7 @@ class DeathKnightTriggerFactoryInternal : public NamedObjectContext<Trigger>
DeathKnightTriggerFactoryInternal() DeathKnightTriggerFactoryInternal()
{ {
creators["bone shield"] = &DeathKnightTriggerFactoryInternal::bone_shield; creators["bone shield"] = &DeathKnightTriggerFactoryInternal::bone_shield;
creators["pestilence"] = &DeathKnightTriggerFactoryInternal::pestilence; creators["pestilence glyph"] = &DeathKnightTriggerFactoryInternal::pestilence_glyph;
creators["blood strike"] = &DeathKnightTriggerFactoryInternal::blood_strike; creators["blood strike"] = &DeathKnightTriggerFactoryInternal::blood_strike;
creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike; creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike;
creators["plague strike on attacker"] = &DeathKnightTriggerFactoryInternal::plague_strike_on_attacker; creators["plague strike on attacker"] = &DeathKnightTriggerFactoryInternal::plague_strike_on_attacker;
@@ -94,7 +94,7 @@ class DeathKnightTriggerFactoryInternal : public NamedObjectContext<Trigger>
private: private:
static Trigger* bone_shield(PlayerbotAI* botAI) { return new BoneShieldTrigger(botAI); } static Trigger* bone_shield(PlayerbotAI* botAI) { return new BoneShieldTrigger(botAI); }
static Trigger* pestilence(PlayerbotAI* botAI) { return new PestilenceTrigger(botAI); } static Trigger* pestilence_glyph(PlayerbotAI* botAI) { return new PestilenceGlyphTrigger(botAI); }
static Trigger* blood_strike(PlayerbotAI* botAI) { return new BloodStrikeTrigger(botAI); } static Trigger* blood_strike(PlayerbotAI* botAI) { return new BloodStrikeTrigger(botAI); }
static Trigger* plague_strike(PlayerbotAI* botAI) { return new PlagueStrikeDebuffTrigger(botAI); } static Trigger* plague_strike(PlayerbotAI* botAI) { return new PlagueStrikeDebuffTrigger(botAI); }
static Trigger* plague_strike_on_attacker(PlayerbotAI* botAI) { return new PlagueStrikeDebuffOnAttackerTrigger(botAI); } static Trigger* plague_strike_on_attacker(PlayerbotAI* botAI) { return new PlagueStrikeDebuffOnAttackerTrigger(botAI); }

View File

@@ -14,7 +14,7 @@ bool DKPresenceTrigger::IsActive()
return !botAI->HasAura("blood presence", target) && !botAI->HasAura("unholy presence", target) && !botAI->HasAura("frost presence", target); return !botAI->HasAura("blood presence", target) && !botAI->HasAura("unholy presence", target) && !botAI->HasAura("frost presence", target);
} }
bool PestilenceTrigger::IsActive() { bool PestilenceGlyphTrigger::IsActive() {
if (!SpellTrigger::IsActive()) { if (!SpellTrigger::IsActive()) {
return false; return false;
} }

View File

@@ -71,10 +71,10 @@ class DeathCoilTrigger : public SpellCanBeCastTrigger
DeathCoilTrigger(PlayerbotAI* botAI) : SpellCanBeCastTrigger(botAI, "death coil") { } DeathCoilTrigger(PlayerbotAI* botAI) : SpellCanBeCastTrigger(botAI, "death coil") { }
}; };
class PestilenceTrigger : public DebuffTrigger class PestilenceGlyphTrigger : public SpellTrigger
{ {
public: public:
PestilenceTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "pestilence") { } PestilenceGlyphTrigger(PlayerbotAI* botAI) : SpellTrigger(botAI, "pestilence") { }
virtual bool IsActive() override; virtual bool IsActive() override;
}; };

View File

@@ -192,5 +192,5 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// triggers.push_back(new TriggerNode("light aoe", NextAction::array(0, // triggers.push_back(new TriggerNode("light aoe", NextAction::array(0,
// new NextAction("pestilence", ACTION_NORMAL + 4), // new NextAction("pestilence", ACTION_NORMAL + 4),
// nullptr))); // nullptr)));
triggers.push_back(new TriggerNode("pestilence", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL))); triggers.push_back(new TriggerNode("pestilence glyph", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL)));
} }

View File

@@ -69,7 +69,7 @@ class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionN
static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode ("dire bear form", return new ActionNode ("dire bear form",
/*P*/ nullptr, /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NextAction::array(0, new NextAction("bear form"), nullptr), /*A*/ NextAction::array(0, new NextAction("bear form"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }

View File

@@ -20,6 +20,7 @@ class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
creators["insect swarm"] = &insect_swarm; creators["insect swarm"] = &insect_swarm;
creators["moonfire"] = &moonfire; creators["moonfire"] = &moonfire;
creators["starfire"] = &starfire; creators["starfire"] = &starfire;
creators["moonkin form"] = &moonkin_form;
} }
private: private:
@@ -94,6 +95,15 @@ class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
/*A*/ nullptr, /*A*/ nullptr,
/*C*/ nullptr); /*C*/ nullptr);
} }
static ActionNode* moonkin_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode ("moonkin form",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
}; };
CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)

View File

@@ -51,7 +51,7 @@ class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
{ {
return new ActionNode ("cat form", return new ActionNode ("cat form",
/*P*/ nullptr, /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr, /*A*/ nullptr,
/*C*/ nullptr); /*C*/ nullptr);
} }

View File

@@ -61,6 +61,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("naxx", NextAction::array(0, new NextAction("naxx chat shortcut", relevance), NULL))); triggers.push_back(new TriggerNode("naxx", NextAction::array(0, new NextAction("naxx chat shortcut", relevance), NULL)));
triggers.push_back(new TriggerNode("bwl", NextAction::array(0, new NextAction("bwl chat shortcut", relevance), NULL))); triggers.push_back(new TriggerNode("bwl", NextAction::array(0, new NextAction("bwl chat shortcut", relevance), NULL)));
triggers.push_back(new TriggerNode("dps", NextAction::array(0, new NextAction("tell expected dps", relevance), NULL))); triggers.push_back(new TriggerNode("dps", NextAction::array(0, new NextAction("tell expected dps", relevance), NULL)));
triggers.push_back(new TriggerNode("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL)));
} }
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)

View File

@@ -8,7 +8,7 @@
void CombatStrategy::InitTriggers(std::vector<TriggerNode*> &triggers) void CombatStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{ {
triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_MOVE + 11), nullptr))); triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_HIGH), nullptr)));
triggers.push_back(new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 100), nullptr))); triggers.push_back(new TriggerNode("invalid target", NextAction::array(0, new NextAction("drop target", 100), nullptr)));
triggers.push_back(new TriggerNode("mounted", NextAction::array(0, new NextAction("check mount state", 54), nullptr))); triggers.push_back(new TriggerNode("mounted", NextAction::array(0, new NextAction("check mount state", 54), nullptr)));
// triggers.push_back(new TriggerNode("out of react range", NextAction::array(0, new NextAction("flee to master", 55), nullptr))); // triggers.push_back(new TriggerNode("out of react range", NextAction::array(0, new NextAction("flee to master", 55), nullptr)));
@@ -24,44 +24,44 @@ AvoidAoeStrategy::AvoidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI)
} }
class AvoidAoeStrategyMultiplier : public Multiplier // class AvoidAoeStrategyMultiplier : public Multiplier
{ // {
public: // public:
AvoidAoeStrategyMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "run away on area debuff") {} // AvoidAoeStrategyMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "run away on area debuff") {}
public: // public:
virtual float GetValue(Action* action); // virtual float GetValue(Action* action);
private: // private:
}; // };
float AvoidAoeStrategyMultiplier::GetValue(Action* action) // float AvoidAoeStrategyMultiplier::GetValue(Action* action)
{ // {
if (!action) // if (!action)
return 1.0f; // return 1.0f;
std::string name = action->getName(); // std::string name = action->getName();
if (name == "follow" || name == "co" || name == "nc" || name == "drop target") // if (name == "follow" || name == "co" || name == "nc" || name == "drop target")
return 1.0f; // return 1.0f;
uint32 spellId = AI_VALUE2(uint32, "spell id", name); // uint32 spellId = AI_VALUE2(uint32, "spell id", name);
const SpellInfo* const pSpellInfo = sSpellMgr->GetSpellInfo(spellId); // const SpellInfo* const pSpellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!pSpellInfo) return 1.0f; // if (!pSpellInfo) return 1.0f;
if (spellId && pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION) // if (spellId && pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
return 1.0f; // return 1.0f;
else if (spellId && pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION) // else if (spellId && pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
return 1.0f; // return 1.0f;
uint32 castTime = pSpellInfo->CalcCastTime(bot); // uint32 castTime = pSpellInfo->CalcCastTime(bot);
if (AI_VALUE2(bool, "has area debuff", "self target") && spellId && castTime > 0) // if (AI_VALUE2(bool, "has area debuff", "self target") && spellId && castTime > 0)
{ // {
return 0.0f; // return 0.0f;
} // }
return 1.0f; // return 1.0f;
} // }
NextAction** AvoidAoeStrategy::getDefaultActions() NextAction** AvoidAoeStrategy::getDefaultActions()
{ {
@@ -82,3 +82,10 @@ void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
// multipliers.push_back(new AvoidAoeStrategyMultiplier(botAI)); // multipliers.push_back(new AvoidAoeStrategyMultiplier(botAI));
} }
NextAction** CombatFormationStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("combat formation move", ACTION_NORMAL),
nullptr);
}

View File

@@ -28,4 +28,12 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
}; };
class CombatFormationStrategy : public Strategy
{
public:
CombatFormationStrategy(PlayerbotAI* ai): Strategy(ai) {}
const std::string getName() override { return "combat formation"; }
NextAction** getDefaultActions() override;
};
#endif #endif

View File

@@ -37,9 +37,10 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("bg status", NextAction::array(0, new NextAction("bg status", relevance), nullptr))); triggers.push_back(new TriggerNode("bg status", NextAction::array(0, new NextAction("bg status", relevance), nullptr)));
triggers.push_back(new TriggerNode("xpgain", NextAction::array(0, new NextAction("xp gain", relevance), nullptr))); triggers.push_back(new TriggerNode("xpgain", NextAction::array(0, new NextAction("xp gain", relevance), nullptr)));
triggers.push_back(new TriggerNode("levelup", NextAction::array(0, triggers.push_back(new TriggerNode("levelup", NextAction::array(0,
new NextAction("auto talents", relevance), new NextAction("auto teleport for level", relevance + 3),
new NextAction("auto learn spell", relevance), new NextAction("auto talents", relevance + 2),
new NextAction("auto teleport for level", relevance), new NextAction("auto learn spell", relevance + 1),
new NextAction("auto upgrade equip", relevance),
nullptr))); nullptr)));
// triggers.push_back(new TriggerNode("group destroyed", NextAction::array(0, new NextAction("reset botAI", relevance), nullptr))); // triggers.push_back(new TriggerNode("group destroyed", NextAction::array(0, new NextAction("reset botAI", relevance), nullptr)));
triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr))); triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr)));

View File

@@ -203,12 +203,14 @@ class CastDragonsBreathAction : public CastSpellAction
{ {
public: public:
CastDragonsBreathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "dragon's breath") { } CastDragonsBreathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "dragon's breath") { }
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
class CastBlastWaveAction : public CastSpellAction class CastBlastWaveAction : public CastSpellAction
{ {
public: public:
CastBlastWaveAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "blast wave") { } CastBlastWaveAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "blast wave") { }
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
class CastInvisibilityAction : public CastBuffSpellAction class CastInvisibilityAction : public CastBuffSpellAction

View File

@@ -153,7 +153,7 @@ bool CastMeleeConsecrationAction::isUseful()
{ {
Unit* target = GetTarget(); Unit* target = GetTarget();
// float dis = distance + CONTACT_DISTANCE; // float dis = distance + CONTACT_DISTANCE;
return target && bot->IsWithinCombatRange(target, sPlayerbotAIConfig->meleeDistance); // sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", GetTargetName()), distance); return target && bot->IsWithinMeleeRange(target); // sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", GetTargetName()), distance);
} }
bool CastDivineSacrificeAction::isUseful() bool CastDivineSacrificeAction::isUseful()

View File

@@ -94,6 +94,9 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"medium group heal occasion", "medium group heal occasion",
NextAction::array(0, new NextAction("divine sacrifice", ACTION_HIGH + 5), nullptr))); NextAction::array(0, new NextAction("divine sacrifice", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode(
"enough mana",
NextAction::array(0, new NextAction("melee consecration", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"not facing target", "not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr))); NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr)));

View File

@@ -161,5 +161,6 @@ class CastMindSearAction : public CastSpellAction
{ {
public: public:
CastMindSearAction(PlayerbotAI* ai) : CastSpellAction(ai, "mind sear") {} CastMindSearAction(PlayerbotAI* ai) : CastSpellAction(ai, "mind sear") {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
#endif #endif

View File

@@ -49,9 +49,11 @@ void CasterShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers); GenericShamanStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), nullptr))); // triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), nullptr)));
triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("flametongue weapon", 23.0f), nullptr))); // triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("flametongue weapon", 23.0f), nullptr)));
triggers.push_back(new TriggerNode("main hand weapon no imbue", NextAction::array(0, new NextAction("flametongue weapon", 22.0f), nullptr)));
// triggers.push_back(new TriggerNode("searing totem", NextAction::array(0, new NextAction("searing totem", 19.0f), nullptr))); // triggers.push_back(new TriggerNode("searing totem", NextAction::array(0, new NextAction("searing totem", 19.0f), nullptr)));
triggers.push_back(new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr))); triggers.push_back(new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr)));
triggers.push_back(new TriggerNode("elemental mastery", NextAction::array(0, new NextAction("elemental mastery", 27.0f), nullptr)));
// triggers.push_back(new TriggerNode("frost shock snare", NextAction::array(0, new NextAction("frost shock", 21.0f), nullptr))); // triggers.push_back(new TriggerNode("frost shock snare", NextAction::array(0, new NextAction("frost shock", 21.0f), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"no fire totem", "no fire totem",

View File

@@ -35,7 +35,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
{ {
return new ActionNode ("flametongue weapon", return new ActionNode ("flametongue weapon",
/*P*/ nullptr, /*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("frostbrand weapon"), nullptr), /*A*/ NextAction::array(0, new NextAction("flametongue weapon"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }
@@ -43,7 +43,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
{ {
return new ActionNode ("frostbrand weapon", return new ActionNode ("frostbrand weapon",
/*P*/ nullptr, /*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("rockbiter weapon"), nullptr), /*A*/ NextAction::array(0, new NextAction("frostbrand weapon"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }
@@ -51,7 +51,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
{ {
return new ActionNode ("windfury weapon", return new ActionNode ("windfury weapon",
/*P*/ nullptr, /*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("rockbiter weapon"), nullptr), /*A*/ NextAction::array(0, new NextAction("windfury weapon"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }

View File

@@ -42,7 +42,8 @@ void HealShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
GenericShamanStrategy::InitTriggers(triggers); GenericShamanStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), nullptr))); // triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", ACTION_NORMAL + 9), nullptr)));
triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("earthliving weapon", 22.0f), nullptr))); // triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("earthliving weapon", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("main hand weapon no imbue", NextAction::array(0, new NextAction("earthliving weapon", 22.0f), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"group heal occasion", "group heal occasion",
NextAction::array(0, new NextAction("riptide on party", 23.0f), new NextAction("chain heal", 22.0f), NULL))); NextAction::array(0, new NextAction("riptide on party", 23.0f), new NextAction("chain heal", 22.0f), NULL)));

View File

@@ -63,7 +63,9 @@ void MeleeShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericShamanStrategy::InitTriggers(triggers); GenericShamanStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("flametongue weapon", 22.0f), nullptr))); //triggers.push_back(new TriggerNode("shaman weapon", NextAction::array(0, new NextAction("flametongue weapon", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("main hand weapon no imbue", NextAction::array(0, new NextAction("windfury weapon", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("off hand weapon no imbue", NextAction::array(0, new NextAction("flametongue weapon", 21.0f), nullptr)));
// triggers.push_back(new TriggerNode("searing totem", NextAction::array(0, new NextAction("reach melee", 22.0f), new NextAction("searing totem", 22.0f), nullptr))); // triggers.push_back(new TriggerNode("searing totem", NextAction::array(0, new NextAction("reach melee", 22.0f), new NextAction("searing totem", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr))); triggers.push_back(new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(

View File

@@ -323,6 +323,7 @@ class CastChainLightningAction : public CastSpellAction
{ {
public: public:
CastChainLightningAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "chain lightning") { } CastChainLightningAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "chain lightning") { }
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
class CastLightningBoltAction : public CastSpellAction class CastLightningBoltAction : public CastSpellAction
@@ -349,6 +350,12 @@ class CastBloodlustAction : public CastBuffSpellAction
CastBloodlustAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "bloodlust") { } CastBloodlustAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "bloodlust") { }
}; };
class CastElementalMasteryAction : public CastBuffSpellAction
{
public:
CastElementalMasteryAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "elemental mastery") { }
};
class CastWindShearOnEnemyHealerAction : public CastSpellOnEnemyHealerAction class CastWindShearOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
{ {
public: public:

View File

@@ -79,7 +79,9 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
creators["searing totem"] = &ShamanATriggerFactoryInternal::searing_totem; creators["searing totem"] = &ShamanATriggerFactoryInternal::searing_totem;
creators["wind shear"] = &ShamanATriggerFactoryInternal::wind_shear; creators["wind shear"] = &ShamanATriggerFactoryInternal::wind_shear;
creators["purge"] = &ShamanATriggerFactoryInternal::purge; creators["purge"] = &ShamanATriggerFactoryInternal::purge;
creators["shaman weapon"] = &ShamanATriggerFactoryInternal::shaman_weapon; //creators["shaman weapon"] = &ShamanATriggerFactoryInternal::shaman_weapon;
creators["main hand weapon no imbue"] = &ShamanATriggerFactoryInternal::main_hand_weapon_no_imbue;
creators["off hand weapon no imbue"] = &ShamanATriggerFactoryInternal::off_hand_weapon_no_imbue;
creators["water shield"] = &ShamanATriggerFactoryInternal::water_shield; creators["water shield"] = &ShamanATriggerFactoryInternal::water_shield;
creators["lightning shield"] = &ShamanATriggerFactoryInternal::lightning_shield; creators["lightning shield"] = &ShamanATriggerFactoryInternal::lightning_shield;
creators["water breathing"] = &ShamanATriggerFactoryInternal::water_breathing; creators["water breathing"] = &ShamanATriggerFactoryInternal::water_breathing;
@@ -96,6 +98,7 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
creators["frost shock snare"] = &ShamanATriggerFactoryInternal::frost_shock_snare; creators["frost shock snare"] = &ShamanATriggerFactoryInternal::frost_shock_snare;
creators["heroism"] = &ShamanATriggerFactoryInternal::heroism; creators["heroism"] = &ShamanATriggerFactoryInternal::heroism;
creators["bloodlust"] = &ShamanATriggerFactoryInternal::bloodlust; creators["bloodlust"] = &ShamanATriggerFactoryInternal::bloodlust;
creators["elemental mastery"] = &ShamanATriggerFactoryInternal::elemental_mastery;
creators["wind shear on enemy healer"] = &ShamanATriggerFactoryInternal::wind_shear_on_enemy_healer; creators["wind shear on enemy healer"] = &ShamanATriggerFactoryInternal::wind_shear_on_enemy_healer;
creators["cure poison"] = &ShamanATriggerFactoryInternal::cure_poison; creators["cure poison"] = &ShamanATriggerFactoryInternal::cure_poison;
creators["party member cure poison"] = &ShamanATriggerFactoryInternal::party_member_cure_poison; creators["party member cure poison"] = &ShamanATriggerFactoryInternal::party_member_cure_poison;
@@ -114,6 +117,7 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
static Trigger* maelstrom_weapon(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI); } static Trigger* maelstrom_weapon(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI); }
static Trigger* heroism(PlayerbotAI* botAI) { return new HeroismTrigger(botAI); } static Trigger* heroism(PlayerbotAI* botAI) { return new HeroismTrigger(botAI); }
static Trigger* bloodlust(PlayerbotAI* botAI) { return new BloodlustTrigger(botAI); } static Trigger* bloodlust(PlayerbotAI* botAI) { return new BloodlustTrigger(botAI); }
static Trigger* elemental_mastery(PlayerbotAI* botAI) { return new ElementalMasteryTrigger(botAI); }
static Trigger* party_member_cleanse_disease(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritDiseaseTrigger(botAI); } static Trigger* party_member_cleanse_disease(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritDiseaseTrigger(botAI); }
static Trigger* party_member_cleanse_curse(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritCurseTrigger(botAI); } static Trigger* party_member_cleanse_curse(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritCurseTrigger(botAI); }
static Trigger* party_member_cleanse_poison(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritPoisonTrigger(botAI); } static Trigger* party_member_cleanse_poison(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritPoisonTrigger(botAI); }
@@ -134,7 +138,9 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
static Trigger* searing_totem(PlayerbotAI* botAI) { return new SearingTotemTrigger(botAI); } static Trigger* searing_totem(PlayerbotAI* botAI) { return new SearingTotemTrigger(botAI); }
static Trigger* wind_shear(PlayerbotAI* botAI) { return new WindShearInterruptSpellTrigger(botAI); } static Trigger* wind_shear(PlayerbotAI* botAI) { return new WindShearInterruptSpellTrigger(botAI); }
static Trigger* purge(PlayerbotAI* botAI) { return new PurgeTrigger(botAI); } static Trigger* purge(PlayerbotAI* botAI) { return new PurgeTrigger(botAI); }
static Trigger* shaman_weapon(PlayerbotAI* botAI) { return new ShamanWeaponTrigger(botAI); } //static Trigger* shaman_weapon(PlayerbotAI* botAI) { return new ShamanWeaponTrigger(botAI); }
static Trigger* main_hand_weapon_no_imbue(PlayerbotAI* botAI) { return new MainHandWeaponNoImbueTrigger(botAI); }
static Trigger* off_hand_weapon_no_imbue(PlayerbotAI* botAI) { return new OffHandWeaponNoImbueTrigger(botAI); }
static Trigger* water_shield(PlayerbotAI* botAI) { return new WaterShieldTrigger(botAI); } static Trigger* water_shield(PlayerbotAI* botAI) { return new WaterShieldTrigger(botAI); }
static Trigger* lightning_shield(PlayerbotAI* botAI) { return new LightningShieldTrigger(botAI); } static Trigger* lightning_shield(PlayerbotAI* botAI) { return new LightningShieldTrigger(botAI); }
static Trigger* shock(PlayerbotAI* botAI) { return new ShockTrigger(botAI); } static Trigger* shock(PlayerbotAI* botAI) { return new ShockTrigger(botAI); }
@@ -206,6 +212,7 @@ class ShamanAiObjectContextInternal : public NamedObjectContext<Action>
creators["thunderstorm"] = &ShamanAiObjectContextInternal::thunderstorm; creators["thunderstorm"] = &ShamanAiObjectContextInternal::thunderstorm;
creators["heroism"] = &ShamanAiObjectContextInternal::heroism; creators["heroism"] = &ShamanAiObjectContextInternal::heroism;
creators["bloodlust"] = &ShamanAiObjectContextInternal::bloodlust; creators["bloodlust"] = &ShamanAiObjectContextInternal::bloodlust;
creators["elemental mastery"] = &ShamanAiObjectContextInternal::elemental_mastery;
// creators["cure disease"] = &ShamanAiObjectContextInternal::cure_disease; // creators["cure disease"] = &ShamanAiObjectContextInternal::cure_disease;
// creators["cure disease on party"] = &ShamanAiObjectContextInternal::cure_disease_on_party; // creators["cure disease on party"] = &ShamanAiObjectContextInternal::cure_disease_on_party;
// creators["cure poison"] = &ShamanAiObjectContextInternal::cure_poison; // creators["cure poison"] = &ShamanAiObjectContextInternal::cure_poison;
@@ -222,6 +229,7 @@ class ShamanAiObjectContextInternal : public NamedObjectContext<Action>
private: private:
static Action* heroism(PlayerbotAI* botAI) { return new CastHeroismAction(botAI); } static Action* heroism(PlayerbotAI* botAI) { return new CastHeroismAction(botAI); }
static Action* bloodlust(PlayerbotAI* botAI) { return new CastBloodlustAction(botAI); } static Action* bloodlust(PlayerbotAI* botAI) { return new CastBloodlustAction(botAI); }
static Action* elemental_mastery(PlayerbotAI* botAI) { return new CastElementalMasteryAction(botAI); }
static Action* thunderstorm(PlayerbotAI* botAI) { return new CastThunderstormAction(botAI); } static Action* thunderstorm(PlayerbotAI* botAI) { return new CastThunderstormAction(botAI); }
static Action* lightning_bolt(PlayerbotAI* botAI) { return new CastLightningBoltAction(botAI); } static Action* lightning_bolt(PlayerbotAI* botAI) { return new CastLightningBoltAction(botAI); }
static Action* chain_lightning(PlayerbotAI* botAI) { return new CastChainLightningAction(botAI); } static Action* chain_lightning(PlayerbotAI* botAI) { return new CastChainLightningAction(botAI); }

View File

@@ -5,6 +5,7 @@
#include "ShamanTriggers.h" #include "ShamanTriggers.h"
#include "Playerbots.h" #include "Playerbots.h"
/*
std::vector<std::string> ShamanWeaponTrigger::spells; std::vector<std::string> ShamanWeaponTrigger::spells;
bool ShamanWeaponTrigger::IsActive() bool ShamanWeaponTrigger::IsActive()
@@ -30,6 +31,21 @@ bool ShamanWeaponTrigger::IsActive()
return false; return false;
} }
*/
bool MainHandWeaponNoImbueTrigger::IsActive() {
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND );
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false;
return true;
}
bool OffHandWeaponNoImbueTrigger::IsActive() {
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND );
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false;
return true;
}
bool ShockTrigger::IsActive() bool ShockTrigger::IsActive()
{ {

View File

@@ -11,6 +11,7 @@
class PlayerbotAI; class PlayerbotAI;
/*
class ShamanWeaponTrigger : public BuffTrigger class ShamanWeaponTrigger : public BuffTrigger
{ {
public: public:
@@ -21,6 +22,21 @@ class ShamanWeaponTrigger : public BuffTrigger
private: private:
static std::vector<std::string> spells; static std::vector<std::string> spells;
}; };
*/
class MainHandWeaponNoImbueTrigger : public BuffTrigger
{
public:
MainHandWeaponNoImbueTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "main hand", 1) {}
virtual bool IsActive();
};
class OffHandWeaponNoImbueTrigger : public BuffTrigger
{
public:
OffHandWeaponNoImbueTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "off hand", 1) {}
virtual bool IsActive();
};
class TotemTrigger : public Trigger class TotemTrigger : public Trigger
{ {
@@ -201,6 +217,12 @@ class BloodlustTrigger : public BoostTrigger
BloodlustTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "bloodlust") { } BloodlustTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "bloodlust") { }
}; };
class ElementalMasteryTrigger : public BoostTrigger
{
public:
ElementalMasteryTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "elemental mastery") { }
};
class MaelstromWeaponTrigger : public HasAuraStackTrigger class MaelstromWeaponTrigger : public HasAuraStackTrigger
{ {
public: public:

View File

@@ -119,6 +119,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
creators["naxx"] = &ChatTriggerContext::naxx; creators["naxx"] = &ChatTriggerContext::naxx;
creators["bwl"] = &ChatTriggerContext::bwl; creators["bwl"] = &ChatTriggerContext::bwl;
creators["dps"] = &ChatTriggerContext::dps; creators["dps"] = &ChatTriggerContext::dps;
creators["disperse"] = &ChatTriggerContext::disperse;
} }
private: private:
@@ -218,6 +219,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
static Trigger* naxx(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "naxx"); } static Trigger* naxx(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "naxx"); }
static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); } static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); } static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
}; };
#endif #endif

View File

@@ -64,6 +64,11 @@ bool AlmostFullManaTrigger::IsActive()
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 85; return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 85;
} }
bool EnoughManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 65;
}
bool RageAvailable::IsActive() bool RageAvailable::IsActive()
{ {
return AI_VALUE2(uint8, "rage", "self target") >= amount; return AI_VALUE2(uint8, "rage", "self target") >= amount;

View File

@@ -30,6 +30,14 @@ class HighManaTrigger : public Trigger
bool IsActive() override; bool IsActive() override;
}; };
class EnoughManaTrigger : public Trigger
{
public:
EnoughManaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "enough mana") { }
bool IsActive() override;
};
class AlmostFullManaTrigger : public Trigger class AlmostFullManaTrigger : public Trigger
{ {
public: public:

View File

@@ -45,6 +45,7 @@ class TriggerContext : public NamedObjectContext<Trigger>
creators["medium mana"] = &TriggerContext::MediumMana; creators["medium mana"] = &TriggerContext::MediumMana;
creators["high mana"] = &TriggerContext::HighMana; creators["high mana"] = &TriggerContext::HighMana;
creators["almost full mana"] = &TriggerContext::AlmostFullMana; creators["almost full mana"] = &TriggerContext::AlmostFullMana;
creators["enough mana"] = &TriggerContext::EnoughMana;
creators["party member critical health"] = &TriggerContext::PartyMemberCriticalHealth; creators["party member critical health"] = &TriggerContext::PartyMemberCriticalHealth;
creators["party member low health"] = &TriggerContext::PartyMemberLowHealth; creators["party member low health"] = &TriggerContext::PartyMemberLowHealth;
@@ -253,6 +254,7 @@ class TriggerContext : public NamedObjectContext<Trigger>
static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); } static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); }
static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); } static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); }
static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); } static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); }
static Trigger* EnoughMana(PlayerbotAI* botAI) { return new EnoughManaTrigger(botAI); }
static Trigger* LightRageAvailable(PlayerbotAI* botAI) { return new LightRageAvailableTrigger(botAI); } static Trigger* LightRageAvailable(PlayerbotAI* botAI) { return new LightRageAvailableTrigger(botAI); }
static Trigger* MediumRageAvailable(PlayerbotAI* botAI) { return new MediumRageAvailableTrigger(botAI); } static Trigger* MediumRageAvailable(PlayerbotAI* botAI) { return new MediumRageAvailableTrigger(botAI); }
static Trigger* HighRageAvailable(PlayerbotAI* botAI) { return new HighRageAvailableTrigger(botAI); } static Trigger* HighRageAvailable(PlayerbotAI* botAI) { return new HighRageAvailableTrigger(botAI); }

View File

@@ -48,6 +48,19 @@ GuidVector AttackersValue::Calculate()
if (bot->duel && bot->duel->Opponent) if (bot->duel && bot->duel->Opponent)
result.push_back(bot->duel->Opponent->GetGUID()); result.push_back(bot->duel->Opponent->GetGUID());
// workaround for bots of same faction not fighting in arena
if (bot->InArena())
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible targets");
for (ObjectGuid const guid : possibleTargets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot)) {
result.push_back(unit->GetGUID());
}
}
}
return result; return result;
} }
@@ -159,7 +172,7 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range)
// (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) && attacker->CanSeeOrDetect(bot) && // (inCannon || !attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) && attacker->CanSeeOrDetect(bot) &&
// !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) && !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/ attacker->isFeared()) && !rti) && // !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) && !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/ attacker->isFeared()) && !rti) &&
/*!sServerFacade->IsInRoots(attacker) &&*/ /*!sServerFacade->IsInRoots(attacker) &&*/
!attacker->IsFriendlyTo(bot) && bot->IsWithinDistInMap(attacker, range) && !attacker->IsFriendlyTo(bot) &&
!attacker->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) && !attacker->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) &&
// !(attacker->GetGUID().IsPet() && enemy) && // !(attacker->GetGUID().IsPet() && enemy) &&
!(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) && !(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&

View File

@@ -180,6 +180,8 @@ uint32 MoneyNeededForValue::Calculate()
case NeedMoneyFor::tradeskill: case NeedMoneyFor::tradeskill:
moneyWanted = (level * level * level); //Or level^3 (10s @ lvl10, 3g @ lvl30, 20g @ lvl60, 50g @ lvl80): Todo replace (Should be buyable reagents that combined allow crafting of usefull items) moneyWanted = (level * level * level); //Or level^3 (10s @ lvl10, 3g @ lvl30, 20g @ lvl60, 50g @ lvl80): Todo replace (Should be buyable reagents that combined allow crafting of usefull items)
break; break;
default:
break;
} }
return moneyWanted; return moneyWanted;

View File

@@ -8,7 +8,7 @@
#ifndef WIN32 #ifndef WIN32
inline int strcmpi(char const* s1, char const* s2) inline int strcmpi(char const* s1, char const* s2)
{ {
for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2); for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2) {}
return *s1 - *s2; return *s1 - *s2;
} }
#endif #endif

View File

@@ -14,7 +14,7 @@ class Unit;
class PartyMemberToDispel : public PartyMemberValue, public Qualified class PartyMemberToDispel : public PartyMemberValue, public Qualified
{ {
public: public:
PartyMemberToDispel(PlayerbotAI* botAI, std::string const name = "party member to dispel") : PartyMemberValue(botAI, name, 2 * 1000), Qualified() { } PartyMemberToDispel(PlayerbotAI* botAI, std::string const name = "party member to dispel") : PartyMemberValue(botAI, name, 1000), Qualified() { }
protected: protected:
Unit* Calculate() override; Unit* Calculate() override;

View File

@@ -74,7 +74,7 @@ bool PartyMemberToHeal::Check(Unit* player)
{ {
// return player && player != bot && player->GetMapId() == bot->GetMapId() && player->IsInWorld() && // return player && player != bot && player->GetMapId() == bot->GetMapId() && player->IsInWorld() &&
// sServerFacade->GetDistance2d(bot, player) < (player->IsPlayer() && botAI->IsTank((Player*)player) ? 50.0f : 40.0f); // sServerFacade->GetDistance2d(bot, player) < (player->IsPlayer() && botAI->IsTank((Player*)player) ? 50.0f : 40.0f);
return player->GetMapId() == bot->GetMapId() && return player->GetMapId() == bot->GetMapId() && !player->IsCharmed() &&
bot->GetDistance2d(player) < sPlayerbotAIConfig->healDistance * 2 && bot->GetDistance2d(player) < sPlayerbotAIConfig->healDistance * 2 &&
bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()); bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ());
} }

View File

@@ -301,6 +301,10 @@ class ValueContext : public NamedObjectContext<UntypedValue>
creators["expected group dps"] = &ValueContext::expected_group_dps; creators["expected group dps"] = &ValueContext::expected_group_dps;
creators["area debuff"] = &ValueContext::area_debuff; creators["area debuff"] = &ValueContext::area_debuff;
creators["nearest trap with damage"] = &ValueContext::nearest_trap_with_damange; creators["nearest trap with damage"] = &ValueContext::nearest_trap_with_damange;
creators["disperse distance"] = &ValueContext::disperse_distance;
creators["last flee angle"] = &ValueContext::last_flee_angle;
creators["last flee timestamp"] = &ValueContext::last_flee_timestamp;
creators["recently flee info"] = &ValueContext::recently_flee_info;
} }
private: private:
@@ -505,6 +509,10 @@ class ValueContext : public NamedObjectContext<UntypedValue>
static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(ai); } static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(ai); }
static UntypedValue* area_debuff(PlayerbotAI* ai) { return new AreaDebuffValue(ai); } static UntypedValue* area_debuff(PlayerbotAI* ai) { return new AreaDebuffValue(ai); }
static UntypedValue* nearest_trap_with_damange(PlayerbotAI* ai) { return new NearestTrapWithDamageValue(ai); } static UntypedValue* nearest_trap_with_damange(PlayerbotAI* ai) { return new NearestTrapWithDamageValue(ai); }
static UntypedValue* disperse_distance(PlayerbotAI* ai) { return new DisperseDistanceValue(ai); }
static UntypedValue* last_flee_angle(PlayerbotAI* ai) { return new LastFleeAngleValue(ai); }
static UntypedValue* last_flee_timestamp(PlayerbotAI* ai) { return new LastFleeTimestampValue(ai); }
static UntypedValue* recently_flee_info(PlayerbotAI* ai) { return new RecentlyFleeInfo(ai); }
}; };
#endif #endif

View File

@@ -87,7 +87,10 @@ void DpsWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void DpsAoeWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void DpsAoeWarlockStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0, new NextAction("rain of fire", 37.0f), nullptr))); triggers.push_back(new TriggerNode("medium aoe", NextAction::array(0,
new NextAction("seed of corruption", 39.0f),
new NextAction("seed of corruption on attacker", 38.0f),
new NextAction("rain of fire", 37.0f), nullptr)));
triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 27.0f), nullptr))); triggers.push_back(new TriggerNode("corruption on attacker", NextAction::array(0, new NextAction("corruption on attacker", 27.0f), nullptr)));
triggers.push_back(new TriggerNode("unstable affliction on attacker", NextAction::array(0, new NextAction("unstable affliction on attacker", 26.0f), NULL))); triggers.push_back(new TriggerNode("unstable affliction on attacker", NextAction::array(0, new NextAction("unstable affliction on attacker", 26.0f), NULL)));
triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 25.0f), nullptr))); triggers.push_back(new TriggerNode("curse of agony on attacker", NextAction::array(0, new NextAction("curse of agony on attacker", 25.0f), nullptr)));

View File

@@ -67,12 +67,18 @@ class CastCorruptionAction : public CastDebuffSpellAction
{ {
public: public:
CastCorruptionAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "corruption", true) { } CastCorruptionAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "corruption", true) { }
bool isUseful() override {
return CastDebuffSpellAction::isUseful() && !botAI->HasAura("seed of corruption", GetTarget(), false, true) ;
}
}; };
class CastCorruptionOnAttackerAction : public CastDebuffSpellOnAttackerAction class CastCorruptionOnAttackerAction : public CastDebuffSpellOnAttackerAction
{ {
public: public:
CastCorruptionOnAttackerAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "corruption", true) { } CastCorruptionOnAttackerAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "corruption", true) { }
bool isUseful() override {
return CastDebuffSpellOnAttackerAction::isUseful() && !botAI->HasAura("seed of corruption", GetTarget(), false, true) ;
}
}; };
class CastCurseOfAgonyOnAttackerAction : public CastDebuffSpellOnAttackerAction class CastCurseOfAgonyOnAttackerAction : public CastDebuffSpellOnAttackerAction
@@ -142,6 +148,20 @@ class CastSeedOfCorruptionAction : public CastDebuffSpellAction
{ {
public: public:
CastSeedOfCorruptionAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "seed of corruption", true, 0) { } CastSeedOfCorruptionAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "seed of corruption", true, 0) { }
bool isUseful() override {
return CastDebuffSpellAction::isUseful() && !botAI->HasAura("corruption", GetTarget(), false, true) ;
}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
};
class CastSeedOfCorruptionOnAttackerAction : public CastDebuffSpellOnAttackerAction
{
public:
CastSeedOfCorruptionOnAttackerAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "seed of corruption", true, 0) { }
bool isUseful() override {
return CastDebuffSpellOnAttackerAction::isUseful() && !botAI->HasAura("corruption", GetTarget(), false, true) ;
}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
}; };
class CastRainOfFireAction : public CastSpellAction class CastRainOfFireAction : public CastSpellAction

View File

@@ -158,6 +158,7 @@ class WarlockAiObjectContextInternal : public NamedObjectContext<Action>
creators["banish"] = &WarlockAiObjectContextInternal::banish; creators["banish"] = &WarlockAiObjectContextInternal::banish;
creators["banish on cc"] = &WarlockAiObjectContextInternal::banish_on_cc; creators["banish on cc"] = &WarlockAiObjectContextInternal::banish_on_cc;
creators["seed of corruption"] = &WarlockAiObjectContextInternal::seed_of_corruption; creators["seed of corruption"] = &WarlockAiObjectContextInternal::seed_of_corruption;
creators["seed of corruption on attacker"] = &WarlockAiObjectContextInternal::seed_of_corruption_on_attacker;
creators["rain of fire"] = &WarlockAiObjectContextInternal::rain_of_fire; creators["rain of fire"] = &WarlockAiObjectContextInternal::rain_of_fire;
creators["shadowfury"] = &WarlockAiObjectContextInternal::shadowfury; creators["shadowfury"] = &WarlockAiObjectContextInternal::shadowfury;
creators["life tap"] = &WarlockAiObjectContextInternal::life_tap; creators["life tap"] = &WarlockAiObjectContextInternal::life_tap;
@@ -209,6 +210,7 @@ class WarlockAiObjectContextInternal : public NamedObjectContext<Action>
static Action* banish(PlayerbotAI* botAI) { return new CastBanishAction(botAI); } static Action* banish(PlayerbotAI* botAI) { return new CastBanishAction(botAI); }
static Action* banish_on_cc(PlayerbotAI* botAI) { return new CastBanishAction(botAI); } static Action* banish_on_cc(PlayerbotAI* botAI) { return new CastBanishAction(botAI); }
static Action* seed_of_corruption(PlayerbotAI* botAI) { return new CastSeedOfCorruptionAction(botAI); } static Action* seed_of_corruption(PlayerbotAI* botAI) { return new CastSeedOfCorruptionAction(botAI); }
static Action* seed_of_corruption_on_attacker(PlayerbotAI* botAI) { return new CastSeedOfCorruptionOnAttackerAction(botAI); }
static Action* rain_of_fire(PlayerbotAI* botAI) { return new CastRainOfFireAction(botAI); } static Action* rain_of_fire(PlayerbotAI* botAI) { return new CastRainOfFireAction(botAI); }
static Action* shadowfury(PlayerbotAI* botAI) { return new CastShadowfuryAction(botAI); } static Action* shadowfury(PlayerbotAI* botAI) { return new CastShadowfuryAction(botAI); }
static Action* life_tap(PlayerbotAI* botAI) { return new CastLifeTapAction(botAI); } static Action* life_tap(PlayerbotAI* botAI) { return new CastLifeTapAction(botAI); }

View File

@@ -6,6 +6,7 @@
#define _PLAYERBOT_WARLOCKTRIGGERS_H #define _PLAYERBOT_WARLOCKTRIGGERS_H
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "PlayerbotAI.h"
class PlayerbotAI; class PlayerbotAI;
@@ -32,13 +33,24 @@ class CurseOfAgonyTrigger : public DebuffTrigger
CurseOfAgonyTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of agony", 1, true, 20.0f) { } CurseOfAgonyTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "curse of agony", 1, true, 20.0f) { }
}; };
DEBUFF_CHECKISOWNER_TRIGGER(CorruptionTrigger, "corruption"); class CorruptionTrigger : public DebuffTrigger
{
public:
CorruptionTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "corruption", 1, true) { } \
bool IsActive() override {
return DebuffTrigger::IsActive() && !botAI->HasAura("seed of corruption", GetTarget(), false, true) ;
}
};
DEBUFF_CHECKISOWNER_TRIGGER(SiphonLifeTrigger, "siphon life"); DEBUFF_CHECKISOWNER_TRIGGER(SiphonLifeTrigger, "siphon life");
class CorruptionOnAttackerTrigger : public DebuffOnAttackerTrigger class CorruptionOnAttackerTrigger : public DebuffOnAttackerTrigger
{ {
public: public:
CorruptionOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "corruption", true) { } CorruptionOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "corruption", true) { }
bool IsActive() override {
return DebuffOnAttackerTrigger::IsActive() && !botAI->HasAura("seed of corruption", GetTarget(), false, true) ;
}
}; };
class CastCurseOfAgonyOnAttackerTrigger : public DebuffOnAttackerTrigger class CastCurseOfAgonyOnAttackerTrigger : public DebuffOnAttackerTrigger

View File

@@ -32,7 +32,9 @@ ArmsWarriorStrategy::ArmsWarriorStrategy(PlayerbotAI* botAI) : GenericWarriorStr
NextAction** ArmsWarriorStrategy::getDefaultActions() NextAction** ArmsWarriorStrategy::getDefaultActions()
{ {
return NextAction::array(0, new NextAction("heroic strike", ACTION_DEFAULT), nullptr); return NextAction::array(0,
new NextAction("bladestorm", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
} }
void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -56,4 +58,5 @@ void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("rend", NextAction::array(0, new NextAction("rend", ACTION_HIGH + 5), nullptr))); triggers.push_back(new TriggerNode("rend", NextAction::array(0, new NextAction("rend", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("rend on attacker", NextAction::array(0, new NextAction("rend on attacker", ACTION_HIGH + 5), nullptr))); triggers.push_back(new TriggerNode("rend on attacker", NextAction::array(0, new NextAction("rend on attacker", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("critical health", NextAction::array(0, new NextAction("intimidating shout", ACTION_EMERGENCY), nullptr))); triggers.push_back(new TriggerNode("critical health", NextAction::array(0, new NextAction("intimidating shout", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("medium rage available", NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 1), nullptr)));
} }

View File

@@ -61,7 +61,7 @@ void FuryWarriorStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
triggers.push_back(new TriggerNode("bloodthirst", NextAction::array(0, new NextAction("bloodthirst", ACTION_HIGH + 7), nullptr))); triggers.push_back(new TriggerNode("bloodthirst", NextAction::array(0, new NextAction("bloodthirst", ACTION_HIGH + 7), nullptr)));
triggers.push_back(new TriggerNode("instant slam", NextAction::array(0, new NextAction("slam", ACTION_HIGH + 5), nullptr))); triggers.push_back(new TriggerNode("instant slam", NextAction::array(0, new NextAction("slam", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 2), nullptr))); triggers.push_back(new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode("high rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 1), NULL))); triggers.push_back(new TriggerNode("medium rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 1), NULL)));
// triggers.push_back(new TriggerNode("berserker rage", NextAction::array(0, new NextAction("berserker rage", ACTION_HIGH + 2), nullptr))); // triggers.push_back(new TriggerNode("berserker rage", NextAction::array(0, new NextAction("berserker rage", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode("light aoe", NextAction::array(0, // triggers.push_back(new TriggerNode("light aoe", NextAction::array(0,
// new NextAction("whirlwind", ACTION_HIGH + 2), // new NextAction("whirlwind", ACTION_HIGH + 2),

View File

@@ -88,4 +88,6 @@ void TankWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("rend", NextAction::array(0, new NextAction("rend", ACTION_NORMAL + 1), nullptr))); triggers.push_back(new TriggerNode("rend", NextAction::array(0, new NextAction("rend", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode("rend on attacker", NextAction::array(0, new NextAction("rend on attacker", ACTION_NORMAL + 1), nullptr))); triggers.push_back(new TriggerNode("rend on attacker", NextAction::array(0, new NextAction("rend on attacker", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode("protect party member", NextAction::array(0, new NextAction("intervene", ACTION_EMERGENCY), nullptr))); triggers.push_back(new TriggerNode("protect party member", NextAction::array(0, new NextAction("intervene", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("high rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode("medium rage available", NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 1), nullptr)));
} }