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

View File

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

View File

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

View File

@@ -352,10 +352,16 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
Player* owner = holder.GetOwner();
if (!helper.ParseChatCommand(command, owner) && holder.GetType() == CHAT_MSG_WHISPER)
{
std::ostringstream out;
out << "Unknown command " << command;
TellMaster(out);
helper.ParseChatCommand("help");
// To prevent spam caused by WIM
if (!(command.rfind("WIM", 0) == 0) &&
!(command.rfind("QHpr", 0) == 0)
)
{
std::ostringstream out;
out << "Unknown command " << command;
TellMaster(out);
helper.ParseChatCommand("help");
}
}
chatCommands.pop();
@@ -1810,7 +1816,7 @@ Player* PlayerbotAI::GetPlayer(ObjectGuid guid)
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) {
Field* fields = results->Fetch();
return fields[0].Get<uint32>();
@@ -1900,19 +1906,19 @@ bool PlayerbotAI::TellMasterNoFacing(std::string const text, PlayerbotSecurityLe
return false;
time_t lastSaid = whispers[text];
// Yunfan: Remove tell cooldown
// if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
// {
whispers[text] = time(nullptr);
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{
whispers[text] = time(nullptr);
ChatMsg type = CHAT_MSG_WHISPER;
if (currentChat.second - time(nullptr) >= 1)
type = currentChat.first;
ChatMsg type = CHAT_MSG_WHISPER;
if (currentChat.second - time(nullptr) >= 1)
type = currentChat.first;
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());
master->SendDirectMessage(&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());
master->SendDirectMessage(&data);
}
return true;
}

View File

@@ -53,12 +53,13 @@ bool PlayerbotAIConfig::Initialize()
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 1500);
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
maxMovementSearchTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxMovementSearchTime", 3);
expireActionTime = sConfigMgr->GetOption<int32>("AiPlayerbot.ExpireActionTime", 5000);
dispelAuraDuration = sConfigMgr->GetOption<int32>("AiPlayerbot.DispelAuraDuration", 7000);
reactDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReactDelay", 500);
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);
rpgDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.RpgDelay", 10000);
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
@@ -94,7 +95,10 @@ bool PlayerbotAIConfig::Initialize()
autoAvoidAoe = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoAvoidAoe", 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);
randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f);
@@ -123,7 +127,7 @@ bool PlayerbotAIConfig::Initialize()
minRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotInWorldTime", 2 * HOUR);
maxRandomBotInWorldTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotInWorldTime", 12 * 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);
maxRandomBotChangeStrategyTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotChangeStrategyTime", 2 * HOUR);
minRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotReviveTime", MINUTE);
@@ -272,7 +276,11 @@ bool PlayerbotAIConfig::Initialize()
equipmentPersistence = sConfigMgr->GetOption<bool>("AiPlayerbot.EquipmentPersistence", false);
equipmentPersistenceLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.EquipmentPersistenceLevel", 80);
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);
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
@@ -292,7 +300,8 @@ bool PlayerbotAIConfig::Initialize()
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
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);
enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
@@ -327,6 +336,9 @@ bool PlayerbotAIConfig::Initialize()
selfBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.SelfBotLevel", 1);
RandomPlayerbotFactory::CreateRandomBots();
if (World::IsStopped()) {
return true;
}
PlayerbotFactory::Init();
sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot();

View File

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

View File

@@ -149,7 +149,10 @@ void PlayerbotFactory::Prepare()
{
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())
@@ -1453,6 +1456,14 @@ void PlayerbotFactory::InitEquipment(bool incremental)
if (itemId == 46978) { // shaman earth ring totem
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);
if (!proto)
continue;
@@ -3451,15 +3462,12 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
continue;
}
// disable next expansion
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 69 && enchantSpell >= 25072) {
// disable next expansion enchantments
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 25072)
continue;
}
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 79 && enchantSpell > 48557) {
if (sPlayerbotAIConfig->limitEnchantExpansion && bot->GetLevel() <= 70 && enchantSpell > 48557)
continue;
}
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 +
resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration +
spell_penetration + armor + rangeDps + meleeDps) * 0.001;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) {
// 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 ||
cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -3832,7 +3841,7 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
) {
// SPELL DPS
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
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -3840,9 +3849,9 @@ float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot)
) {
// HEALER
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)
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
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(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
// 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
+ 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) ||
(cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD
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
+ 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){
// BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2
+ 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 {
// 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
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ 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 +
resilience + hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration +
spell_penetration + armor + dps) * 0.001;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) {
// 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 ||
cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -4253,7 +4263,7 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
) {
// SPELL DPS
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
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -4261,9 +4271,9 @@ float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot)
) {
// HEALER
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)
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
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(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
// 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
+ 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) ||
(cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD
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
+ 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){
// BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2
+ 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 {
// 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
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ 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;
// todo: remove duplicate code
if (cls == CLASS_HUNTER) {
// 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 ||
cls == CLASS_MAGE ||
(cls == CLASS_PRIEST && tab == 2) || // shadow
@@ -4532,7 +4543,7 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
) {
// SPELL DPS
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
(cls == CLASS_PRIEST && tab != 2) || // discipline / holy
(cls == CLASS_SHAMAN && tab == 2) || // heal
@@ -4540,9 +4551,9 @@ float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32
) {
// HEALER
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)
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
(cls == CLASS_WARRIOR && tab != 2) || // arm / fury
(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
// 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
+ 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) ||
(cls == CLASS_PALADIN && tab == 1)) {
// TANK WITH SHIELD
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
+ 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){
// BLOOD DK TANK
score += strength * 1 + agility * 2 + attack_power * 0.2
+ 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 {
// 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
+ defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5
+ 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)
{
// has bot already been added?
Player* loginBot = ObjectAccessor::FindConnectedPlayer(holder.GetGuid());
if (loginBot && loginBot->IsInWorld()) {
return;
}
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);
@@ -363,6 +369,11 @@ Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const
void PlayerbotHolder::OnBotLogin(Player* const bot)
{
// Prevent duplicate login
if (playerBots.find(bot->GetGUID()) != playerBots.end()) {
return;
}
sPlayerbotsMgr->AddPlayerbotData(bot, true);
playerBots[bot->GetGUID()] = 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.";
}
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 (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_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_VALUE2(type, name, param, value) context->GetValue<type>(name, param)->Set(value)
#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_TROLL);
availableRaces[CLASS_PRIEST].push_back(RACE_UNDEAD_PLAYER);
availableRaces[CLASS_PRIEST].push_back(RACE_DRAENEI);
if(expansion >= EXPANSION_THE_BURNING_CRUSADE)
{
availableRaces[CLASS_PRIEST].push_back(RACE_DRAENEI);
availableRaces[CLASS_PRIEST].push_back(RACE_BLOODELF);
}

View File

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

View File

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

View File

@@ -16,6 +16,16 @@
class PlayerbotAI;
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;
class UntypedValue : public AiNamedObject
@@ -37,6 +47,7 @@ class Value
virtual ~Value() { }
virtual T Get() = 0;
virtual T LazyGet() = 0;
virtual T& RefGet() = 0;
virtual void Reset() { }
virtual void Set(T value) = 0;
operator T() { return Get(); }
@@ -79,7 +90,26 @@ class CalculatedValue : public UntypedValue, public Value<T>
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 Update() override {}
void Reset() override { lastCheckTime = 0; }
@@ -304,6 +334,7 @@ class ManualSetValue : public UntypedValue, public Value<T>
T Get() override { return value; }
T LazyGet() override { return value; }
T& RefGet() override { return value; }
void Set(T val) override { value = val; }
void Update() override {}
void Reset() override
@@ -326,4 +357,32 @@ class UnitManualSetValue : public ManualSetValue<Unit*>
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

View File

@@ -89,6 +89,8 @@ class ActionContext : public NamedObjectContext<Action>
creators["flee"] = &ActionContext::flee;
creators["flee with pet"] = &ActionContext::flee_with_pet;
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["shoot"] = &ActionContext::shoot;
creators["lifeblood"] = &ActionContext::lifeblood;
@@ -150,6 +152,7 @@ class ActionContext : public NamedObjectContext<Action>
creators["auto talents"] = &ActionContext::auto_talents;
creators["auto learn spell"] = &ActionContext::auto_learn_spell;
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["invite nearby"] = &ActionContext::invite_nearby;
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_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(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* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(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_learn_spell(PlayerbotAI* botAI) { return new AutoLearnSpellAction(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* invite_nearby(PlayerbotAI* botAI) { return new InviteNearbyToGroupAction(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;
}
if (bot->IsMounted() && bot->IsWithinLOSInMap(target) && sServerFacade->GetDistance2d(bot, target) < 40.0f)
if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
{
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);

View File

@@ -24,7 +24,6 @@ bool AutoLearnSpellAction::Execute(Event event)
out << ".";
botAI->TellMaster(out);
}
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);
};
class AutoUpgradeEquipAction : public Action
{
public:
AutoUpgradeEquipAction(PlayerbotAI* botAI, std::string const name = "auto upgrade equip") : Action(botAI, name) { }
bool Execute(Event event);
};
#endif

View File

@@ -6,8 +6,6 @@
#include "SharedDefines.h"
bool AutoTeleportForLevelAction::Execute(Event event) {
AutoUpgradeEquip();
if (!sPlayerbotAIConfig->autoTeleportForLevel || !sRandomPlayerbotMgr->IsRandomBot(bot)) {
return false;
}
@@ -16,15 +14,4 @@ bool AutoTeleportForLevelAction::Execute(Event event) {
}
sRandomPlayerbotMgr->RandomTeleportForLevel(bot);
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) { }
bool Execute(Event event);
private:
void AutoUpgradeEquip();
};
#endif

View File

@@ -459,7 +459,7 @@ bool BGJoinAction::JoinQueue(uint32 type)
// get BG MapId
uint32 bgTypeId_ = bgTypeId;
uint32 instanceId = 0; // 0 = First Available
bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID();
bool isPremade = false;
bool isArena = false;
bool isRated = false;
@@ -483,6 +483,11 @@ bool BGJoinAction::JoinQueue(uint32 type)
return false;
}
// refresh food/regs
sRandomPlayerbotMgr->Refresh(bot);
bool joinAsGroup = bot->GetGroup() && bot->GetGroup()->GetLeaderGUID() == bot->GetGUID();
// in wotlk only arena requires battlemaster guid
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(),
isRated ? "Rated Arena" : isArena ? "Arena" : "");
// refresh food/regs
sRandomPlayerbotMgr->Refresh(bot);
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 };
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 };
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 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_ICEBLOOD_GARRISON_WAITING_ALLIANCE = { -492.17f, -187.077f, 57.1342f, 2.77f };
Position const AV_STONEHEARTH_WAITING_HORDE = { 28.1264f, -302.593f, 15.076f, 2.96f };
Position const AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE = { -523.105f, -182.178f, 57.956f, 2.77f };
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_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 =
{
{ -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 },
{ -839.408f, -499.746f, 49.7505f, 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,
};
static uint32 AV_HordeAttackObjectives[] =
static std::pair<uint32, uint32> AV_HordeAttackObjectives[] =
{
// Attack
{ BG_AV_NODES_STONEHEART_BUNKER },
{ BG_AV_NODES_STONEHEART_GRAVE },
{ BG_AV_NODES_ICEWING_BUNKER },
{ BG_AV_NODES_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH },
{ BG_AV_NODES_FIRSTAID_STATION }
{ BG_AV_NODES_STONEHEART_BUNKER, BG_AV_OBJECT_FLAG_A_STONEHEART_BUNKER },
{ BG_AV_NODES_STONEHEART_GRAVE, BG_AV_OBJECT_FLAG_A_STONEHEART_GRAVE },
{ BG_AV_NODES_ICEWING_BUNKER, BG_AV_OBJECT_FLAG_A_ICEWING_BUNKER },
{ BG_AV_NODES_STORMPIKE_GRAVE, BG_AV_OBJECT_FLAG_A_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_NORTH },
{ BG_AV_NODES_FIRSTAID_STATION, BG_AV_OBJECT_FLAG_A_FIRSTAID_STATION }
};
static uint32 AV_HordeDefendObjectives[] =
static std::pair<uint32, uint32> AV_HordeDefendObjectives[] =
{
// Defend
{ BG_AV_NODES_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER },
{ BG_AV_NODES_TOWER_POINT },
{ BG_AV_NODES_ICEBLOOD_TOWER },
{ BG_AV_NODES_FROSTWOLF_GRAVE, BG_AV_OBJECT_FLAG_H_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_WTOWER },
{ BG_AV_NODES_TOWER_POINT, BG_AV_OBJECT_FLAG_H_TOWER_POINT },
{ BG_AV_NODES_ICEBLOOD_TOWER, BG_AV_OBJECT_FLAG_H_ICEBLOOD_TOWER },
};
static uint32 AV_AllianceAttackObjectives[] =
static std::pair<uint32, uint32> AV_AllianceAttackObjectives[] =
{
// Attack
{ BG_AV_NODES_ICEBLOOD_TOWER },
{ BG_AV_NODES_ICEBLOOD_GRAVE },
{ BG_AV_NODES_TOWER_POINT },
{ BG_AV_NODES_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER },
{ BG_AV_NODES_FROSTWOLF_HUT },
{ BG_AV_NODES_ICEBLOOD_TOWER, BG_AV_OBJECT_FLAG_H_ICEBLOOD_TOWER},
{ BG_AV_NODES_ICEBLOOD_GRAVE, BG_AV_OBJECT_FLAG_H_ICEBLOOD_GRAVE},
{ BG_AV_NODES_TOWER_POINT, BG_AV_OBJECT_FLAG_H_TOWER_POINT },
{ BG_AV_NODES_FROSTWOLF_GRAVE, BG_AV_OBJECT_FLAG_H_FROSTWOLF_GRAVE },
{ BG_AV_NODES_FROSTWOLF_ETOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_ETOWER },
{ BG_AV_NODES_FROSTWOLF_WTOWER, BG_AV_OBJECT_FLAG_H_FROSTWOLF_WTOWER },
{ BG_AV_NODES_FROSTWOLF_HUT, BG_AV_OBJECT_FLAG_H_FROSTWOLF_HUT },
};
static uint32 AV_AllianceDefendObjectives[] =
static std::pair<uint32, uint32> AV_AllianceDefendObjectives[] =
{
// Defend
{ BG_AV_NODES_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH },
{ BG_AV_NODES_ICEWING_BUNKER },
{ BG_AV_NODES_STONEHEART_BUNKER },
{ BG_AV_NODES_STORMPIKE_GRAVE, BG_AV_OBJECT_FLAG_A_STORMPIKE_GRAVE },
{ BG_AV_NODES_DUNBALDAR_SOUTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_SOUTH },
{ BG_AV_NODES_DUNBALDAR_NORTH, BG_AV_OBJECT_FLAG_A_DUNBALDAR_NORTH },
{ BG_AV_NODES_ICEWING_BUNKER, BG_AV_OBJECT_FLAG_A_ICEWING_BUNKER },
{ BG_AV_NODES_STONEHEART_BUNKER, BG_AV_OBJECT_FLAG_A_STONEHEART_BUNKER },
};
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_FIRSTAID_STATION).TotalOwnerId != TEAM_ALLIANCE)
{
if (Creature* pVanndar = bg->GetBGCreature(AV_NPC_A_BOSS))
if (Creature* pVanndar = bg->GetBGCreature(AV_CPLACE_TRIGGER17))
{
BgObjective = pVanndar;
endBoss = true;
@@ -2952,8 +2965,9 @@ bool BGTactics::selectObjective(bool reset)
// Only go to Snowfall Graveyard if already close to it.
// Need to fix AV script
if (!BgObjective && supporter && (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_ALLIANCE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE || alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_OTHER))
if (!BgObjective && supporter &&
(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 (bot->IsWithinDist(pGO, 200.f))
@@ -2967,19 +2981,20 @@ bool BGTactics::selectObjective(bool reset)
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)
{
uint32 attackCount = 0;
attackCount += getDefendersCount(AV_STONEHEARTH_WAITING_HORDE, 10.0f, false);
uint32 attackCount = getDefendersCount(AV_STONEHEARTH_WAITING_HORDE, 10.0f, false) + getDefendersCount(AV_STONEHEARTH_ATTACKING_HORDE, 10.0f, false);
// prepare to attack Captain
if (attackCount < 10 && !pBalinda->IsInCombat())
{
// get in position to attack Captain
pos.Set(AV_STONEHEARTH_WAITING_HORDE.GetPositionX(), AV_STONEHEARTH_WAITING_HORDE.GetPositionY(),
AV_STONEHEARTH_WAITING_HORDE.GetPositionZ(), bg->GetMapId());
pos.Set(AV_STONEHEARTH_WAITING_HORDE.GetPositionX(),
AV_STONEHEARTH_WAITING_HORDE.GetPositionY(),
AV_STONEHEARTH_WAITING_HORDE.GetPositionZ(),
bg->GetMapId());
std::ostringstream out;
out << "Taking position at Stonehearth!";
@@ -2987,6 +3002,12 @@ bool BGTactics::selectObjective(bool reset)
}
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;
out << "Attacking Balinda!";
//bot->Say(out.str(), LANG_UNIVERSAL);
@@ -3007,9 +3028,9 @@ bool BGTactics::selectObjective(bool reset)
{
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))
{
BgObjective = pGO;
@@ -3022,7 +3043,8 @@ bool BGTactics::selectObjective(bool reset)
}
// 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)
{
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)
{
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).OwnerId == TEAM_OTHER))
alterValleyBG->GetAVNodeInfo(objective.first).TotalOwnerId == TEAM_ALLIANCE)//need to check TotalOwnerId for attack objectives
{
if (GameObject* pGO = bg->GetBGObject(objective))
if (GameObject* pGO = bg->GetBGObject(objective.second))
{
BgObjective = pGO;
//std::ostringstream out;
@@ -3073,11 +3094,13 @@ bool BGTactics::selectObjective(bool reset)
{
bool endBoss = false;
// End boss
if (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_ICEBLOOD_TOWER).OwnerId != TEAM_HORDE && alterValleyBG->GetAVNodeInfo(BG_AV_NODES_TOWER_POINT).OwnerId != 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_FROSTWOLF_HUT).OwnerId != TEAM_HORDE)
if (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_ICEBLOOD_TOWER).TotalOwnerId != TEAM_HORDE &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_TOWER_POINT).TotalOwnerId != 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;
endBoss = true;
@@ -3091,9 +3114,9 @@ bool BGTactics::selectObjective(bool reset)
bool supporter = role < 3;
// Only go to Snowfall Graveyard if already close to it.
if (!BgObjective && supporter && (alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).TotalOwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).TotalOwnerId == TEAM_OTHER))
if (!BgObjective && supporter &&
(alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_SNOWFALL_GRAVE).OwnerId == TEAM_OTHER))
{
if (GameObject* pGO = bg->GetBGObject(BG_AV_NODES_SNOWFALL_GRAVE))
if (bot->IsWithinDist(pGO, 200.f))
@@ -3110,9 +3133,9 @@ bool BGTactics::selectObjective(bool reset)
{
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;
//std::ostringstream out; out << "Defending Node #" << objective.first;
@@ -3123,8 +3146,8 @@ bool BGTactics::selectObjective(bool reset)
}
// Mine capture (need paths & script fix)
if (!BgObjective && supporter && !endBoss && (alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_HORDE ||
alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_OTHER) &&
if (!BgObjective && supporter && !endBoss &&
(alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_HORDE || alterValleyBG->GetMineOwner(AV_SOUTH_MINE) == TEAM_OTHER) &&
alterValleyBG->GetAVNodeInfo(BG_AV_NODES_FROSTWOLF_GRAVE).TotalOwnerId != TEAM_HORDE)
{
if (Creature* mBossNeutral = bg->GetBGCreature(AV_CPLACE_MINE_S_3))
@@ -3158,17 +3181,18 @@ bool BGTactics::selectObjective(bool reset)
if (alterValleyBG->IsCaptainAlive(1))
{
if (Creature* pGalvangar = bg->GetBGCreature(AV_NPC_H_CAPTAIN))
if (Creature* pGalvangar = bg->GetBGCreature(AV_CPLACE_TRIGGER18))
{
uint32 attackCount = 0;
attackCount += getDefendersCount(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE, 10.0f, false);
uint32 attackCount = getDefendersCount(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE, 10.0f, false) + getDefendersCount(AV_ICEBLOOD_GARRISON_ATTACKING_ALLIANCE, 10.0f, false);
// prepare to attack Captain
if (attackCount < 10 && !pGalvangar->IsInCombat())
{
// get in position to attack Captain
pos.Set(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionX(), AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionY(),
AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionZ(), bg->GetMapId());
pos.Set(AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionX(),
AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionY(),
AV_ICEBLOOD_GARRISON_WAITING_ALLIANCE.GetPositionZ(),
bg->GetMapId());
//std::ostringstream out;
//out << "Taking position at Iceblood Outpost!";
@@ -3176,6 +3200,12 @@ bool BGTactics::selectObjective(bool reset)
}
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;
// out << "Attacking Galvangar!";
//bot->Say(out.str(), LANG_UNIVERSAL);
@@ -3187,11 +3217,9 @@ bool BGTactics::selectObjective(bool reset)
for (const auto& objective : AV_AllianceAttackObjectives)
{
if (alterValleyBG->GetAVNodeInfo(objective).OwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(objective).TotalOwnerId == TEAM_HORDE ||
alterValleyBG->GetAVNodeInfo(objective).TotalOwnerId == TEAM_OTHER)
if (alterValleyBG->GetAVNodeInfo(objective.first).TotalOwnerId == TEAM_HORDE)//need to check TotalOwnerId for attack objectives
{
if (GameObject* pGO = bg->GetBGObject(objective))
if (GameObject* pGO = bg->GetBGObject(objective.second))
{
float const distance = sqrt(bot->GetDistance(pGO));
if (attackObjectiveDistance > distance)
@@ -4087,7 +4115,8 @@ bool BGTactics::selectObjective(bool reset)
}
break;
}
default:
break;
}
return false;
@@ -4124,7 +4153,7 @@ bool BGTactics::moveToObjective()
}
// 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();
@@ -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);
//bot->Say(out.str(), LANG_UNIVERSAL);
// more precise position for wsg
if (bgType == BATTLEGROUND_WS)
// more precise position for wsg and AV (flags in AV towers require precision)
if (bgType == BATTLEGROUND_WS || bgType == BATTLEGROUND_AV)
return MoveTo(bot->GetMapId(), pos.x, pos.y, pos.z);
else
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))*/)
return wsgPaths();
BattleBotPath* pClosestPath = nullptr;
uint32 closestPoint = 0;
float closestDistanceToTarget = FLT_MAX;
bool reverse = false;
float maxDistanceToPoint = 50.0f;
if (bgType == BATTLEGROUND_IC)
maxDistanceToPoint = 80.0f;
float chosenPathScore = FLT_MAX;//lower score is better
BattleBotPath* chosenPath = nullptr;
uint32 chosenPathPoint = 0;
bool chosenPathReverse = false;
for (auto const& pPath : vPaths)
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)
botDistanceLimit = 80.0f;
else if (bgType == BATTLEGROUND_AB)
{
botDistanceScoreSubtract = 2.0f;
botDistanceScoreMultiply = 4.0f;
}
for (auto const& path : vPaths)
{
// 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;
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;
BattleBotWaypoint& lastPoint = ((*pPath)[pPath->size() - 1]);
float const distanceFromPathEndToTarget = sqrt(Position(pos.x, pos.y, pos.z, 0.f).GetExactDist(lastPoint.x, lastPoint.y, lastPoint.z));
if (closestDistanceToTarget > distanceFromPathEndToTarget)
BattleBotWaypoint& startPoint = ((*path)[0]);
float const startPointDistToDestination = sqrt(Position(pos.x, pos.y, pos.z, 0.f).GetExactDist(startPoint.x, startPoint.y, startPoint.z));
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));
bool reverse = startPointDistToDestination < endPointDistToDestination;
// dont travel reverse if it's a reverse paths
if (reverse && std::find(vPaths_NoReverseAllowed.begin(), vPaths_NoReverseAllowed.end(), path) != vPaths_NoReverseAllowed.end())
continue;
int closestPointIndex = -1;
float closestPointDistToBot = FLT_MAX;
for (uint32 i = 0; i < path->size(); i++)
{
float closestDistanceFromMeToPoint = FLT_MAX;
for (uint32 i = 0; i < pPath->size(); i++)
BattleBotWaypoint& waypoint = ((*path)[i]);
float const distToBot = sqrt(bot->GetDistance(waypoint.x, waypoint.y, waypoint.z));
if (closestPointDistToBot > distToBot)
{
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;
}
closestPointDistToBot = distToBot;
closestPointIndex = i;
}
}
// skip no reverse paths
if (std::find(vPaths_NoReverseAllowed.begin(), vPaths_NoReverseAllowed.end(), pPath) != vPaths_NoReverseAllowed.end())
// 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;
// skip mine paths of own faction
if (bot->GetTeamId() == TEAM_ALLIANCE && std::find(vPaths_AllyMine.begin(), vPaths_AllyMine.end(), pPath) != vPaths_AllyMine.end())
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)
// 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
// paths (one after another) anyway
float distToDestination = reverse ? startPointDistToDestination : endPointDistToDestination;
float pathScore = (closestPointDistToBot < botDistanceScoreSubtract ? 0.0f : ((closestPointDistToBot - botDistanceScoreSubtract) * botDistanceScoreMultiply)) + distToDestination;
{
BattleBotWaypoint& firstPoint = ((*pPath)[0]);
float const distanceFromPathBeginToTarget = sqrt(Position(pos.x, pos.y, pos.z, 0).GetExactDist(firstPoint.x, firstPoint.y, firstPoint.z));
if (closestDistanceToTarget > distanceFromPathBeginToTarget)
{
float closestDistanceFromMeToPoint = FLT_MAX;
//LOG_INFO("playerbots", "bot={}\t{:6.1f}\t{:4.1f}\t{:4.1f}\t{}", bot->GetName(), pathScore, closestPointDistToBot, distToDestination, vPaths_AB_name[pathNum]);
for (uint32 i = 0; i < pPath->size(); i++)
{
BattleBotWaypoint& waypoint = ((*pPath)[i]);
float const distanceFromMeToPoint = sqrt(bot->GetDistance(waypoint.x, waypoint.y, waypoint.z));
if (distanceFromMeToPoint < maxDistanceToPoint && closestDistanceFromMeToPoint > distanceFromMeToPoint)
{
reverse = true;
pClosestPath = pPath;
closestPoint = i;
closestDistanceToTarget = distanceFromPathBeginToTarget;
closestDistanceFromMeToPoint = distanceFromMeToPoint;
}
}
}
if (chosenPathScore > pathScore) {
chosenPathScore = pathScore;
chosenPath = path;
chosenPathPoint = closestPointIndex;
chosenPathReverse = reverse;
}
}
if (!pClosestPath)
if (!chosenPath)
return false;
// Prevent picking last point of path.
// It means we are already there.
if (reverse)
{
if (closestPoint == 0)
return false;
}
else
{
if (closestPoint == pClosestPath->size() - 1)
return false;
}
//LOG_INFO("playerbots", "bot={} {}", bot->GetName(), vPaths_AB_name[chosenPathNum]);
BattleBotPath* currentPath = pClosestPath;
uint32 currentPoint = reverse ? closestPoint + 1 : closestPoint - 1;
return moveToObjectiveWp(currentPath, currentPoint, reverse);
return moveToObjectiveWp(chosenPath, chosenPathPoint, chosenPathReverse);
return false;
}
@@ -4490,7 +4504,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
if (f == vFlagIds.end())
continue;
if (!go->isSpawned() || !go->GetGoState() == GO_STATE_READY)
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
continue;
if (!bot->CanUseBattlegroundObject(go) && bgType != BATTLEGROUND_WS)
@@ -4631,6 +4645,8 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
}
break;
}
default:
break;
}
}
@@ -4961,9 +4977,9 @@ bool ArenaTactics::moveToCenter(Battleground* bg)
case BATTLEGROUND_DS:
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
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());
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());
}
break;

View File

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

View File

@@ -11,7 +11,7 @@
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)
return pos;
@@ -64,7 +64,7 @@ bool CastCustomSpellAction::Execute(Event event)
Item* itemTarget = nullptr;
uint32 pos = FindLastSeparator(text, " ");
size_t pos = FindLastSeparator(text, " ");
uint32 castCount = 1;
if (pos != std::string::npos)
{

View File

@@ -44,9 +44,11 @@ bool ChangeTalentsAction::Execute(Event event)
} else if (param.find("spec ") != std::string::npos) {
param = param.substr(5);
out << SpecPick(param);
botAI->ResetStrategies();
} else if (param.find("apply ") != std::string::npos) {
param = param.substr(6);
out << SpecApply(param);
botAI->ResetStrategies();
} else {
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 });
//Grind for money
if (!foundTarget && AI_VALUE(bool, "should get money"))
if (!foundTarget && AI_VALUE(bool, "should get money")) {
if (urand(1, 100) > 66)
{
foundTarget = SetQuestTarget(newTarget, true); //Turn in quests for money.
if (!foundTarget)
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
else
} else {
foundTarget = SetNewQuestTarget(newTarget); //Find a low level quest to do
}
}
//Continue
if (!foundTarget && urand(1, 100) > 10) //90% chance

View File

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

View File

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

View File

@@ -4,6 +4,7 @@
#include "MovementActions.h"
#include "GameObject.h"
#include "Geometry.h"
#include "Map.h"
#include "MotionMaster.h"
#include "MoveSplineInitArgs.h"
@@ -12,6 +13,7 @@
#include "ObjectGuid.h"
#include "PathGenerator.h"
#include "PlayerbotAIConfig.h"
#include "Position.h"
#include "Random.h"
#include "SharedDefines.h"
#include "SpellAuraEffects.h"
@@ -25,10 +27,15 @@
#include "LootObjectStack.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "Timer.h"
#include "Transport.h"
#include "Unit.h"
#include "Vehicle.h"
#include "WaypointMovementGenerator.h"
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <string>
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) &&
!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);
if (distance > sPlayerbotAIConfig->contactDistance)
{
@@ -1495,6 +1504,9 @@ bool FleeWithPetAction::Execute(Event event)
bool AvoidAoeAction::isUseful()
{
if (getMSTime() - moveInterval < lastMoveTimer) {
return false;
}
GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage");
GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
return AI_VALUE(Aura*, "area debuff") || !traps.empty() || !triggers.empty();
@@ -1523,6 +1535,9 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
if (!aura || aura->IsRemoved() || aura->IsExpired()) {
return false;
}
if (!aura->GetOwner() || !aura->GetOwner()->IsInWorld()) {
return false;
}
// Crash fix: maybe change owner due to check interval
if (aura->GetType() != DYNOBJ_AURA_TYPE) {
return false;
@@ -1540,8 +1555,15 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
return false;
}
std::ostringstream name;
name << spellInfo->SpellName[0]; // << "] (aura)";
if (FleePosition(dynOwner->GetPosition(), radius, name.str())) {
name << spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; // << "] (aura)";
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 false;
@@ -1591,8 +1613,15 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
continue;
}
std::ostringstream name;
name << spellInfo->SpellName[0]; // << "] (object)";
if (FleePosition(go->GetPosition(), radius, name.str())) {
name << spellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; // << "] (object)";
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;
}
@@ -1633,9 +1662,15 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
break;
}
std::ostringstream name;
name << triggerSpellInfo->SpellName[0]; //<< "] (unit)";
if (FleePosition(unit->GetPosition(), radius, name.str())) {
return true;
name << triggerSpellInfo->SpellName[sWorld->GetDefaultDbcLocale()]; //<< "] (unit)";
if (FleePosition(unit->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);
}
}
}
}
@@ -1645,7 +1680,7 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
return false;
}
Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius)
Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
std::vector<CheckAngle> possibleAngles;
@@ -1670,13 +1705,18 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius)
Position bestPos;
for (CheckAngle &checkAngle : possibleAngles) {
float angle = checkAngle.angle;
auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
if (!CheckLastFlee(angle, infoList)) {
continue;
}
bool strict = checkAngle.strict;
float fleeDis = sPlayerbotAIConfig->fleeDistance;
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis,
bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()};
if (strict && currentTarget
&& fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->tooCloseDistance) {
&& fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->tooCloseDistance
&& bot->IsWithinMeleeRange(currentTarget)) {
continue;
}
if (pos.GetExactDist(fleePos) > farestDis) {
@@ -1690,7 +1730,7 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius)
return Position();
}
Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius)
Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
std::vector<CheckAngle> possibleAngles;
@@ -1713,8 +1753,12 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius)
Position bestPos;
for (CheckAngle &checkAngle : possibleAngles) {
float angle = checkAngle.angle;
auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
if (!CheckLastFlee(angle, infoList)) {
continue;
}
bool strict = checkAngle.strict;
float fleeDis = sPlayerbotAIConfig->fleeDistance;
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis,
bot->GetPositionY() + sin(angle) * fleeDis,
bot->GetPositionZ()};
@@ -1737,28 +1781,197 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius)
return Position();
}
bool AvoidAoeAction::FleePosition(Position pos, float radius, std::string name)
bool MovementAction::FleePosition(Position pos, float radius)
{
Position bestPos;
if (botAI->IsMelee(bot)) {
bestPos = BestPositionForMelee(pos, radius);
bestPos = BestPositionForMeleeToFlee(pos, radius);
} else {
bestPos = BestPositionForRanged(pos, radius);
bestPos = BestPositionForRangedToFlee(pos, radius);
}
if (bestPos != Position()) {
if (MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, true)) {
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) {
lastTellTimer = time(NULL);
std::ostringstream out;
out << "I'm avoiding " << name << "...";
bot->Say(out.str(), LANG_UNIVERSAL);
auto& infoList = AI_VALUE_REF(std::list<FleeInfo>, "recently flee info");
uint32 curTS = getMSTime();
while (!infoList.empty()) {
if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS) {
infoList.pop_front();
} else {
break;
}
}
infoList.push_back({pos, radius, bot->GetAngle(&bestPos), curTS});
return true;
}
}
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)
{
return Flee(AI_VALUE(Unit*, "master target"));

View File

@@ -13,6 +13,7 @@ class Player;
class PlayerbotAI;
class Unit;
class WorldObject;
class Position;
class MovementAction : public Action
{
@@ -41,6 +42,15 @@ class MovementAction : public Action
bool MoveAway(Unit* target);
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);
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:
// 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);
@@ -69,7 +79,8 @@ class FleeWithPetAction : public MovementAction
class AvoidAoeAction : public MovementAction
{
public:
AvoidAoeAction(PlayerbotAI* botAI) : MovementAction(botAI, "avoid aoe") { }
AvoidAoeAction(PlayerbotAI* botAI, int moveInterval = 1000) : MovementAction(botAI, "avoid aoe"),
moveInterval(moveInterval) { }
bool isUseful() override;
bool Execute(Event event) override;
@@ -78,14 +89,36 @@ class AvoidAoeAction : public MovementAction
bool AvoidAuraWithDynamicObj();
bool AvoidGameObjectWithDamage();
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;
struct CheckAngle {
float angle;
bool strict;
};
int lastMoveTimer = 0;
int moveInterval;
};
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,8 @@ bool SummonAction::Execute(Event event)
}
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);
}
@@ -175,11 +176,30 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
if (summoner->IsWithinLOS(x, y, z))
{
bool allowed = sPlayerbotAIConfig->botReviveWhenSummon == 2 || (sPlayerbotAIConfig->botReviveWhenSummon == 1 && !master->IsInCombat() && master->IsAlive());
if (allowed && bot->isDead())
if (sPlayerbotAIConfig->botRepairWhenSummon) // .conf option to repair bot gear when summoned 0 = off, 1 = on
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->DurabilityRepairAll(false, 1.0f, false);
botAI->TellMasterNoFacing("I live, again!");
}

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ class DeathKnightTriggerFactoryInternal : public NamedObjectContext<Trigger>
DeathKnightTriggerFactoryInternal()
{
creators["bone shield"] = &DeathKnightTriggerFactoryInternal::bone_shield;
creators["pestilence"] = &DeathKnightTriggerFactoryInternal::pestilence;
creators["pestilence glyph"] = &DeathKnightTriggerFactoryInternal::pestilence_glyph;
creators["blood strike"] = &DeathKnightTriggerFactoryInternal::blood_strike;
creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike;
creators["plague strike on attacker"] = &DeathKnightTriggerFactoryInternal::plague_strike_on_attacker;
@@ -94,7 +94,7 @@ class DeathKnightTriggerFactoryInternal : public NamedObjectContext<Trigger>
private:
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* plague_strike(PlayerbotAI* botAI) { return new PlagueStrikeDebuffTrigger(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);
}
bool PestilenceTrigger::IsActive() {
bool PestilenceGlyphTrigger::IsActive() {
if (!SpellTrigger::IsActive()) {
return false;
}

View File

@@ -71,10 +71,10 @@ class DeathCoilTrigger : public SpellCanBeCastTrigger
DeathCoilTrigger(PlayerbotAI* botAI) : SpellCanBeCastTrigger(botAI, "death coil") { }
};
class PestilenceTrigger : public DebuffTrigger
class PestilenceGlyphTrigger : public SpellTrigger
{
public:
PestilenceTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "pestilence") { }
PestilenceGlyphTrigger(PlayerbotAI* botAI) : SpellTrigger(botAI, "pestilence") { }
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,
// new NextAction("pestilence", ACTION_NORMAL + 4),
// 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)
{
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),
/*C*/ nullptr);
}

View File

@@ -20,6 +20,7 @@ class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
creators["insect swarm"] = &insect_swarm;
creators["moonfire"] = &moonfire;
creators["starfire"] = &starfire;
creators["moonkin form"] = &moonkin_form;
}
private:
@@ -94,6 +95,15 @@ class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
/*A*/ 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)

View File

@@ -51,7 +51,7 @@ class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNod
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode ("cat form",
/*P*/ nullptr,
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ 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("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("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL)));
}
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)

View File

@@ -8,7 +8,7 @@
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("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)));
@@ -24,44 +24,44 @@ AvoidAoeStrategy::AvoidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI)
}
class AvoidAoeStrategyMultiplier : public Multiplier
{
public:
AvoidAoeStrategyMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "run away on area debuff") {}
// class AvoidAoeStrategyMultiplier : public Multiplier
// {
// public:
// AvoidAoeStrategyMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "run away on area debuff") {}
public:
virtual float GetValue(Action* action);
// public:
// virtual float GetValue(Action* action);
private:
};
// private:
// };
float AvoidAoeStrategyMultiplier::GetValue(Action* action)
{
if (!action)
return 1.0f;
// float AvoidAoeStrategyMultiplier::GetValue(Action* action)
// {
// if (!action)
// return 1.0f;
std::string name = action->getName();
if (name == "follow" || name == "co" || name == "nc" || name == "drop target")
return 1.0f;
// std::string name = action->getName();
// if (name == "follow" || name == "co" || name == "nc" || name == "drop target")
// return 1.0f;
uint32 spellId = AI_VALUE2(uint32, "spell id", name);
const SpellInfo* const pSpellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!pSpellInfo) return 1.0f;
// uint32 spellId = AI_VALUE2(uint32, "spell id", name);
// const SpellInfo* const pSpellInfo = sSpellMgr->GetSpellInfo(spellId);
// if (!pSpellInfo) return 1.0f;
if (spellId && pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
return 1.0f;
else if (spellId && pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
return 1.0f;
// if (spellId && pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
// return 1.0f;
// else if (spellId && pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
// 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)
{
return 0.0f;
}
// if (AI_VALUE2(bool, "has area debuff", "self target") && spellId && castTime > 0)
// {
// return 0.0f;
// }
return 1.0f;
}
// return 1.0f;
// }
NextAction** AvoidAoeStrategy::getDefaultActions()
{
@@ -81,4 +81,11 @@ void AvoidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// 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;
};
class CombatFormationStrategy : public Strategy
{
public:
CombatFormationStrategy(PlayerbotAI* ai): Strategy(ai) {}
const std::string getName() override { return "combat formation"; }
NextAction** getDefaultActions() override;
};
#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("xpgain", NextAction::array(0, new NextAction("xp gain", relevance), nullptr)));
triggers.push_back(new TriggerNode("levelup", NextAction::array(0,
new NextAction("auto talents", relevance),
new NextAction("auto learn spell", relevance),
new NextAction("auto teleport for level", relevance),
new NextAction("auto teleport for level", relevance + 3),
new NextAction("auto talents", relevance + 2),
new NextAction("auto learn spell", relevance + 1),
new NextAction("auto upgrade equip", 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)));

View File

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

View File

@@ -153,7 +153,7 @@ bool CastMeleeConsecrationAction::isUseful()
{
Unit* target = GetTarget();
// 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()

View File

@@ -94,6 +94,9 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"medium group heal occasion",
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(
"not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr)));

View File

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

View File

@@ -49,9 +49,11 @@ void CasterShamanStrategy::InitTriggers(std::vector<TriggerNode*>& 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("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("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(
"no fire totem",

View File

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

View File

@@ -42,7 +42,8 @@ void HealShamanStrategy::InitTriggers(std::vector<TriggerNode*>& 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("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(
"group heal occasion",
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);
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("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr)));
triggers.push_back(new TriggerNode(

View File

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

View File

@@ -79,7 +79,9 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
creators["searing totem"] = &ShamanATriggerFactoryInternal::searing_totem;
creators["wind shear"] = &ShamanATriggerFactoryInternal::wind_shear;
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["lightning shield"] = &ShamanATriggerFactoryInternal::lightning_shield;
creators["water breathing"] = &ShamanATriggerFactoryInternal::water_breathing;
@@ -96,6 +98,7 @@ class ShamanATriggerFactoryInternal : public NamedObjectContext<Trigger>
creators["frost shock snare"] = &ShamanATriggerFactoryInternal::frost_shock_snare;
creators["heroism"] = &ShamanATriggerFactoryInternal::heroism;
creators["bloodlust"] = &ShamanATriggerFactoryInternal::bloodlust;
creators["elemental mastery"] = &ShamanATriggerFactoryInternal::elemental_mastery;
creators["wind shear on enemy healer"] = &ShamanATriggerFactoryInternal::wind_shear_on_enemy_healer;
creators["cure poison"] = &ShamanATriggerFactoryInternal::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* heroism(PlayerbotAI* botAI) { return new HeroismTrigger(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_curse(PlayerbotAI* botAI) { return new PartyMemberCleanseSpiritCurseTrigger(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* wind_shear(PlayerbotAI* botAI) { return new WindShearInterruptSpellTrigger(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* lightning_shield(PlayerbotAI* botAI) { return new LightningShieldTrigger(botAI); }
static Trigger* shock(PlayerbotAI* botAI) { return new ShockTrigger(botAI); }
@@ -206,6 +212,7 @@ class ShamanAiObjectContextInternal : public NamedObjectContext<Action>
creators["thunderstorm"] = &ShamanAiObjectContextInternal::thunderstorm;
creators["heroism"] = &ShamanAiObjectContextInternal::heroism;
creators["bloodlust"] = &ShamanAiObjectContextInternal::bloodlust;
creators["elemental mastery"] = &ShamanAiObjectContextInternal::elemental_mastery;
// creators["cure disease"] = &ShamanAiObjectContextInternal::cure_disease;
// creators["cure disease on party"] = &ShamanAiObjectContextInternal::cure_disease_on_party;
// creators["cure poison"] = &ShamanAiObjectContextInternal::cure_poison;
@@ -222,6 +229,7 @@ class ShamanAiObjectContextInternal : public NamedObjectContext<Action>
private:
static Action* heroism(PlayerbotAI* botAI) { return new CastHeroismAction(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* lightning_bolt(PlayerbotAI* botAI) { return new CastLightningBoltAction(botAI); }
static Action* chain_lightning(PlayerbotAI* botAI) { return new CastChainLightningAction(botAI); }

View File

@@ -5,6 +5,7 @@
#include "ShamanTriggers.h"
#include "Playerbots.h"
/*
std::vector<std::string> ShamanWeaponTrigger::spells;
bool ShamanWeaponTrigger::IsActive()
@@ -30,6 +31,21 @@ bool ShamanWeaponTrigger::IsActive()
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()
{

View File

@@ -11,6 +11,7 @@
class PlayerbotAI;
/*
class ShamanWeaponTrigger : public BuffTrigger
{
public:
@@ -21,6 +22,21 @@ class ShamanWeaponTrigger : public BuffTrigger
private:
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
{
@@ -201,6 +217,12 @@ class BloodlustTrigger : public BoostTrigger
BloodlustTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "bloodlust") { }
};
class ElementalMasteryTrigger : public BoostTrigger
{
public:
ElementalMasteryTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "elemental mastery") { }
};
class MaelstromWeaponTrigger : public HasAuraStackTrigger
{
public:

View File

@@ -119,6 +119,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
creators["naxx"] = &ChatTriggerContext::naxx;
creators["bwl"] = &ChatTriggerContext::bwl;
creators["dps"] = &ChatTriggerContext::dps;
creators["disperse"] = &ChatTriggerContext::disperse;
}
private:
@@ -218,6 +219,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
static Trigger* naxx(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "naxx"); }
static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
};
#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;
}
bool EnoughManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") > 65;
}
bool RageAvailable::IsActive()
{
return AI_VALUE2(uint8, "rage", "self target") >= amount;

View File

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

View File

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

View File

@@ -47,8 +47,21 @@ GuidVector AttackersValue::Calculate()
if (bot->duel && bot->duel->Opponent)
result.push_back(bot->duel->Opponent->GetGUID());
return result;
// 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;
}
void AttackersValue::AddAttackersOf(Group* group, std::unordered_set<Unit*>& targets)
@@ -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) &&
// !(attacker->HasUnitState(UNIT_STATE_STUNNED) && botAI->HasAura("shackle undead", attacker)) && !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/ attacker->isFeared()) && !rti) &&
/*!sServerFacade->IsInRoots(attacker) &&*/
!attacker->IsFriendlyTo(bot) && bot->IsWithinDistInMap(attacker, range) &&
!attacker->IsFriendlyTo(bot) &&
!attacker->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) &&
// !(attacker->GetGUID().IsPet() && enemy) &&
!(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) &&

View File

@@ -180,6 +180,8 @@ uint32 MoneyNeededForValue::Calculate()
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)
break;
default:
break;
}
return moneyWanted;

View File

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

View File

@@ -14,7 +14,7 @@ class Unit;
class PartyMemberToDispel : public PartyMemberValue, public Qualified
{
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:
Unit* Calculate() override;

View File

@@ -74,7 +74,7 @@ bool PartyMemberToHeal::Check(Unit* player)
{
// return player && player != bot && player->GetMapId() == bot->GetMapId() && player->IsInWorld() &&
// 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->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["area debuff"] = &ValueContext::area_debuff;
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:
@@ -505,6 +509,10 @@ class ValueContext : public NamedObjectContext<UntypedValue>
static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(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* 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

View File

@@ -87,7 +87,10 @@ void DpsWarlockStrategy::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("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)));

View File

@@ -67,12 +67,18 @@ class CastCorruptionAction : public CastDebuffSpellAction
{
public:
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
{
public:
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
@@ -142,6 +148,20 @@ class CastSeedOfCorruptionAction : public CastDebuffSpellAction
{
public:
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

View File

@@ -158,6 +158,7 @@ class WarlockAiObjectContextInternal : public NamedObjectContext<Action>
creators["banish"] = &WarlockAiObjectContextInternal::banish;
creators["banish on cc"] = &WarlockAiObjectContextInternal::banish_on_cc;
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["shadowfury"] = &WarlockAiObjectContextInternal::shadowfury;
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_on_cc(PlayerbotAI* botAI) { return new CastBanishAction(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* shadowfury(PlayerbotAI* botAI) { return new CastShadowfuryAction(botAI); }
static Action* life_tap(PlayerbotAI* botAI) { return new CastLifeTapAction(botAI); }

View File

@@ -6,6 +6,7 @@
#define _PLAYERBOT_WARLOCKTRIGGERS_H
#include "GenericTriggers.h"
#include "PlayerbotAI.h"
class PlayerbotAI;
@@ -32,13 +33,24 @@ class CurseOfAgonyTrigger : public DebuffTrigger
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");
class CorruptionOnAttackerTrigger : public DebuffOnAttackerTrigger
{
public:
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

View File

@@ -32,7 +32,9 @@ ArmsWarriorStrategy::ArmsWarriorStrategy(PlayerbotAI* botAI) : GenericWarriorStr
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)
@@ -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 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("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("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("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("light aoe", NextAction::array(0,
// 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 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("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)));
}

View File

@@ -117,7 +117,7 @@ class WarriorTriggerFactoryInternal : public NamedObjectContext<Trigger>
static Trigger* SwordAndBoard(PlayerbotAI* botAI) { return new SwordAndBoardTrigger(botAI); }
static Trigger* shield_bash_on_enemy_healer(PlayerbotAI* botAI) { return new ShieldBashInterruptEnemyHealerSpellTrigger(botAI); }
static Trigger* thunderclap_and_rage(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "thunderclap", "light rage available"); }
static Trigger* thunderclap_and_rage(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "thunder clap", "light rage available"); }
static Trigger* intercept_can_cast(PlayerbotAI* botAI) { return new InterceptCanCastTrigger(botAI); }
static Trigger* intercept_and_far_enemy(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "enemy is out of melee", "intercept can cast"); }
static Trigger* intercept_and_rage(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "intercept and far enemy", "light rage available"); }