Merge branch 'master' into bwl-strats

This commit is contained in:
Fuzz
2024-09-10 12:31:43 +10:00
101 changed files with 2129 additions and 971 deletions

View File

@@ -24,6 +24,8 @@
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h"
#include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
#include "raids/aq20/RaidAq20TriggerContext.h"
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
{
@@ -40,6 +42,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidAq20ActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -48,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -94,7 +94,7 @@ private:
{
return new ActionNode("mana potion",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("drink"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}

View File

@@ -51,7 +51,7 @@ bool AcceptQuestAction::Execute(Event event)
guid = unit->GetGUID().GetRawValue();
break;
}
if (unit && text == "*" && sqrt(bot->GetDistance(unit)) <= INTERACTION_DISTANCE)
if (unit && text == "*" && bot->GetDistance(unit) <= INTERACTION_DISTANCE)
hasAccept |= QuestAction::ProcessQuests(unit);
}
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
@@ -63,7 +63,7 @@ bool AcceptQuestAction::Execute(Event event)
guid = go->GetGUID().GetRawValue();
break;
}
if (go && text == "*" && sqrt(bot->GetDistance(go)) <= INTERACTION_DISTANCE)
if (go && text == "*" && bot->GetDistance(go) <= INTERACTION_DISTANCE)
hasAccept |= QuestAction::ProcessQuests(go);
}
}

View File

@@ -172,7 +172,7 @@ public:
creators["rtsc"] = &ChatActionContext::rtsc;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["tell expected dps"] = &ChatActionContext::tell_expected_dps;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join;
creators["calc"] = &ChatActionContext::calc;
}
@@ -271,7 +271,7 @@ private:
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); }
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
};

View File

@@ -311,7 +311,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
Player* master = botAI->GetGroupMaster();
Player* gmaster = botAI->GetGroupMaster();
Player* realMaster = botAI->GetMaster();
AiObjectContext* context = botAI->GetAiObjectContext();
@@ -327,30 +327,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
return false;
}
if (!master || bot == master)
if (!gmaster || bot == gmaster)
return true;
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return true;
if (bot->GetDistance(master) > sPlayerbotAIConfig->rpgDistance * 2)
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
return false;
Formation* formation = AI_VALUE(Formation*, "formation");
float distance = master->GetDistance2d(pos.getX(), pos.getY());
float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
{
Player* player = master;
if (!master->isMoving() ||
Player* player = gmaster;
if (gmaster && !gmaster->isMoving() ||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
return true;
}
if ((inDungeon || !master->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == master && distance > 5.0f)
if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
return false;
if (!master->isMoving() && distance < 25.0f)
if (!gmaster->isMoving() && distance < 25.0f)
return true;
if (distance < formation->GetMaxDistance())

View File

@@ -162,8 +162,14 @@ bool CastMeleeDebuffSpellAction::isUseful()
bool CastAuraSpellAction::isUseful()
{
return GetTarget() && (GetTarget() != nullptr) && CastSpellAction::isUseful() &&
!botAI->HasAura(spell, GetTarget(), false, isOwner);
if (!GetTarget() || !CastSpellAction::isUseful())
return false;
Aura* aura = botAI->GetAura(spell, GetTarget(), isOwner, checkDuration);
if (!aura)
return true;
if (beforeDuration && aura->GetDuration() < beforeDuration)
return true;
return false;
}
CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell)
@@ -251,8 +257,8 @@ Value<Unit*>* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue()
return context->GetValue<Unit*>("melee attacker without aura", spell);
}
CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner)
: CastAuraSpellAction(botAI, spell, checkIsOwner)
CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
: CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration)
{
range = botAI->GetRange("spell");
}
@@ -364,5 +370,5 @@ bool CastDebuffSpellAction::isUseful()
return false;
}
return CastAuraSpellAction::isUseful() &&
(target->GetHealth() / AI_VALUE(float, "expected group dps")) >= needLifeTime;
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
}

View File

@@ -37,16 +37,20 @@ protected:
class CastAuraSpellAction : public CastSpellAction
{
public:
CastAuraSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false)
CastAuraSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
: CastSpellAction(botAI, spell)
{
this->isOwner = isOwner;
this->beforeDuration = beforeDuration;
this->checkDuration = checkDuration;
}
bool isUseful() override;
protected:
bool isOwner;
bool checkDuration;
uint32 beforeDuration;
};
class CastMeleeSpellAction : public CastSpellAction
@@ -107,7 +111,7 @@ public:
class CastBuffSpellAction : public CastAuraSpellAction
{
public:
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false);
CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner = false, uint32 beforeDuration = 0);
std::string const GetTargetName() override { return "self target"; }
};

View File

@@ -11,13 +11,13 @@
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
std::set<uint32> ListSpellsAction::vendorItems;
bool CompareSpells(std::pair<uint32, std::string>& s1, std::pair<uint32, std::string>& s2)
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
{
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
if (!si1 || !si2)
{
LOG_ERROR("playerbots", "SpellInfo missing.");
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
return false;
}
uint32 p1 = si1->SchoolMask * 20000;
@@ -54,7 +54,7 @@ bool CompareSpells(std::pair<uint32, std::string>& s1, std::pair<uint32, std::st
if (p1 == p2)
{
return strcmp(si1->SpellName[0], si1->SpellName[1]) > 0;
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
}
return p1 > p2;
@@ -273,7 +273,11 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (out.str().empty())
continue;
if (itr->first == 0)
{
LOG_ERROR("playerbots", "?! {}", itr->first);
}
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
alreadySeenList += spellInfo->SpellName[0];
alreadySeenList += ",";

View File

@@ -762,7 +762,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// return true;
}
bool MovementAction::MoveTo(Unit* target, float distance, MovementPriority priority)
bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriority priority)
{
if (!IsMovingAllowed(target))
return false;
@@ -814,39 +814,25 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
float combatDistance = bot->GetCombatReach() + target->GetCombatReach();
distance += combatDistance;
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD)) // target is moving forward, predict the position
{
float needToGo = bot->GetExactDist(target) - distance;
float timeToGo = MoveDelay(abs(needToGo)) + sPlayerbotAIConfig->reactDelay / 1000.0f;
float targetMoveDist = timeToGo * target->GetSpeed(MOVE_RUN);
targetMoveDist = std::min(5.0f, targetMoveDist);
tx += targetMoveDist * cos(target->GetOrientation());
ty += targetMoveDist * sin(target->GetOrientation());
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), tx, ty, tz))
{
// disable prediction if position is invalid
tx = target->GetPositionX();
ty = target->GetPositionY();
tz = target->GetPositionZ();
}
// Prediction may cause this, which makes ShortenPathUntilDist fail
if (bot->GetExactDist(tx, ty, tz) <= distance)
{
tx = target->GetPositionX();
ty = target->GetPositionY();
tz = target->GetPositionZ();
}
}
if (bot->GetExactDist(tx, ty, tz) <= distance)
return false;
PathGenerator path(bot);
path.CalculatePath(tx, ty, tz, false);
PathType type = path.GetPathType();
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
if (!(type & typeOk))
return false;
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), distance);
float shortenTo = distance;
// Avoid walking too far when moving towards each other
if (bot->GetDistance(tx, ty, tz) >= 10.0f)
shortenTo = std::max(distance, bot->GetDistance(tx, ty, tz) / 2);
if (bot->GetExactDist(tx, ty, tz) <= shortenTo)
return false;
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo);
G3D::Vector3 endPos = path.GetPath().back();
return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
@@ -874,7 +860,7 @@ float MovementAction::GetFollowAngle()
return 0;
}
bool MovementAction::IsMovingAllowed(Unit* target)
bool MovementAction::IsMovingAllowed(WorldObject* target)
{
if (!target)
return false;
@@ -1272,6 +1258,8 @@ bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle)
// bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveChase((Unit*)obj, distance);
// TODO shouldnt this use "last movement" value?
WaitForReach(bot->GetExactDist2d(obj) - distance);
return true;
}
@@ -1295,6 +1283,7 @@ float MovementAction::MoveDelay(float distance)
return delay;
}
// TODO should this be removed? (or modified to use "last movement" value?)
void MovementAction::WaitForReach(float distance)
{
float delay = 1000.0f * MoveDelay(distance);
@@ -1313,6 +1302,15 @@ void MovementAction::WaitForReach(float distance)
botAI->SetNextCheckDelay((uint32)delay);
}
// similiar to botAI->SetNextCheckDelay() but only stops movement
void MovementAction::SetNextMovementDelay(float delayMillis)
{
AI_VALUE(LastMovement&, "last movement")
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
delayMillis,
MovementPriority::MOVEMENT_FORCED);
}
bool MovementAction::Flee(Unit* target)
{
Player* master = GetMaster();
@@ -1821,12 +1819,17 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
{
return false;
}
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
return false;
DynamicObject* dynOwner = aura->GetDynobjOwner();
if (!dynOwner || !dynOwner->IsInWorld())
{
return false;
}
float radius = dynOwner->GetRadius();
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
return false;
if (bot->GetDistance(dynOwner) > radius)
{
return false;
@@ -1840,7 +1843,7 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Aura]";
bot->Say(out.str(), LANG_UNIVERSAL);
}
return true;
@@ -1871,17 +1874,28 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
{
continue;
}
// 0 trap with no despawn after cast. 1 trap despawns after cast. 2 bomb casts on spawn.
if (goInfo->trap.type != 0)
continue;
uint32 spellId = goInfo->trap.spellId;
if (!spellId)
{
continue;
}
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
continue;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo || spellInfo->IsPositive())
{
continue;
}
float radius = (float)goInfo->trap.diameter / 2;
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
continue;
// for (int i = 0; i < MAX_SPELL_EFFECTS; i++) {
// if (spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA) {
// if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE) {
@@ -1905,7 +1919,7 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Trap]";
bot->Say(out.str(), LANG_UNIVERSAL);
}
return true;
@@ -1948,6 +1962,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
if (!triggerSpellInfo)
continue;
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
return false;
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
{
if (triggerSpellInfo->Effects[j].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
@@ -1957,6 +1973,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
{
break;
}
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
continue;
std::ostringstream name;
name << triggerSpellInfo->SpellName[LOCALE_enUS]; //<< "] (unit)";
if (FleePosition(unit->GetPosition(), radius))
@@ -1966,7 +1984,7 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
lastTellTimer = time(NULL);
lastMoveTimer = getMSTime();
std::ostringstream out;
out << "I'm avoiding " << name.str() << "...";
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")" << " Radius " << radius << " - [Unit Trigger]";
bot->Say(out.str(), LANG_UNIVERSAL);
}
}

View File

@@ -30,7 +30,7 @@ protected:
bool MoveToLOS(WorldObject* target, bool ranged = false);
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveTo(Unit* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
float GetFollowAngle();
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
@@ -39,7 +39,8 @@ protected:
bool ReachCombatTo(Unit* target, float distance = 0.0f);
float MoveDelay(float distance);
void WaitForReach(float distance);
bool IsMovingAllowed(Unit* target);
void SetNextMovementDelay(float delayMillis);
bool IsMovingAllowed(WorldObject* target);
bool IsMovingAllowed(uint32 mapId, float x, float y, float z);
bool IsDuplicateMove(uint32 mapId, float x, float y, float z);
bool IsWaitingForLastMove(MovementPriority priority);

View File

@@ -205,21 +205,20 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint
return;
}
// Disable since ExtractAllItemIds bad performance
// //toxic links
// if (msg.starts_with(sPlayerbotAIConfig->toxicLinksPrefix)
// && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0))
// {
// HandleToxicLinksReply(bot, chatChannelSource, msg, name);
// return;
// }
//toxic links
if (msg.starts_with(sPlayerbotAIConfig->toxicLinksPrefix)
&& (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0))
{
HandleToxicLinksReply(bot, chatChannelSource, msg, name);
return;
}
// //thunderfury
// if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019))
// {
// HandleThunderfuryReply(bot, chatChannelSource, msg, name);
// return;
// }
//thunderfury
if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019))
{
HandleThunderfuryReply(bot, chatChannelSource, msg, name);
return;
}
auto messageRepy = GenerateReplyMessage(bot, msg, guid1, name);
SendGeneralResponse(bot, chatChannelSource, messageRepy, name);
@@ -245,6 +244,8 @@ bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chat
GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL);
break;
}
default:
break;
}
GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 25));
@@ -309,6 +310,8 @@ bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatC
GET_PLAYERBOT_AI(bot)->SayToGuild(BOT_TEXT2("suggest_toxic_links", placeholders));
break;
}
default:
break;
}
GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 60));
@@ -317,8 +320,6 @@ bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatC
}
bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name)
{
// Disable since ExtractAllItemIds bad performance
return false;
auto messageItemIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg);
if (messageItemIds.empty())
@@ -404,6 +405,8 @@ bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatCha
}
break;
}
default:
break;
}
GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 60));
}
@@ -412,8 +415,6 @@ bool ChatReplyAction::HandleWTBItemsReply(Player* bot, ChatChannelSource chatCha
}
bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name)
{
// Disable since ExtractAllQuestIds bad performance
return false;
auto messageQuestIds = GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg);
if (messageQuestIds.empty())
@@ -490,6 +491,8 @@ bool ChatReplyAction::HandleLFGQuestsReply(Player* bot, ChatChannelSource chatCh
GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name);
break;
}
default:
break;
}
GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<time_t>("last said", "chat")->Set(time(0) + urand(5, 25));
}
@@ -510,9 +513,11 @@ bool ChatReplyAction::SendGeneralResponse(Player* bot, ChatChannelSource chatCha
}
case ChatChannelSource::SRC_GENERAL:
{
//may reply to the same channel or whisper
GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL);
GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name);
//may reply to the same channel 80% or whisper
if (urand(0, 100) < 80)
GET_PLAYERBOT_AI(bot)->SayToChannel(responseMessage, ChatChannelId::GENERAL);
else
GET_PLAYERBOT_AI(bot)->Whisper(responseMessage, name);
break;
}
case ChatChannelSource::SRC_TRADE:

View File

@@ -11,6 +11,7 @@
#include "Object.h"
#include "Playerbots.h"
#include "QuestDef.h"
#include "StatsWeightCalculator.h"
#include "WorldPacket.h"
#include "BroadcastHelper.h"
@@ -176,8 +177,20 @@ void TalkToQuestGiverAction::RewardMultipleItem(Quest const* quest, Object* ques
bestIds = BestRewards(quest);
if (!bestIds.empty())
{
ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[*bestIds.begin()]);
bot->RewardQuest(quest, *bestIds.begin(), questGiver, true);
StatsWeightCalculator calc(bot);
uint32 best = 0;
float bestScore = 0;
for (uint32 id : bestIds)
{
float score = calc.CalculateItem(quest->RewardChoiceItemId[id]);
if (score > bestScore)
{
bestScore = score;
best = id;
}
}
ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[best]);
bot->RewardQuest(quest, best, questGiver, true);
out << "Rewarded " << ChatHelper::FormatItem(item);
}
else

View File

@@ -130,10 +130,10 @@ bool TellAuraAction::Execute(Event event)
return true;
}
bool TellExpectedDpsAction::Execute(Event event)
bool TellEstimatedDpsAction::Execute(Event event)
{
float dps = AI_VALUE(float, "expected group dps");
botAI->TellMaster("Expected Group DPS: " + std::to_string(dps));
float dps = AI_VALUE(float, "estimated group dps");
botAI->TellMaster("Estimated Group DPS: " + std::to_string(dps));
return true;
}

View File

@@ -30,10 +30,10 @@ public:
virtual bool Execute(Event event);
};
class TellExpectedDpsAction : public Action
class TellEstimatedDpsAction : public Action
{
public:
TellExpectedDpsAction(PlayerbotAI* ai) : Action(ai, "tell expected dps") {}
TellEstimatedDpsAction(PlayerbotAI* ai) : Action(ai, "tell estimated dps") {}
virtual bool Execute(Event event);
};

View File

@@ -29,13 +29,14 @@ bool TellAttackersAction::Execute(Event event)
botAI->TellMaster("--- Attackers ---");
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
int32 count = 0;
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
continue;
botAI->TellMaster(unit->GetName());
botAI->TellMaster(std::to_string(++count) + std::string(".") + unit->GetName());
}
botAI->TellMaster("--- Threat ---");

View File

@@ -165,16 +165,10 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
bool SummonAction::Teleport(Player* summoner, Player* player)
{
Player* master = GetMaster();
// if (master->GetMap() && master->GetMap()->IsDungeon()) {
// InstanceMap* map = master->GetMap()->ToInstanceMap();
// if (map) {
// if (map->CannotEnter(player, true) == Map::CANNOT_ENTER_MAX_PLAYERS) {
// botAI->TellError("I can not enter this dungeon");
// return false;
// }
// }
// }
// Player* master = GetMaster();
if (!summoner)
return false;
if (player->GetVehicle())
{
botAI->TellError("You cannot summon me while I'm on a vehicle");
@@ -197,13 +191,13 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
->botRepairWhenSummon) // .conf option to repair bot gear when summoned 0 = off, 1 = on
bot->DurabilityRepairAll(false, 1.0f, false);
if (master->IsInCombat() && !sPlayerbotAIConfig->allowSummonInCombat)
if (summoner->IsInCombat() && !sPlayerbotAIConfig->allowSummonInCombat)
{
botAI->TellError("You cannot summon me while you're in combat");
return false;
}
if (!master->IsAlive() && !sPlayerbotAIConfig->allowSummonWhenMasterIsDead)
if (!summoner->IsAlive() && !sPlayerbotAIConfig->allowSummonWhenMasterIsDead)
{
botAI->TellError("You cannot summon me while you're dead");
return false;
@@ -218,7 +212,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
bool revive =
sPlayerbotAIConfig->reviveBotWhenSummoned == 2 ||
(sPlayerbotAIConfig->reviveBotWhenSummoned == 1 && !master->IsInCombat() && master->IsAlive());
(sPlayerbotAIConfig->reviveBotWhenSummoned == 1 && !summoner->IsInCombat() && summoner->IsAlive());
if (bot->isDead() && revive)
{

View File

@@ -100,9 +100,12 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr)));
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 5),
new NextAction("vampiric blood", ACTION_HIGH + 4),
new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4),
new NextAction("death strike", ACTION_HIGH + 3), nullptr)));
// triggers.push_back(new TriggerNode("army of the dead", NextAction::array(0, new NextAction("army of the dead",
// ACTION_HIGH + 6), nullptr)));
triggers.push_back(
new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr)));
triggers.push_back(
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode(
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
}

View File

@@ -165,7 +165,7 @@ public:
class CastGhoulFrenzyAction : public CastBuffSpellAction
{
public:
CastGhoulFrenzyAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ghoul frenzy") {}
CastGhoulFrenzyAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "ghoul frenzy", false, 5000) {}
std::string const GetTargetName() override { return "pet target"; }
};
@@ -242,7 +242,7 @@ class CastDeathAndDecayAction : public CastSpellAction
{
public:
CastDeathAndDecayAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "death and decay") {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
// ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
};
class CastHornOfWinterAction : public CastSpellAction

View File

@@ -10,6 +10,7 @@
#include "DKTriggers.h"
#include "FrostDKStrategy.h"
#include "GenericDKNonCombatStrategy.h"
#include "GenericTriggers.h"
#include "Playerbots.h"
#include "PullStrategy.h"
#include "UnholyDKStrategy.h"
@@ -73,10 +74,14 @@ public:
creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike;
creators["plague strike on attacker"] = &DeathKnightTriggerFactoryInternal::plague_strike_on_attacker;
creators["icy touch"] = &DeathKnightTriggerFactoryInternal::icy_touch;
creators["icy touch 8s"] = &DeathKnightTriggerFactoryInternal::icy_touch_8s;
creators["dd cd and icy touch 8s"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_icy_touch_8s;
creators["death coil"] = &DeathKnightTriggerFactoryInternal::death_coil;
creators["icy touch on attacker"] = &DeathKnightTriggerFactoryInternal::icy_touch_on_attacker;
creators["improved icy talons"] = &DeathKnightTriggerFactoryInternal::improved_icy_talons;
creators["plague strike"] = &DeathKnightTriggerFactoryInternal::plague_strike;
creators["plague strike 8s"] = &DeathKnightTriggerFactoryInternal::plague_strike_8s;
creators["dd cd and plague strike 8s"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_plague_strike_8s;
creators["horn of winter"] = &DeathKnightTriggerFactoryInternal::horn_of_winter;
creators["mind freeze"] = &DeathKnightTriggerFactoryInternal::mind_freeze;
creators["mind freeze on enemy healer"] = &DeathKnightTriggerFactoryInternal::mind_freeze_on_enemy_healer;
@@ -87,8 +92,11 @@ public:
creators["chains of ice"] = &DeathKnightTriggerFactoryInternal::chains_of_ice;
creators["unbreakable armor"] = &DeathKnightTriggerFactoryInternal::unbreakable_armor;
creators["high blood rune"] = &DeathKnightTriggerFactoryInternal::high_blood_rune;
creators["high frost rune"] = &DeathKnightTriggerFactoryInternal::high_frost_rune;
creators["high unholy rune"] = &DeathKnightTriggerFactoryInternal::high_unholy_rune;
creators["freezing fog"] = &DeathKnightTriggerFactoryInternal::freezing_fog;
creators["no desolation"] = &DeathKnightTriggerFactoryInternal::no_desolation;
creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation;
creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown;
creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead;
}
@@ -98,11 +106,15 @@ private:
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_8s(PlayerbotAI* botAI) { return new PlagueStrike8sDebuffTrigger(botAI); }
static Trigger* dd_cd_and_plague_strike_8s(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "plague strike 8s"); }
static Trigger* plague_strike_on_attacker(PlayerbotAI* botAI)
{
return new PlagueStrikeDebuffOnAttackerTrigger(botAI);
}
static Trigger* icy_touch(PlayerbotAI* botAI) { return new IcyTouchDebuffTrigger(botAI); }
static Trigger* icy_touch_8s(PlayerbotAI* botAI) { return new IcyTouch8sDebuffTrigger(botAI); }
static Trigger* dd_cd_and_icy_touch_8s(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "icy touch 8s"); }
static Trigger* death_coil(PlayerbotAI* botAI) { return new DeathCoilTrigger(botAI); }
static Trigger* icy_touch_on_attacker(PlayerbotAI* botAI) { return new IcyTouchDebuffOnAttackerTrigger(botAI); }
static Trigger* improved_icy_talons(PlayerbotAI* botAI) { return new ImprovedIcyTalonsTrigger(botAI); }
@@ -122,8 +134,11 @@ private:
static Trigger* chains_of_ice(PlayerbotAI* botAI) { return new ChainsOfIceSnareTrigger(botAI); }
static Trigger* unbreakable_armor(PlayerbotAI* botAI) { return new UnbreakableArmorTrigger(botAI); }
static Trigger* high_blood_rune(PlayerbotAI* botAI) { return new HighBloodRuneTrigger(botAI); }
static Trigger* high_frost_rune(PlayerbotAI* botAI) { return new HighFrostRuneTrigger(botAI); }
static Trigger* high_unholy_rune(PlayerbotAI* botAI) { return new HighUnholyRuneTrigger(botAI); }
static Trigger* freezing_fog(PlayerbotAI* botAI) { return new FreezingFogTrigger(botAI); }
static Trigger* no_desolation(PlayerbotAI* botAI) { return new DesolationTrigger(botAI); }
static Trigger* dd_cd_and_no_desolation(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "death and decay cooldown", "no desolation"); }
static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); }
static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); }
};

View File

@@ -39,14 +39,21 @@ bool PestilenceGlyphTrigger::IsActive()
bool HighBloodRuneTrigger::IsActive()
{
// bot->Say(std::to_string(bot->GetBaseRune(0)) + "_" + std::to_string(bot->GetRuneCooldown(0)) + " " +
// std::to_string(bot->GetBaseRune(1)) + "_" + std::to_string(bot->GetRuneCooldown(1)), LANG_UNIVERSAL);
return !bot->GetRuneCooldown(0) && !bot->GetRuneCooldown(1);
}
bool HighFrostRuneTrigger::IsActive()
{
return !bot->GetRuneCooldown(2) && !bot->GetRuneCooldown(3);
}
bool HighUnholyRuneTrigger::IsActive()
{
return !bot->GetRuneCooldown(4) && !bot->GetRuneCooldown(5);
}
bool DesolationTrigger::IsActive()
{
return bot->HasAura(66817) && !botAI->HasAura("desolation", GetTarget(), false, true, -1, true);
return bot->HasAura(66817) && BuffTrigger::IsActive();
}
bool DeathAndDecayCooldownTrigger::IsActive()
@@ -54,6 +61,6 @@ bool DeathAndDecayCooldownTrigger::IsActive()
uint32 spellId = AI_VALUE2(uint32, "spell id", name);
if (!spellId)
return true;
return bot->HasSpellCooldown(spellId);
return bot->GetSpellCooldownDelay(spellId) >= 3000;
}

View File

@@ -17,14 +17,26 @@ BUFF_TRIGGER(ImprovedIcyTalonsTrigger, "improved icy talons");
class PlagueStrikeDebuffTrigger : public DebuffTrigger
{
public:
PlagueStrikeDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", true, .0f) {}
PlagueStrikeDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", 1, true, .0f) {}
};
class PlagueStrike8sDebuffTrigger : public DebuffTrigger
{
public:
PlagueStrike8sDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "blood plague", 1, true, .0f, 3000) {}
};
// DEBUFF_CHECKISOWNER_TRIGGER(IcyTouchDebuffTrigger, "frost fever");
class IcyTouchDebuffTrigger : public DebuffTrigger
{
public:
IcyTouchDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", true, .0f) {}
IcyTouchDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", 1, true, .0f) {}
};
class IcyTouch8sDebuffTrigger : public DebuffTrigger
{
public:
IcyTouch8sDebuffTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "frost fever", 1, true, .0f, 3000) {}
};
BUFF_TRIGGER(UnbreakableArmorTrigger, "unbreakable armor");
@@ -139,6 +151,20 @@ public:
bool IsActive() override;
};
class HighFrostRuneTrigger : public Trigger
{
public:
HighFrostRuneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high frost rune") {}
bool IsActive() override;
};
class HighUnholyRuneTrigger : public Trigger
{
public:
HighUnholyRuneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high unholy rune") {}
bool IsActive() override;
};
class FreezingFogTrigger : public HasAuraTrigger
{
public:
@@ -148,7 +174,7 @@ public:
class DesolationTrigger : public BuffTrigger
{
public:
DesolationTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "desolation") {}
DesolationTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "desolation", 1, false, true, 10000) {}
bool IsActive() override;
};

View File

@@ -26,7 +26,7 @@ public:
// creators["icebound fortitude"] = &icebound_fortitude;
// creators["mind freeze"] = &mind_freeze;
// creators["hungering cold"] = &hungering_cold;
// creators["unbreakable armor"] = &unbreakable_armor;
creators["unbreakable armor"] = &unbreakable_armor;
// creators["improved icy talons"] = &improved_icy_talons;
}
@@ -70,6 +70,13 @@ private:
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("unbreakable armor",
/*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
};
FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
@@ -80,24 +87,32 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
NextAction** FrostDKStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("obliterate", ACTION_DEFAULT + 0.5f), new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
// new NextAction("death strike", ACTION_NORMAL + 3),
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.2f),
0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f),
new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
}
void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr)));
triggers.push_back(new TriggerNode(
"freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_DEFAULT + 0.5f), nullptr)));
triggers.push_back(new TriggerNode(
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode(
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
triggers.push_back(new TriggerNode(
"unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_NORMAL + 4), nullptr)));
triggers.push_back(new TriggerNode(
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode(
"freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 1), nullptr)));
triggers.push_back(
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode(
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune
// weapon", ACTION_NORMAL + 4), nullptr)));
}

View File

@@ -135,7 +135,7 @@ private:
{
return new ActionNode("death and decay",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
@@ -176,33 +176,14 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("mind freeze on enemy healer",
NextAction::array(0, new NextAction("mind freeze on enemy healer", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode(
"horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr)));
// triggers.push_back(new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("reach melee",
// ACTION_NORMAL + 8), nullptr)));
triggers.push_back(new TriggerNode("critical health",
NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5),
new NextAction("rune tap", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode("medium health",
NextAction::array(0, new NextAction("rune tap", ACTION_NORMAL + 4),
new NextAction("death strike", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("icy touch on attacker",
NextAction::array(0, new NextAction("icy touch on attacker", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("plague strike on attacker",
NextAction::array(0, new NextAction("plague strike on attacker", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("high aoe",
// NextAction::array(0,
// new NextAction("death and decay", ACTION_NORMAL + 5),
// new NextAction("pestilence", ACTION_NORMAL + 4),
// new NextAction("blood boil", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_HIGH + 9),
new NextAction("pestilence", ACTION_NORMAL + 4),

View File

@@ -26,7 +26,7 @@ public:
// creators["summon gargoyle"] = &army of the dead;
// creators["anti magic shell"] = &anti_magic_shell;
// creators["anti magic zone"] = &anti_magic_zone;
// creators["ghoul frenzy"] = &ghoul_frenzy;
creators["ghoul frenzy"] = &ghoul_frenzy;
creators["corpse explosion"] = &corpse_explosion;
creators["icy touch"] = &icy_touch;
}
@@ -35,15 +35,21 @@ private:
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death strike",
/*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr),
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("ghoul frenzy",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("corpse explosion",
/*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr),
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
@@ -51,14 +57,14 @@ private:
static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("scourge strike",
/*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr),
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icy touch",
/*P*/ NextAction::array(0, new NextAction("unholy presence"), nullptr),
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
@@ -72,29 +78,57 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI
NextAction** UnholyDKStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("death and decay", ACTION_DEFAULT + 0.5f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.4f),
new NextAction("summon gargoyle", ACTION_DEFAULT + 0.3f), new NextAction("death coil", ACTION_DEFAULT + 0.2f),
new NextAction("scourge strike", ACTION_NORMAL + 0.1f), new NextAction("melee", ACTION_DEFAULT), nullptr);
0, new NextAction("death and decay", ACTION_HIGH + 5),
new NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
new NextAction("death coil", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
}
void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"death and decay cooldown", NextAction::array(0,
new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
new NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
new NextAction("blood boil", ACTION_DEFAULT + 0.7f),
new NextAction("icy touch", ACTION_DEFAULT + 0.6f),
new NextAction("plague strike", ACTION_DEFAULT + 0.5f),
nullptr)));
triggers.push_back(new TriggerNode("dd cd and no desolation",
NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.75f), nullptr)));
// triggers.push_back(
// new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode(
// "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"high frost rune", NextAction::array(0,
new NextAction("icy touch", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(new TriggerNode(
"high unholy rune", NextAction::array(0,
new NextAction("plague strike", ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode(
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(
new TriggerNode("dd cd and plague strike 8s", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("dd cd and icy touch 8s", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction(, ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode(
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
triggers.push_back(new TriggerNode("critical health",
NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("no desolation",
NextAction::array(0, new NextAction("blood strike", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode(
"death and decay cooldown", NextAction::array(0, new NextAction("ghoul frenzy", ACTION_NORMAL + 5.0f),
new NextAction("scourge strike", ACTION_NORMAL + 4.0f),
new NextAction("blood boil", ACTION_NORMAL + 3.0f),
new NextAction("icy touch", ACTION_NORMAL + 2.0f),
new NextAction("plague strike", ACTION_NORMAL + 1.0f), nullptr)));
triggers.push_back(
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_HIGH + 1), nullptr)));
}
void UnholyDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -70,7 +70,7 @@ private:
{
return new ActionNode("mangle (cat)",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("claw"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
}
@@ -122,38 +122,58 @@ CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrateg
NextAction** CatDpsDruidStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.1f), nullptr);
return NextAction::array(0, new NextAction("tiger's fury", ACTION_DEFAULT + 0.1f), nullptr);
}
void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
FeralDruidStrategy::InitTriggers(triggers);
// Default priority
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full and high energy",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full and high energy",
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(
new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 2), nullptr)));
new TriggerNode("faerie fire (feral)",
NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f), nullptr)));
// Main spell
triggers.push_back(
new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_NORMAL + 5), nullptr)));
new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr)));
triggers.push_back(
new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr)));
triggers.push_back(new TriggerNode("combo points available",
NextAction::array(0, new NextAction("rip", ACTION_HIGH + 6), nullptr)));
triggers.push_back(new TriggerNode(
"combo points available", NextAction::array(0, new NextAction("ferocious bite", ACTION_NORMAL + 9), nullptr)));
"ferocious bite time", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 5), nullptr)));
triggers.push_back(
new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_EMERGENCY + 1), nullptr)));
new TriggerNode("target with combo points almost dead",
NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode("mangle (cat)",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_HIGH + 1), nullptr)));
// AOE
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode(
"faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH), nullptr)));
"light aoe", NextAction::array(0, new NextAction("rake on attacker", ACTION_HIGH + 2), nullptr)));
// Reach target
triggers.push_back(new TriggerNode(
"tiger's fury", NextAction::array(0, new NextAction("tiger's fury", ACTION_EMERGENCY + 1), nullptr)));
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9), nullptr)));
triggers.push_back(
new TriggerNode("behind target", NextAction::array(0, new NextAction("pounce", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("player has no flag", NextAction::array(0, new NextAction("prowl",
// ACTION_HIGH), nullptr))); triggers.push_back(new TriggerNode("enemy out of melee", NextAction::array(0, new
// NextAction("prowl", ACTION_INTERRUPT + 1), nullptr)));
triggers.push_back(
new TriggerNode("player has flag", NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("enemy flagcarrier near",
NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY), nullptr)));
new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("dash", ACTION_HIGH + 8), nullptr)));
}
void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 2), nullptr)));
}
void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}

View File

@@ -83,6 +83,8 @@ public:
creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire;
creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp;
creators["tiger's fury"] = &DruidTriggerFactoryInternal::tigers_fury;
creators["berserk"] = &DruidTriggerFactoryInternal::berserk;
creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar;
creators["rake"] = &DruidTriggerFactoryInternal::rake;
creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild;
creators["mark of the wild on party"] = &DruidTriggerFactoryInternal::mark_of_the_wild_on_party;
@@ -101,6 +103,8 @@ public:
creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse;
creators["eclipse (solar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_solar_cooldown;
creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown;
creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat;
creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time;
}
private:
@@ -117,6 +121,8 @@ private:
static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); }
static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); }
static Trigger* tigers_fury(PlayerbotAI* botAI) { return new TigersFuryTrigger(botAI); }
static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); }
static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); }
static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); }
static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); }
static Trigger* mark_of_the_wild_on_party(PlayerbotAI* botAI) { return new MarkOfTheWildOnPartyTrigger(botAI); }
@@ -133,6 +139,8 @@ private:
static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); }
static Trigger* eclipse_solar_cooldown(PlayerbotAI* ai) { return new EclipseSolarCooldownTrigger(ai); }
static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); }
static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); }
static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); }
};
class DruidAiObjectContextInternal : public NamedObjectContext<Action>
@@ -174,6 +182,7 @@ public:
creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat;
creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat;
creators["rake"] = &DruidAiObjectContextInternal::rake;
creators["rake on attacker"] = &DruidAiObjectContextInternal::rake_on_attacker;
creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite;
creators["rip"] = &DruidAiObjectContextInternal::rip;
creators["cower"] = &DruidAiObjectContextInternal::cower;
@@ -188,6 +197,7 @@ public:
creators["abolish poison on party"] = &DruidAiObjectContextInternal::abolish_poison_on_party;
creators["berserk"] = &DruidAiObjectContextInternal::berserk;
creators["tiger's fury"] = &DruidAiObjectContextInternal::tigers_fury;
creators["savage roar"] = &DruidAiObjectContextInternal::savage_roar;
creators["mark of the wild"] = &DruidAiObjectContextInternal::mark_of_the_wild;
creators["mark of the wild on party"] = &DruidAiObjectContextInternal::mark_of_the_wild_on_party;
creators["regrowth"] = &DruidAiObjectContextInternal::regrowth;
@@ -257,6 +267,7 @@ private:
static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); }
static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); }
static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); }
static Action* rake_on_attacker(PlayerbotAI* botAI) { return new CastRakeOnMeleeAttackersAction(botAI); }
static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); }
static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); }
static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); }
@@ -271,6 +282,7 @@ private:
static Action* abolish_poison_on_party(PlayerbotAI* botAI) { return new CastAbolishPoisonOnPartyAction(botAI); }
static Action* berserk(PlayerbotAI* botAI) { return new CastBerserkAction(botAI); }
static Action* tigers_fury(PlayerbotAI* botAI) { return new CastTigersFuryAction(botAI); }
static Action* savage_roar(PlayerbotAI* botAI) { return new CastSavageRoarAction(botAI); }
static Action* mark_of_the_wild(PlayerbotAI* botAI) { return new CastMarkOfTheWildAction(botAI); }
static Action* mark_of_the_wild_on_party(PlayerbotAI* botAI) { return new CastMarkOfTheWildOnPartyAction(botAI); }
static Action* regrowth(PlayerbotAI* botAI) { return new CastRegrowthAction(botAI); }

View File

@@ -0,0 +1 @@
#include "DruidCatActions.h"

View File

@@ -35,10 +35,23 @@ public:
CastTigersFuryAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "tiger's fury") {}
};
class CastSavageRoarAction : public CastBuffSpellAction
{
public:
CastSavageRoarAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "savage roar") {}
std::string const GetTargetName() override { return "current target"; }
};
class CastRakeAction : public CastDebuffSpellAction
{
public:
CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake") {}
CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {}
};
class CastRakeOnMeleeAttackersAction : public CastDebuffSpellOnMeleeAttackerAction
{
public:
CastRakeOnMeleeAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnMeleeAttackerAction(botAI, "rake", true, 6.0f) {}
};
class CastClawAction : public CastMeleeSpellAction
@@ -65,10 +78,10 @@ public:
CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {}
};
class CastRipAction : public CastMeleeSpellAction
class CastRipAction : public CastMeleeDebuffSpellAction
{
public:
CastRipAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rip") {}
CastRipAction(PlayerbotAI* botAI) : CastMeleeDebuffSpellAction(botAI, "rip", true, 12.0f) {}
};
class CastShredAction : public CastMeleeSpellAction

View File

@@ -22,8 +22,6 @@ bool ThornsOnPartyTrigger::IsActive()
return BuffOnPartyTrigger::IsActive() && !botAI->HasAura("thorns", GetTarget());
}
bool MoonfireTrigger::IsActive() { return DebuffTrigger::IsActive() && !GetTarget()->HasUnitState(UNIT_STATE_ROOT); }
bool EntanglingRootsKiteTrigger::IsActive()
{
return DebuffTrigger::IsActive() && AI_VALUE(uint8, "attacker count") < 3 && !GetTarget()->GetPower(POWER_MANA);

View File

@@ -9,6 +9,8 @@
#include "CureTriggers.h"
#include "GenericTriggers.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "SharedDefines.h"
class PlayerbotAI;
@@ -73,8 +75,6 @@ class MoonfireTrigger : public DebuffTrigger
{
public:
MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {}
bool IsActive() override;
};
class FaerieFireTrigger : public DebuffTrigger
@@ -95,10 +95,22 @@ public:
BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {}
};
class TigersFuryTrigger : public BoostTrigger
class TigersFuryTrigger : public BuffTrigger
{
public:
TigersFuryTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "tiger's fury") {}
TigersFuryTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "tiger's fury") {}
};
class BerserkTrigger : public BoostTrigger
{
public:
BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {}
};
class SavageRoarTrigger : public BuffTrigger
{
public:
SavageRoarTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "savage roar") {}
};
class NaturesGraspTrigger : public BoostTrigger
@@ -212,4 +224,43 @@ public:
bool IsActive() override { return bot->HasSpellCooldown(48518); }
};
class MangleCatTrigger : public DebuffTrigger
{
public:
MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {}
bool IsActive() override
{
return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true)
&& !botAI->HasAura("trauma", GetTarget(), false, false, -1, true);
}
};
class FerociousBiteTimeTrigger : public Trigger
{
public:
FerociousBiteTimeTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite time") {}
bool IsActive() override
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
uint8 cp = AI_VALUE2(uint8, "combo", "current target");
if (cp < 5)
return false;
Aura* roar = botAI->GetAura("savage roar", bot);
bool roarCheck = !roar || roar->GetDuration() > 10000;
if (!roarCheck)
return false;
Aura* rip = botAI->GetAura("rip", target, true);
bool ripCheck = !rip || rip->GetDuration() > 10000;
if (!ripCheck)
return false;
return true;
}
};
#endif

View File

@@ -112,4 +112,6 @@ void FeralDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr)));
triggers.push_back(new TriggerNode("enemy flagcarrier near",
NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr)));
triggers.push_back(
new TriggerNode("berserk", NextAction::array(0, new NextAction("berserk", ACTION_HIGH + 6), nullptr)));
}

View File

@@ -47,7 +47,7 @@ float CastTimeMultiplier::GetValue(Action* action)
return 1.0f;
}
if (castTime > (1000 * target->GetHealth() / AI_VALUE(float, "expected group dps")))
if (castTime > (1000 * target->GetHealth() / AI_VALUE(float, "estimated group dps")))
{
return 0.1f;
}

View File

@@ -89,7 +89,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
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)));
new TriggerNode("dps", NextAction::array(0, new NextAction("tell estimated dps", relevance), NULL)));
triggers.push_back(
new TriggerNode("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL)));
}

View File

@@ -5,12 +5,19 @@
#include "UseFoodStrategy.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
void UseFoodStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
Strategy::InitTriggers(triggers);
if (sPlayerbotAIConfig->freeFood)
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
else
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("food", 3.0f), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
if (sPlayerbotAIConfig->freeFood)
triggers.push_back(new TriggerNode("high mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
else
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("drink", 3.0f), nullptr)));
}

View File

@@ -41,12 +41,13 @@ DpsHunterStrategy::DpsHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy
NextAction** DpsHunterStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("kill shot", ACTION_DEFAULT + 0.6f), new NextAction("chimera shot", ACTION_DEFAULT + 0.5f),
new NextAction("explosive shot", ACTION_DEFAULT + 0.4f), new NextAction("aimed shot", ACTION_DEFAULT + 0.3f),
/*new NextAction("arcane shot", ACTION_DEFAULT + 0.2f),*/ new NextAction("steady shot", ACTION_DEFAULT + 0.1f),
0, new NextAction("kill shot", ACTION_DEFAULT + 0.8f), new NextAction("chimera shot", ACTION_DEFAULT + 0.7f),
new NextAction("explosive shot", ACTION_DEFAULT + 0.6f), new NextAction("aimed shot", ACTION_DEFAULT + 0.5f),
new NextAction("silencing shot", ACTION_DEFAULT + 0.4f),
new NextAction("kill command", ACTION_DEFAULT + 0.3f),
// new NextAction("arcane shot", ACTION_DEFAULT + 0.2f),
new NextAction("steady shot", ACTION_DEFAULT + 0.1f),
new NextAction("auto shot", ACTION_DEFAULT), nullptr);
// return NextAction::array(0, new NextAction("explosive shot", 11.0f), new NextAction("auto shot", 10.0f), new
// NextAction("auto attack", 9.0f), nullptr);
}
void DpsHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@@ -95,7 +95,7 @@ void GenericHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("enemy too close for auto shot",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
triggers.push_back(
new TriggerNode("misdirection on main tank",
new TriggerNode("low tank threat",
NextAction::array(0, new NextAction("misdirection on main tank", ACTION_HIGH + 7), NULL)));
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("deterrence", ACTION_HIGH + 5), nullptr)));

View File

@@ -78,6 +78,9 @@ END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastKillShotAction, "kill shot")
END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastSilencingShotAction, "silencing shot")
END_SPELL_ACTION()
BEGIN_RANGED_SPELL_ACTION(CastTranquilizingShotAction, "tranquilizing shot")
END_SPELL_ACTION()
@@ -139,6 +142,14 @@ public:
std::string const GetTargetName() override { return "pet target"; }
};
class CastKillCommandAction : public CastAuraSpellAction
{
public:
CastKillCommandAction(PlayerbotAI* botAI) : CastAuraSpellAction(botAI, "kill command") {}
std::string const GetTargetName() override { return "pet target"; }
};
class CastRevivePetAction : public CastBuffSpellAction
{
public:

View File

@@ -143,6 +143,7 @@ public:
creators["scorpid sting"] = &HunterAiObjectContextInternal::scorpid_sting;
creators["hunter's mark"] = &HunterAiObjectContextInternal::hunters_mark;
creators["mend pet"] = &HunterAiObjectContextInternal::mend_pet;
creators["kill command"] = &HunterAiObjectContextInternal::kill_command;
creators["revive pet"] = &HunterAiObjectContextInternal::revive_pet;
creators["call pet"] = &HunterAiObjectContextInternal::call_pet;
creators["black arrow"] = &HunterAiObjectContextInternal::black_arrow;
@@ -171,6 +172,7 @@ public:
creators["steady shot"] = &HunterAiObjectContextInternal::steady_shot;
creators["kill shot"] = &HunterAiObjectContextInternal::kill_shot;
creators["misdirection on main tank"] = &HunterAiObjectContextInternal::misdirection_on_main_tank;
creators["silencing shot"] = &HunterAiObjectContextInternal::silencing_shot;
}
private:
@@ -196,6 +198,7 @@ private:
static Action* scorpid_sting(PlayerbotAI* botAI) { return new CastScorpidStingAction(botAI); }
static Action* hunters_mark(PlayerbotAI* botAI) { return new CastHuntersMarkAction(botAI); }
static Action* mend_pet(PlayerbotAI* botAI) { return new CastMendPetAction(botAI); }
static Action* kill_command(PlayerbotAI* botAI) { return new CastKillCommandAction(botAI); }
static Action* revive_pet(PlayerbotAI* botAI) { return new CastRevivePetAction(botAI); }
static Action* call_pet(PlayerbotAI* botAI) { return new CastCallPetAction(botAI); }
static Action* black_arrow(PlayerbotAI* botAI) { return new CastBlackArrow(botAI); }
@@ -217,6 +220,8 @@ private:
static Action* steady_shot(PlayerbotAI* ai) { return new CastSteadyShotAction(ai); }
static Action* kill_shot(PlayerbotAI* ai) { return new CastKillShotAction(ai); }
static Action* misdirection_on_main_tank(PlayerbotAI* ai) { return new CastMisdirectionOnMainTankAction(ai); }
static Action* silencing_shot(PlayerbotAI* ai) { return new CastSilencingShotAction(ai); }
};
HunterAiObjectContext::HunterAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI)

View File

@@ -82,23 +82,32 @@ DpsPaladinStrategy::DpsPaladinStrategy(PlayerbotAI* botAI) : GenericPaladinStrat
NextAction** DpsPaladinStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("crusader strike", ACTION_DEFAULT + 0.4f),
new NextAction("judgement of wisdom", ACTION_DEFAULT + 0.3f),
new NextAction("divine storm", ACTION_DEFAULT + 0.2f),
return NextAction::array(0,
new NextAction("hammer of wrath", ACTION_DEFAULT + 0.6f),
new NextAction("crusader strike", ACTION_DEFAULT + 0.5f),
new NextAction("judgement of wisdom", ACTION_DEFAULT + 0.4f),
new NextAction("divine storm", ACTION_DEFAULT + 0.3f),
new NextAction("consecration", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), NULL);
new NextAction("melee", ACTION_DEFAULT), nullptr);
}
void DpsPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericPaladinStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode(
// "enough mana", NextAction::array(0, new NextAction("consecration", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(
new TriggerNode("art of war", NextAction::array(0, new NextAction("exorcism", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(
new TriggerNode("seal", NextAction::array(0, new NextAction("seal of corruption", ACTION_HIGH), NULL)));
// triggers.push_back(new TriggerNode("seal", NextAction::array(0, new NextAction("seal of command", 90.0f),
// nullptr)));
triggers.push_back(
new TriggerNode("low mana", NextAction::array(0, new NextAction("seal of wisdom", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode(
"avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode("sanctity aura", NextAction::array(0, new NextAction("sanctity aura", 90.0f),
// nullptr))); triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("repentance or
// shield", ACTION_CRITICAL_HEAL + 3), new NextAction("holy light", ACTION_CRITICAL_HEAL + 2), nullptr)));
@@ -112,11 +121,11 @@ void DpsPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// triggers.push_back(new TriggerNode("repentance", NextAction::array(0, new NextAction("repentance",
// ACTION_INTERRUPT + 2), nullptr)));
triggers.push_back(new TriggerNode(
"medium aoe", NextAction::array(0, new NextAction("consecration", ACTION_HIGH + 3), nullptr)));
triggers.push_back(
new TriggerNode("art of war", NextAction::array(0, new NextAction("exorcism", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode("target critical health",
NextAction::array(0, new NextAction("hammer of wrath", ACTION_HIGH), nullptr)));
"medium aoe", NextAction::array(0,
new NextAction("divine storm", ACTION_HIGH + 4),
new NextAction("consecration", ACTION_HIGH + 3), nullptr)));
// triggers.push_back(new TriggerNode("target critical health",
// NextAction::array(0, new NextAction("hammer of wrath", ACTION_HIGH), nullptr)));
// triggers.push_back(new TriggerNode(
// "not facing target",
// NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), NULL)));

View File

@@ -37,7 +37,7 @@ void GenericPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"protect party member",
NextAction::array(0, new NextAction("blessing of protection on party", ACTION_EMERGENCY + 2), nullptr)));
triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("divine plea", ACTION_HIGH), NULL)));
new TriggerNode("high mana", NextAction::array(0, new NextAction("divine plea", ACTION_HIGH), NULL)));
}
void PaladinCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -61,8 +61,7 @@ void PaladinCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void PaladinBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode("divine favor", NextAction::array(0, new NextAction("divine favor",
// ACTION_HIGH + 1), nullptr)));
}

View File

@@ -50,7 +50,9 @@ void HealPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("medium group heal occasion",
NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5), nullptr)));
NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5),
new NextAction("avenging wrath", ACTION_HIGH + 4),
nullptr)));
triggers.push_back(
new TriggerNode("party member critical health",

View File

@@ -98,6 +98,8 @@ void TankPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("holy shield", ACTION_HIGH + 4), nullptr)));
// triggers.push_back(new TriggerNode("blessing", NextAction::array(0, new NextAction("blessing of sanctuary",
// ACTION_HIGH + 9), nullptr)));
triggers.push_back(new TriggerNode(
"avenging wrath", NextAction::array(0, new NextAction("avenging wrath", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
new TriggerNode("target critical health",
NextAction::array(0, new NextAction("hammer of wrath", ACTION_CRITICAL_HEAL), nullptr)));

View File

@@ -6,6 +6,7 @@
#include "RaidBwlStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidMcStrategy.h"
#include "RaidAq20Strategy.h"
class RaidStrategyContext : public NamedObjectContext<Strategy>
{
@@ -19,6 +20,7 @@ public:
creators["bwl"] = &RaidStrategyContext::bwl;
creators["uld"] = &RaidStrategyContext::uld;
creators["mc"] = &RaidStrategyContext::mc;
creators["aq20"] = &RaidStrategyContext::aq20;
}
private:
@@ -26,6 +28,7 @@ private:
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); }
};
#endif
#endif

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H
#define _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidAq20Actions.h"
class RaidAq20ActionContext : public NamedObjectContext<Action>
{
public:
RaidAq20ActionContext()
{
creators["aq20 use crystal"] = &RaidAq20ActionContext::use_crystal;
}
private:
static Action* use_crystal(PlayerbotAI* ai) { return new Aq20UseCrystalAction(ai); }
};
#endif

View File

@@ -0,0 +1,49 @@
#include "RaidAq20Actions.h"
#include "Playerbots.h"
#include "RaidAq20Utils.h"
bool Aq20UseCrystalAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred"))
{
if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss))
{
float botDist = bot->GetDistance(crystal);
if (botDist > INTERACTION_DISTANCE)
return MoveTo(bot->GetMapId(),
crystal->GetPositionX() + frand(-3.5f, 3.5f),
crystal->GetPositionY() + frand(-3.5f, 3.5f),
crystal->GetPositionZ());
// if we're already in range just wait here until it's time to activate crystal
SetNextMovementDelay(500);
// don't activate crystal if boss too far or its already been activated
if (boss->GetDistance(crystal) > 25.0f ||
crystal->HasGameObjectFlag(GO_FLAG_IN_USE))
return false;
// don't activate crystal if boss doesn't have buff yet AND isn't going to have it soon
// (though ideally bot should activate it ~5 seconds early due to time it takes for
// crystal to activate and remove buff)
if (!RaidAq20Utils::IsOssirianBuffActive(boss) &&
RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss) > 5000)
return false;
// this makes crystal do its animation (then disappear after)
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << crystal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
// this makes crystal actually remove the buff and put on debuff (took a while to figure that out)
WorldPacket data2(CMSG_GAMEOBJ_USE);
data2 << crystal->GetGUID();
bot->GetSession()->HandleGameobjectReportUse(data2);
return true;
}
}
return false;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAYERBOT_RAIDAQ20ACTIONS_H
#define _PLAYERBOT_RAIDAQ20ACTIONS_H
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
class Aq20UseCrystalAction : public MovementAction
{
public:
Aq20UseCrystalAction(PlayerbotAI* botAI, std::string const name = "aq20 use crystal") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,11 @@
#include "RaidAq20Strategy.h"
#include "Strategy.h"
void RaidAq20Strategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("aq20 move to crystal",
NextAction::array(0, new NextAction("aq20 use crystal", ACTION_RAID), nullptr)));
}

View File

@@ -0,0 +1,17 @@
#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H
#define _PLAYERBOT_RAIDAQ20STRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidAq20Strategy : public Strategy
{
public:
RaidAq20Strategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "aq20"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
// virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidAq20Triggers.h"
class RaidAq20TriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidAq20TriggerContext()
{
creators["aq20 move to crystal"] = &RaidAq20TriggerContext::move_to_crystal;
}
private:
static Trigger* move_to_crystal(PlayerbotAI* ai) { return new Aq20MoveToCrystalTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,37 @@
#include "RaidAq20Triggers.h"
#include "SharedDefines.h"
#include "RaidAq20Utils.h"
bool Aq20MoveToCrystalTrigger::IsActive()
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred"))
{
if (boss->IsInCombat())
{
// if buff is active move to crystal
if (RaidAq20Utils::IsOssirianBuffActive(boss))
return true;
// if buff is not active a debuff will be, buff becomes active once debuff expires
// so move to crystal when debuff almost done, or based debuff time left and
// distance bot is from crystal (ie: start moving early enough to make it)
int32 debuffTimeRemaining = RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss);
if (debuffTimeRemaining < 5000)
return true;
if (debuffTimeRemaining < 30000)
{
if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss))
{
float botDist = bot->GetDistance(crystal);
float timeToReach = botDist / bot->GetSpeed(MOVE_RUN);
// bot should ideally activate crystal a ~5 seconds early (due to time it takes for crystal
// to activate) so aim to get there in time to do so
return debuffTimeRemaining - 5000 < timeToReach * 1000.0f;
}
}
}
}
return false;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERS_H
#define _PLAYERBOT_RAIDAQ20TRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
class Aq20MoveToCrystalTrigger : public Trigger
{
public:
Aq20MoveToCrystalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aq20 move to crystal") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,38 @@
#include "RaidAq20Utils.h"
#include "SpellAuras.h"
uint32 const OSSIRIAN_BUFF = 25176;
uint32 const OSSIRIAN_DEBUFFS[] = {25177, 25178, 25180, 25181, 25183};
uint32 const OSSIRIAN_CRYSTAL_GO_ENTRY = 180619;
bool RaidAq20Utils::IsOssirianBuffActive(Unit* ossirian)
{
return ossirian && ossirian->HasAura(OSSIRIAN_BUFF);
}
int32 RaidAq20Utils::GetOssirianDebuffTimeRemaining(Unit* ossirian)
{
int32 retVal = 0xffffff;
if (ossirian)
{
for (uint32 debuff : OSSIRIAN_DEBUFFS)
{
if (AuraApplication* auraApplication = ossirian->GetAuraApplication(debuff))
{
if (Aura* aura = auraApplication->GetBase())
{
int32 duration = aura->GetDuration();
if (retVal > duration)
retVal = duration;
}
}
}
}
return retVal;
}
GameObject* RaidAq20Utils::GetNearestCrystal(Unit* ossirian)
{
return ossirian ? ossirian->FindNearestGameObject(OSSIRIAN_CRYSTAL_GO_ENTRY, 200.0f) : nullptr;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_RAIDAQ20UTILS_H
#define _PLAYERBOT_RAIDAQ20UTILS_H
#include "GameObject.h"
#include "Unit.h"
class RaidAq20Utils
{
public:
static bool IsOssirianBuffActive(Unit* ossirian);
static int32 GetOssirianDebuffTimeRemaining(Unit* ossirian);
static GameObject* GetNearestCrystal(Unit* ossirian);
};
#endif

View File

@@ -64,6 +64,7 @@ public:
this->prev_phase = 0;
this->prev_erupt = 0;
this->prev_timer = 0;
ResetSafe();
waypoints.push_back(std::make_pair(2793.58f, -3665.93f));
waypoints.push_back(std::make_pair(2775.49f, -3674.43f));
waypoints.push_back(std::make_pair(2762.30f, -3684.59f));

View File

@@ -83,7 +83,7 @@ void AssassinationRogueStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("fan of knives", ACTION_NORMAL + 5), NULL)));
triggers.push_back(new TriggerNode(
"tricks of the trade on main tank",
"low tank threat",
NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), NULL)));
triggers.push_back(new TriggerNode(

View File

@@ -141,7 +141,7 @@ void DpsRogueStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("expose armor", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode(
"tricks of the trade on main tank",
"low tank threat",
NextAction::array(0, new NextAction("tricks of the trade on main tank", ACTION_HIGH + 7), nullptr)));
}

View File

@@ -77,8 +77,6 @@ public:
creators["tricks of the trade on main tank"] = &RogueTriggerFactoryInternal::tricks_of_the_trade_on_main_tank;
creators["adrenaline rush"] = &RogueTriggerFactoryInternal::adrenaline_rush;
creators["blade fury"] = &RogueTriggerFactoryInternal::blade_fury;
creators["target with combo points almost dead"] =
&RogueTriggerFactoryInternal::target_with_combo_points_almost_dead;
}
private:
@@ -102,10 +100,6 @@ private:
{
return new TricksOfTheTradeOnMainTankTrigger(ai);
}
static Trigger* target_with_combo_points_almost_dead(PlayerbotAI* ai)
{
return new TargetWithComboPointsLowerHealTrigger(ai, 3, 3.0f);
}
};
class RogueAiObjectContextInternal : public NamedObjectContext<Action>

View File

@@ -124,14 +124,3 @@ bool OffHandWeaponNoEnchantTrigger::IsActive()
return false;
return true;
}
bool TargetWithComboPointsLowerHealTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target || !target->IsAlive() || !target->IsInWorld())
{
return false;
}
return ComboPointsAvailableTrigger::IsActive() &&
(target->GetHealth() / AI_VALUE(float, "expected group dps")) <= lifeTime;
}

View File

@@ -127,17 +127,6 @@ public:
TricksOfTheTradeOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "tricks of the trade", true) {}
};
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
{
public:
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime)
{
}
bool IsActive() override;
private:
float lifeTime;
};
#endif

View File

@@ -51,8 +51,8 @@ void CasterShamanStrategy::InitTriggers(std::vector<TriggerNode*>& 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(
"enough mana", NextAction::array(0, new NextAction("chain lightning", ACTION_DEFAULT + 0.1f), nullptr)));
// triggers.push_back(new TriggerNode(
// "enough mana", NextAction::array(0, new NextAction("chain lightning", ACTION_DEFAULT + 0.1f), nullptr)));
triggers.push_back(new TriggerNode("main hand weapon no imbue",
NextAction::array(0, new NextAction("flametongue weapon", 22.0f), nullptr)));
@@ -66,7 +66,9 @@ void CasterShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// shock", 21.0f), nullptr)));
triggers.push_back(
new TriggerNode("no fire totem", NextAction::array(0, new NextAction("totem of wrath", 15.0f), NULL)));
triggers.push_back(new TriggerNode("fire elemental totem",
NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr)));
triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("thunderstorm", ACTION_HIGH + 1), nullptr)));

View File

@@ -104,6 +104,11 @@ void HealShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("mana tide totem", ACTION_HIGH + 5), NULL)));
triggers.push_back(
new TriggerNode("no fire totem", NextAction::array(0, new NextAction("flametongue totem", 7.0f),
new NextAction("searing totem", 6.0f), nullptr)));
triggers.push_back(new TriggerNode("fire elemental totem",
NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr)));
triggers.push_back(new TriggerNode(
"party member to heal out of spell range",
NextAction::array(0, new NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 1), nullptr)));

View File

@@ -72,11 +72,8 @@ void MeleeShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
new TriggerNode("flame shock", NextAction::array(0, new NextAction("flame shock", 20.0f), nullptr)));
triggers.push_back(
new TriggerNode("maelstrom weapon", NextAction::array(0, new NextAction("lightning bolt", 25.0f), nullptr)));
triggers.push_back(new TriggerNode("not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_NORMAL + 7), nullptr)));
// triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of
// enemy contact", ACTION_NORMAL + 8), nullptr)));
new TriggerNode("maelstrom weapon 4", NextAction::array(0, new NextAction("lightning bolt", 25.0f), nullptr)));
triggers.push_back(new TriggerNode(
"medium aoe", NextAction::array(0, new NextAction("strength of earth totem", ACTION_LIGHT_HEAL), nullptr)));
triggers.push_back(new TriggerNode(
@@ -86,9 +83,8 @@ void MeleeShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"no fire totem",
NextAction::array(0, new NextAction("reach melee", 23.0f), new NextAction("magma totem", 22.0f), nullptr)));
triggers.push_back(new TriggerNode("fire elemental totem",
NextAction::array(0, new NextAction("reach melee", 33.0f),
new NextAction("fire elemental totem", 32.0f), nullptr)));
triggers.push_back(new TriggerNode(
"fire elemental totem", NextAction::array(0, new NextAction("fire elemental totem melee", 32.0f), nullptr)));
triggers.push_back(
new TriggerNode("no air totem", NextAction::array(0, new NextAction("windfury totem", 20.0f), nullptr)));

View File

@@ -17,7 +17,7 @@ bool CastTotemAction::isUseful()
{
return false;
}
float dps = AI_VALUE(float, "expected group dps");
float dps = AI_VALUE(float, "estimated group dps");
if (target->GetHealth() / dps < needLifeTime)
{
return false;
@@ -51,11 +51,14 @@ bool CastMagmaTotemAction::isUseful() {
}
bool CastFireNovaAction::isUseful() {
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
Creature* fireTotem = bot->GetMap()->GetCreature(bot->m_SummonSlot[1]);
if (!fireTotem)
return false;
if (bot->GetDistance(fireTotem) > 8.0f)
if (target->GetDistance(fireTotem) > 8.0f)
return false;
return CastMeleeSpellAction::isUseful();

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_SHAMANACTIONS_H
#include "GenericSpellActions.h"
#include "Playerbots.h"
#include "SharedDefines.h"
class PlayerbotAI;
@@ -426,6 +427,20 @@ public:
virtual bool isUseful() override { return CastTotemAction::isUseful(); }
};
class CastFireElementalTotemMeleeAction : public CastTotemAction
{
public:
CastFireElementalTotemMeleeAction(PlayerbotAI* ai) : CastTotemAction(ai, "fire elemental totem", "", 0.0f) {}
virtual std::string const GetTargetName() override { return "self target"; }
virtual bool isUseful() override
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target || !bot->IsWithinMeleeRange(target))
return false;
return CastTotemAction::isUseful();
}
};
class CastWrathOfAirTotemAction : public CastTotemAction
{
public:

View File

@@ -110,13 +110,17 @@ public:
creators["no water totem"] = &ShamanATriggerFactoryInternal::no_water_totem;
creators["no air totem"] = &ShamanATriggerFactoryInternal::no_air_totem;
creators["earth shield on main tank"] = &ShamanATriggerFactoryInternal::earth_shield_on_main_tank;
creators["maelstrom weapon"] = &ShamanATriggerFactoryInternal::maelstrom_weapon;
creators["maelstrom weapon 3"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_3;
creators["maelstrom weapon 4"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_4;
creators["maelstrom weapon 5"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_5;
creators["flame shock"] = &ShamanATriggerFactoryInternal::flame_shock;
creators["wrath of air totem"] = &ShamanATriggerFactoryInternal::wrath_of_air_totem;
}
private:
static Trigger* maelstrom_weapon(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI); }
static Trigger* maelstrom_weapon_3(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 3); }
static Trigger* maelstrom_weapon_4(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 4); }
static Trigger* maelstrom_weapon_5(PlayerbotAI* botAI) { return new MaelstromWeaponTrigger(botAI, 5); }
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); }
@@ -234,6 +238,7 @@ public:
creators["lava burst"] = &ShamanAiObjectContextInternal::lava_burst;
creators["earth shield on main tank"] = &ShamanAiObjectContextInternal::earth_shield_on_main_tank;
creators["fire elemental totem"] = &ShamanAiObjectContextInternal::fire_elemental_totem;
creators["fire elemental totem melee"] = &ShamanAiObjectContextInternal::fire_elemental_totem_melee;
creators["totem of wrath"] = &ShamanAiObjectContextInternal::totem_of_wrath;
creators["wrath of air totem"] = &ShamanAiObjectContextInternal::wrath_of_air_totem;
creators["shamanistic rage"] = &ShamanAiObjectContextInternal::shamanistic_rage;
@@ -314,6 +319,7 @@ private:
static Action* earth_shield_on_main_tank(PlayerbotAI* ai) { return new CastEarthShieldOnMainTankAction(ai); }
static Action* totem_of_wrath(PlayerbotAI* ai) { return new CastTotemOfWrathAction(ai); }
static Action* fire_elemental_totem(PlayerbotAI* ai) { return new CastFireElementalTotemAction(ai); }
static Action* fire_elemental_totem_melee(PlayerbotAI* ai) { return new CastFireElementalTotemMeleeAction(ai); }
static Action* wrath_of_air_totem(PlayerbotAI* ai) { return new CastWrathOfAirTotemAction(ai); }
static Action* shamanistic_rage(PlayerbotAI* ai) { return new CastShamanisticRageAction(ai); }
static Action* feral_spirit(PlayerbotAI* ai) { return new CastFeralSpiritAction(ai); }

View File

@@ -241,7 +241,7 @@ public:
class MaelstromWeaponTrigger : public HasAuraStackTrigger
{
public:
MaelstromWeaponTrigger(PlayerbotAI* botAI) : HasAuraStackTrigger(botAI, "maelstrom weapon", 5) {}
MaelstromWeaponTrigger(PlayerbotAI* botAI, int stack = 5) : HasAuraStackTrigger(botAI, "maelstrom weapon", stack) {}
};
class WindShearInterruptEnemyHealerSpellTrigger : public InterruptEnemyHealerTrigger

View File

@@ -13,20 +13,13 @@ void TotemsShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericShamanStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("fire elemental totem",
NextAction::array(0, new NextAction("fire elemental totem", 32.0f), nullptr)));
triggers.push_back(
new TriggerNode("no air totem", NextAction::array(0, new NextAction("wrath of air totem", 8.0f), NULL)));
new TriggerNode("no air totem", NextAction::array(0, new NextAction("wrath of air totem", 8.0f), nullptr)));
triggers.push_back(
new TriggerNode("no water totem", NextAction::array(0, new NextAction("mana spring totem", 7.0f),
new NextAction("healing stream totem", 6.0f), nullptr)));
triggers.push_back(
new TriggerNode("no fire totem", NextAction::array(0, new NextAction("flametongue totem", 7.0f),
new NextAction("searing totem", 6.0f), nullptr)));
triggers.push_back(new TriggerNode("strength of earth totem",
NextAction::array(0, new NextAction("strength of earth totem", 6.0f), NULL)));
NextAction::array(0, new NextAction("strength of earth totem", 6.0f), nullptr)));
}

View File

@@ -10,9 +10,11 @@
#include "BattlegroundWS.h"
#include "CreatureAI.h"
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
#include "TemporarySummon.h"
#include "ThreatMgr.h"
#include "Timer.h"
bool LowManaTrigger::IsActive()
@@ -64,7 +66,7 @@ bool PetAttackTrigger::IsActive()
bool HighManaTrigger::IsActive()
{
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < 65;
return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig->highMana;
}
bool AlmostFullManaTrigger::IsActive()
@@ -83,6 +85,19 @@ bool EnergyAvailable::IsActive() { return AI_VALUE2(uint8, "energy", "self targe
bool ComboPointsAvailableTrigger::IsActive() { return AI_VALUE2(uint8, "combo", "current target") >= amount; }
bool ComboPointsNotFullTrigger::IsActive() { return AI_VALUE2(uint8, "combo", "current target") < amount; }
bool TargetWithComboPointsLowerHealTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target || !target->IsAlive() || !target->IsInWorld())
{
return false;
}
return ComboPointsAvailableTrigger::IsActive() &&
(target->GetHealth() / AI_VALUE(float, "estimated group dps")) <= lifeTime;
}
bool LoseAggroTrigger::IsActive() { return !AI_VALUE2(bool, "has aggro", "current target"); }
bool HasAggroTrigger::IsActive() { return AI_VALUE2(bool, "has aggro", "current target"); }
@@ -137,7 +152,16 @@ bool OutNumberedTrigger::IsActive()
bool BuffTrigger::IsActive()
{
Unit* target = GetTarget();
return SpellTrigger::IsActive() && !botAI->HasAura(spell, target, false, checkIsOwner);
if (!target)
return false;
if (!SpellTrigger::IsActive())
return false;
Aura* aura = botAI->GetAura(spell, target, checkIsOwner, checkDuration);
if (!aura)
return true;
if (beforeDuration && aura->GetDuration() < beforeDuration)
return true;
return false;
}
Value<Unit*>* BuffOnPartyTrigger::GetTargetValue()
@@ -171,6 +195,22 @@ bool MyAttackerCountTrigger::IsActive()
return AI_VALUE2(bool, "combat", "self target") && AI_VALUE(uint8, "my attacker count") >= amount;
}
bool LowTankThreatTrigger::IsActive()
{
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
return false;
Unit* current_target = AI_VALUE(Unit*, "current target");
if (!current_target)
return false;
ThreatMgr& mgr = current_target->GetThreatMgr();
float threat = mgr.GetThreat(bot);
float tankThreat = mgr.GetThreat(mt);
return tankThreat == 0.0f || threat > tankThreat * 0.5f;
}
bool AoeTrigger::IsActive()
{
Unit* current_target = AI_VALUE(Unit*, "current target");
@@ -221,7 +261,7 @@ bool DebuffTrigger::IsActive()
{
return false;
}
return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "expected group dps")) >= needLifeTime;
return BuffTrigger::IsActive() && (target->GetHealth() / AI_VALUE(float, "estimated group dps")) >= needLifeTime;
}
bool DebuffOnBossTrigger::IsActive()

View File

@@ -10,6 +10,7 @@
#include "HealthTriggers.h"
#include "RangeTriggers.h"
#include "Trigger.h"
class PlayerbotAI;
class Unit;
@@ -113,6 +114,30 @@ public:
bool IsActive() override;
};
class TargetWithComboPointsLowerHealTrigger : public ComboPointsAvailableTrigger
{
public:
TargetWithComboPointsLowerHealTrigger(PlayerbotAI* ai, int32 combo_point = 5, float lifeTime = 8.0f)
: ComboPointsAvailableTrigger(ai, combo_point), lifeTime(lifeTime)
{
}
bool IsActive() override;
private:
float lifeTime;
};
class ComboPointsNotFullTrigger : public StatAvailable
{
public:
ComboPointsNotFullTrigger(PlayerbotAI* botAI, int32 amount = 5, std::string const name = "combo points not full")
: StatAvailable(botAI, amount, name)
{
}
bool IsActive() override;
};
class LoseAggroTrigger : public Trigger
{
public:
@@ -224,6 +249,13 @@ public:
MediumThreatTrigger(PlayerbotAI* botAI) : MyAttackerCountTrigger(botAI, 2) {}
};
class LowTankThreatTrigger : public Trigger
{
public:
LowTankThreatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "low tank threat") {}
bool IsActive() override;
};
class AoeTrigger : public AttackerCountTrigger
{
public:
@@ -276,10 +308,12 @@ public:
class BuffTrigger : public SpellTrigger
{
public:
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false)
BuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false, bool checkDuration = false, uint32 beforeDuration = 0)
: SpellTrigger(botAI, spell, checkInterval)
{
this->checkIsOwner = checkIsOwner;
this->checkDuration = checkDuration;
this->beforeDuration = beforeDuration;
}
public:
@@ -288,6 +322,8 @@ public:
protected:
bool checkIsOwner;
bool checkDuration;
uint32 beforeDuration;
};
class BuffOnPartyTrigger : public BuffTrigger
@@ -347,8 +383,8 @@ class DebuffTrigger : public BuffTrigger
{
public:
DebuffTrigger(PlayerbotAI* botAI, std::string const spell, int32 checkInterval = 1, bool checkIsOwner = false,
float needLifeTime = 8.0f)
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner), needLifeTime(needLifeTime)
float needLifeTime = 8.0f, uint32 beforeDuration = 0)
: BuffTrigger(botAI, spell, checkInterval, checkIsOwner, false, beforeDuration), needLifeTime(needLifeTime)
{
}

View File

@@ -64,6 +64,7 @@ public:
creators["light energy available"] = &TriggerContext::LightEnergyAvailable;
creators["medium energy available"] = &TriggerContext::MediumEnergyAvailable;
creators["high energy available"] = &TriggerContext::HighEnergyAvailable;
creators["almost full energy available"] = &TriggerContext::AlmostFullEnergyAvailable;
creators["loot available"] = &TriggerContext::LootAvailable;
creators["no attackers"] = &TriggerContext::NoAttackers;
@@ -96,8 +97,12 @@ public:
creators["combo points available"] = &TriggerContext::ComboPointsAvailable;
creators["combo points 3 available"] = &TriggerContext::ComboPoints3Available;
creators["target with combo points almost dead"] = &TriggerContext::target_with_combo_points_almost_dead;
creators["combo points not full"] = &TriggerContext::ComboPointsNotFull;
creators["combo points not full and high energy"] = &TriggerContext::ComboPointsNotFullAndHighEnergy;
creators["medium threat"] = &TriggerContext::MediumThreat;
creators["low tank threat"] = &TriggerContext::low_tank_threat;
creators["dead"] = &TriggerContext::Dead;
creators["corpse near"] = &TriggerContext::corpse_near;
@@ -279,6 +284,7 @@ private:
static Trigger* LightEnergyAvailable(PlayerbotAI* botAI) { return new LightEnergyAvailableTrigger(botAI); }
static Trigger* MediumEnergyAvailable(PlayerbotAI* botAI) { return new MediumEnergyAvailableTrigger(botAI); }
static Trigger* HighEnergyAvailable(PlayerbotAI* botAI) { return new HighEnergyAvailableTrigger(botAI); }
static Trigger* AlmostFullEnergyAvailable(PlayerbotAI* botAI) { return new EnergyAvailable(botAI, 90); }
static Trigger* LootAvailable(PlayerbotAI* botAI) { return new LootAvailableTrigger(botAI); }
static Trigger* NoAttackers(PlayerbotAI* botAI) { return new NoAttackersTrigger(botAI); }
static Trigger* TankAssist(PlayerbotAI* botAI) { return new TankAssistTrigger(botAI); }
@@ -309,7 +315,15 @@ private:
}
static Trigger* ComboPointsAvailable(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI); }
static Trigger* ComboPoints3Available(PlayerbotAI* botAI) { return new ComboPointsAvailableTrigger(botAI, 3); }
static Trigger* target_with_combo_points_almost_dead(PlayerbotAI* ai)
{
return new TargetWithComboPointsLowerHealTrigger(ai, 3, 3.0f);
}
static Trigger* ComboPointsNotFull(PlayerbotAI* botAI) { return new ComboPointsNotFullTrigger(botAI); }
static Trigger* ComboPointsNotFullAndHighEnergy(PlayerbotAI* botAI) { return new TwoTriggers(botAI, "combo points not full", "high energy available"); }
static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); }
static Trigger* low_tank_threat(PlayerbotAI* botAI) { return new LowTankThreatTrigger(botAI); }
// static Trigger* MediumThreat(PlayerbotAI* botAI) { return new MediumThreatTrigger(botAI); }
static Trigger* Dead(PlayerbotAI* botAI) { return new DeadTrigger(botAI); }
static Trigger* corpse_near(PlayerbotAI* botAI) { return new CorpseNearTrigger(botAI); }
static Trigger* PartyMemberDead(PlayerbotAI* botAI) { return new PartyMemberDeadTrigger(botAI); }

View File

@@ -145,6 +145,9 @@ Aura* AreaDebuffValue::Calculate()
{
continue;
}
// float radius = dynOwner->GetRadius();
// if (radius > 12.0f)
// continue;
return aura;
}
}

View File

@@ -46,14 +46,14 @@ WorldLocation ArrowFormation::GetLocationInternal()
float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX();
float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY();
float z = master->GetPositionZ();
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX();
y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY();
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}

View File

@@ -36,3 +36,36 @@ Unit* AttackerWithoutAuraTargetValue::Calculate()
return result;
}
Unit* MeleeAttackerWithoutAuraTargetValue::Calculate()
{
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
// Unit* target = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
uint32 max_health = 0;
Unit* result = nullptr;
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
continue;
if (!bot->IsWithinMeleeRange(unit))
continue;
if (checkArc && !bot->HasInArc(CAST_ANGLE_IN_FRONT, unit))
continue;
if (unit->GetHealth() < max_health)
{
continue;
}
if (!botAI->HasAura(qualifier, unit, false, true))
{
max_health = unit->GetHealth();
result = unit;
}
}
return result;
}

View File

@@ -28,7 +28,9 @@ protected:
class MeleeAttackerWithoutAuraTargetValue : public AttackerWithoutAuraTargetValue
{
public:
MeleeAttackerWithoutAuraTargetValue(PlayerbotAI* botAI) : AttackerWithoutAuraTargetValue(botAI, "melee") {}
MeleeAttackerWithoutAuraTargetValue(PlayerbotAI* botAI, bool checkArc = true) : AttackerWithoutAuraTargetValue(botAI, "melee"), checkArc(checkArc) {}
Unit* Calculate() override;
bool checkArc;
};
#endif

View File

@@ -50,6 +50,7 @@ public:
CasterFindTargetSmartStrategy(PlayerbotAI* botAI, float dps)
: FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000)
{
result = nullptr;
}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
@@ -86,13 +87,15 @@ public:
{
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
// [5-20] > (5-0] > (20-inf)
if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit))
// [5-30] > (5-0] > (20-inf)
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return true;
return new_level > old_level;
}
int32_t level = GetIntervalLevel(new_unit);
if (level % 10 == 2 || level % 10 == 1)
int32_t level = new_level;
if (level % 10 == 2 || level % 10 == 0)
{
return new_time < old_time;
}
@@ -116,15 +119,15 @@ public:
botAI->IsRanged(botAI->GetBot()) ? sPlayerbotAIConfig->spellDistance : sPlayerbotAIConfig->meleeDistance;
attackRange += 5.0f;
int level = dis < attackRange ? 10 : 0;
if (time >= 3 && time <= 20)
if (time >= 5 && time <= 30)
{
return level + 2;
}
if (time > 20)
if (time > 30)
{
return level + 1;
return level;
}
return level;
return level + 1;
}
protected:
@@ -176,12 +179,14 @@ public:
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
// [5-20] > (5-0] > (20-inf)
if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit))
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return true;
return new_level > old_level;
}
// attack enemy in range and with lowest health
int level = GetIntervalLevel(new_unit);
int level = new_level;
if (level == 10)
{
return new_time < old_time;
@@ -249,12 +254,14 @@ public:
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
// [5-20] > (5-0] > (20-inf)
if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit))
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return true;
return new_level > old_level;
}
// attack enemy in range and with lowest health
int level = GetIntervalLevel(new_unit);
int level = new_level;
Player* bot = botAI->GetBot();
if (level == 10)
{
@@ -291,7 +298,7 @@ Unit* DpsTargetValue::Calculate()
return rti;
// FindLeastHpTargetStrategy strategy(botAI);
float dps = AI_VALUE(float, "expected group dps");
float dps = AI_VALUE(float, "estimated group dps");
if (botAI->IsCaster(bot))
{
CasterFindTargetSmartStrategy strategy(botAI, dps);

View File

@@ -0,0 +1,145 @@
#include "EstimatedLifetimeValue.h"
#include "AiFactory.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "SharedDefines.h"
float EstimatedLifetimeValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target || !target->IsAlive())
{
return 0.0f;
}
float dps = AI_VALUE(float, "estimated group dps");
bool aoePenalty = AI_VALUE(uint8, "attacker count") >= 3;
if (aoePenalty)
dps *= 0.75;
float res = target->GetHealth() / dps;
// bot->Say(target->GetName() + " lifetime: " + std::to_string(res), LANG_UNIVERSAL);
return res;
}
float EstimatedGroupDpsValue::Calculate()
{
float totalDps;
std::vector<Player*> groupPlayer = {bot};
if (Group* group = bot->GetGroup())
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member == bot) // calculated
continue;
if (!member || !member->IsInWorld())
continue;
if (member->GetMapId() != bot->GetMapId())
continue;
if (member->GetExactDist(bot) > sPlayerbotAIConfig->sightDistance)
continue;
groupPlayer.push_back(member);
}
}
for (Player* player : groupPlayer)
{
float roleMultiplier;
if (botAI->IsTank(player))
roleMultiplier = 0.3f;
else if (botAI->IsHeal(player))
roleMultiplier = 0.1f;
else
roleMultiplier = 1.0f;
float basicDps = GetBasicDps(player->GetLevel());
float basicGs = GetBasicGs(player->GetLevel());
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(player, true, false, 12);
float gs_modifier = (float)mixedGearScore / basicGs;
// bonus for wotlk epic gear
if (mixedGearScore >= 300)
{
gs_modifier *= 1 + (mixedGearScore - 300) * 0.01;
}
if (gs_modifier < 0.75)
gs_modifier = 0.75;
if (gs_modifier > 4)
gs_modifier = 4;
totalDps += basicDps * roleMultiplier * gs_modifier;
}
// Group buff bonus
if (groupPlayer.size() >= 25)
totalDps *= 1.2;
else if (groupPlayer.size() >= 10)
totalDps *= 1.1;
else if (groupPlayer.size() >= 5)
totalDps *= 1.05;
return totalDps;
}
float EstimatedGroupDpsValue::GetBasicDps(uint32 level)
{
float basic_dps;
if (level <= 15)
{
basic_dps = 5 + level * 1;
}
else if (level <= 25)
{
basic_dps = 20 + (level - 15) * 2;
}
else if (level <= 45)
{
basic_dps = 40 + (level - 25) * 3;
}
else if (level <= 55)
{
basic_dps = 100 + (level - 45) * 20;
}
else if (level <= 60)
{
basic_dps = 300 + (level - 55) * 50;
}
else if (level <= 70)
{
basic_dps = 550 + (level - 60) * 65;
}
else
{
basic_dps = 1200 + (level - 70) * 200;
}
return basic_dps;
}
float EstimatedGroupDpsValue::GetBasicGs(uint32 level)
{
float basic_gs;
if (level <= 8)
{
basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_NORMAL);
}
else if (level <= 15)
{
basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_UNCOMMON);
}
else if (level <= 60)
{
basic_gs = PlayerbotFactory::CalcMixedGearScore(level + 5, ITEM_QUALITY_RARE);
}
else if (level <= 70)
{
basic_gs = PlayerbotFactory::CalcMixedGearScore(85 + (level - 60) * 3, ITEM_QUALITY_RARE);
}
else
{
basic_gs = PlayerbotFactory::CalcMixedGearScore(155 + (level - 70) * 4, ITEM_QUALITY_RARE);
}
return basic_gs;
}

View File

@@ -3,8 +3,8 @@
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_EXPECTEDLIFETIMEVALUE_H
#define _PLAYERBOT_EXPECTEDLIFETIMEVALUE_H
#ifndef _PLAYERBOT_EstimatedLifetimeValue_H
#define _PLAYERBOT_EstimatedLifetimeValue_H
#include "NamedObjectContext.h"
#include "PossibleTargetsValue.h"
@@ -15,22 +15,26 @@ class PlayerbotAI;
class Unit;
// [target health] / [expected group single target dps] = [expected lifetime]
class ExpectedLifetimeValue : public FloatCalculatedValue, public Qualified
class EstimatedLifetimeValue : public FloatCalculatedValue, public Qualified
{
public:
ExpectedLifetimeValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "expected lifetime") {}
EstimatedLifetimeValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "estimated lifetime") {}
public:
float Calculate() override;
};
class ExpectedGroupDpsValue : public FloatCalculatedValue
class EstimatedGroupDpsValue : public FloatCalculatedValue
{
public:
ExpectedGroupDpsValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "expected group dps", 20 * 1000) {}
EstimatedGroupDpsValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "estimated group dps", 20 * 1000) {}
public:
float Calculate() override;
protected:
float GetBasicDps(uint32 level);
float GetBasicGs(uint32 level);
};
#endif

View File

@@ -1,99 +0,0 @@
#include "ExpectedLifetimeValue.h"
#include "AiFactory.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "SharedDefines.h"
float ExpectedLifetimeValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target || !target->IsAlive())
{
return 0.0f;
}
float dps = AI_VALUE(float, "expected group dps");
float res = target->GetHealth() / dps;
// bot->Say(target->GetName() + " lifetime: " + std::to_string(res), LANG_UNIVERSAL);
return res;
}
float ExpectedGroupDpsValue::Calculate()
{
float dps_num;
Group* group = bot->GetGroup();
if (!group)
{
dps_num = 0.7;
}
else
{
dps_num = group->GetMembersCount() * 0.7;
}
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(bot, true, false, 12);
// efficiency record based on rare gear level, is there better calculation method?
// float dps_efficiency = 1;
float basic_dps;
int32 basic_gs;
int32 level = bot->GetLevel();
if (level <= 15)
{
basic_dps = 5 + level * 1;
}
else if (level <= 25)
{
basic_dps = 20 + (level - 15) * 2;
}
else if (level <= 40)
{
basic_dps = 40 + (level - 30) * 4;
}
else if (level <= 55)
{
basic_dps = 100 + (level - 45) * 20;
}
else if (level <= 60)
{
basic_dps = 300 + (level - 55) * 50;
}
else if (level <= 70)
{
basic_dps = 450 + (level - 60) * 40;
}
else
{
basic_dps = 750 + (level - 70) * 175;
}
if (level <= 8)
{
basic_gs = (level + 5) * 2;
}
else if (level <= 15)
{
basic_gs = (level + 5) * 3;
}
else if (level <= 60)
{
basic_gs = (level + 5) * 4;
}
else if (level <= 70)
{
basic_gs = (85 + (level - 60) * 3) * 4;
}
else
{
basic_gs = (155 + (level - 70) * 4) * 4;
}
float gap = mixedGearScore - basic_gs;
float gs_modifier = (float)mixedGearScore / basic_gs - 1;
gs_modifier = gs_modifier * 3 + 1;
if (gs_modifier < 0.75)
gs_modifier = 0.75;
if (gs_modifier > 4)
gs_modifier = 4;
return dps_num * basic_dps * gs_modifier;
}

View File

@@ -88,7 +88,7 @@ public:
float angle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range;
float y = master->GetPositionY() + sin(angle) * range;
float z = master->GetPositionZ();
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
@@ -138,15 +138,14 @@ public:
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ();
z = bot->GetMapHeight(x, y, z + 5.0f);
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
master->UpdateAllowedPositionZ(x, y, z);
}
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
// bot->GetPositionZ(), x, y, z);
@@ -155,15 +154,14 @@ public:
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ();
z = bot->GetMapHeight(x, y, z + 5.0f);
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
@@ -221,8 +219,8 @@ public:
{
x = target->GetPositionX() + cos(angle) * range;
y = target->GetPositionY() + sin(angle) * range;
z = target->GetPositionZ() + target->GetHoverHeight();
z = target->GetMapHeight(x, y, z);
z = target->GetPositionZ();
target->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
@@ -389,7 +387,7 @@ public:
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), minX, minY, z);
}
@@ -403,7 +401,7 @@ public:
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}

View File

@@ -166,7 +166,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
return ITEM_USAGE_NONE;
uint16 dest;
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, false);
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, true);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;

View File

@@ -77,7 +77,7 @@ GuidVector NearestTrapWithDamageValue::Calculate()
continue;
}
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (spellInfo->IsPositive())
if (!spellInfo || spellInfo->IsPositive())
{
continue;
}

View File

@@ -26,7 +26,7 @@
#include "DuelTargetValue.h"
#include "EnemyHealerTargetValue.h"
#include "EnemyPlayerValue.h"
#include "ExpectedLifetimeValue.h"
#include "EstimatedLifetimeValue.h"
#include "Formations.h"
#include "GrindTargetValue.h"
#include "GroupValues.h"
@@ -299,8 +299,8 @@ public:
creators["boss target"] = &ValueContext::boss_target;
creators["nearest triggers"] = &ValueContext::nearest_triggers;
creators["neglect threat"] = &ValueContext::neglect_threat;
creators["expected lifetime"] = &ValueContext::expected_lifetime;
creators["expected group dps"] = &ValueContext::expected_group_dps;
creators["estimated lifetime"] = &ValueContext::expected_lifetime;
creators["estimated 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;
@@ -538,8 +538,8 @@ private:
static UntypedValue* boss_target(PlayerbotAI* ai) { return new BossTargetValue(ai); }
static UntypedValue* nearest_triggers(PlayerbotAI* ai) { return new NearestTriggersValue(ai); }
static UntypedValue* neglect_threat(PlayerbotAI* ai) { return new NeglectThreatResetValue(ai); }
static UntypedValue* expected_lifetime(PlayerbotAI* ai) { return new ExpectedLifetimeValue(ai); }
static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(ai); }
static UntypedValue* expected_lifetime(PlayerbotAI* ai) { return new EstimatedLifetimeValue(ai); }
static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new EstimatedGroupDpsValue(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); }

View File

@@ -34,8 +34,9 @@ ArmsWarriorStrategy::ArmsWarriorStrategy(PlayerbotAI* botAI) : GenericWarriorStr
NextAction** ArmsWarriorStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("bladestorm", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
return NextAction::array(0, new NextAction("bladestorm", ACTION_DEFAULT + 0.2f),
new NextAction("mortal strike", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
}
void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -63,9 +64,11 @@ void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"victory rush", NextAction::array(0, new NextAction("victory rush", ACTION_INTERRUPT), nullptr)));
triggers.push_back(new TriggerNode(
"medium rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 10), nullptr)));
/*triggers.push_back(new TriggerNode("high rage available", NextAction::array(0, new NextAction("slam", ACTION_HIGH
* + 1), nullptr)));*/
"high rage available", NextAction::array(0, new NextAction("heroic strike", ACTION_HIGH + 10), nullptr)));
triggers.push_back(new TriggerNode("medium rage available",
NextAction::array(0, new NextAction("slam", ACTION_HIGH + 1),
new NextAction("thunder clap", ACTION_HIGH),
nullptr)));
triggers.push_back(
new TriggerNode("bloodrage", NextAction::array(0, new NextAction("bloodrage", ACTION_HIGH + 2), nullptr)));
triggers.push_back(
@@ -75,6 +78,6 @@ void ArmsWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"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)));
triggers.push_back(new TriggerNode("medium aoe",
NextAction::array(0, new NextAction("thunder clap", ACTION_HIGH + 2), nullptr)));
}