mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Fix: Shaman bots stuck spamming “Call of the Elements” / “set … totem” (totem rank detection & trigger loop) (#1659)
* Fix Issue #1648 Fix low level randombot shamans (~34) don't heal in dungeongroups, seems new "resto" strategy is broken #1648 * Final working fix, tested with all 3 shaman class specs. * Update asked by review * requested review changes * Minor corrections of the cpp file * boyscouting Lets try and leave the code cleaner behind as we find it when we can. * Oupsie fix ;) --------- Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com>
This commit is contained in:
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -45,7 +46,10 @@ bool CastCleansingTotemAction::isUseful()
|
||||
}
|
||||
|
||||
// Will only cast Stoneclaw Totem if low on health and not in a group
|
||||
bool CastStoneclawTotemAction::isUseful() { return !botAI->GetBot()->GetGroup(); }
|
||||
bool CastStoneclawTotemAction::isUseful()
|
||||
{
|
||||
return !bot->GetGroup();
|
||||
}
|
||||
|
||||
// Will only cast Lava Burst if Flame Shock is on the target
|
||||
bool CastLavaBurstAction::isUseful()
|
||||
@@ -54,10 +58,10 @@ bool CastLavaBurstAction::isUseful()
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
static const uint32 FLAME_SHOCK_IDS[] = {8050, 8052, 8053, 10447, 10448, 29228, 25457, 49232, 49233};
|
||||
static const uint32 FLAME_SHOCK_SPELL_IDS[] = {8050, 8052, 8053, 10447, 10448, 29228, 25457, 49232, 49233};
|
||||
|
||||
ObjectGuid botGuid = botAI->GetBot()->GetGUID();
|
||||
for (uint32 spellId : FLAME_SHOCK_IDS)
|
||||
ObjectGuid botGuid = bot->GetGUID();
|
||||
for (uint32 spellId : FLAME_SHOCK_SPELL_IDS)
|
||||
{
|
||||
if (target->HasAura(spellId, botGuid))
|
||||
return true;
|
||||
@@ -69,7 +73,6 @@ bool CastLavaBurstAction::isUseful()
|
||||
// There is no existing code for guardians casting spells in the AC/Playerbots repo.
|
||||
bool CastSpiritWalkAction::Execute(Event event)
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
constexpr uint32 SPIRIT_WOLF = 29264;
|
||||
constexpr uint32 SPIRIT_WALK_SPELL = 58875;
|
||||
|
||||
@@ -90,28 +93,30 @@ bool CastSpiritWalkAction::Execute(Event event)
|
||||
// Set Strategy Assigned Totems (Actions) - First, it checks
|
||||
// the highest-rank spell the bot knows for each totem type,
|
||||
// then adds it to the Call of the Elements bar.
|
||||
|
||||
bool SetTotemAction::Execute(Event event)
|
||||
{
|
||||
const size_t spellIdsCount = sizeof(totemSpellIds) / sizeof(uint32);
|
||||
if (spellIdsCount == 0)
|
||||
return false; // early return
|
||||
|
||||
uint32 totemSpell = 0;
|
||||
|
||||
// Iterate backwards to prioritize the highest-rank totem spell the bot knows
|
||||
for (size_t i = spellIdsCount; i-- > 0;)
|
||||
for (size_t i = 0; i < totemSpellIdsCount; ++i)
|
||||
{
|
||||
const uint32 spellId = totemSpellIds[i];
|
||||
if (bot->HasSpell(spellId))
|
||||
if (bot->HasSpell(totemSpellIds[i]))
|
||||
{
|
||||
totemSpell = spellId;
|
||||
totemSpell = totemSpellIds[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (totemSpell == 0)
|
||||
if (!totemSpell)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const ActionButton* button = bot->GetActionButton(actionButtonId))
|
||||
{
|
||||
if (button->GetType() == ACTION_BUTTON_SPELL && button->GetAction() == totemSpell)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bot->addActionButton(actionButtonId, totemSpell, ACTION_BUTTON_SPELL);
|
||||
return true;
|
||||
|
||||
@@ -531,12 +531,18 @@ public:
|
||||
class SetTotemAction : public Action
|
||||
{
|
||||
public:
|
||||
SetTotemAction(PlayerbotAI* botAI, std::string const totemName, const uint32 totemSpellIds[], int actionButtonId)
|
||||
: Action(botAI, "set " + totemName), totemSpellIds(totemSpellIds), actionButtonId(actionButtonId)
|
||||
{
|
||||
}
|
||||
// Template constructor: infers N (size of the id array) at compile time
|
||||
template <size_t N>
|
||||
SetTotemAction(PlayerbotAI* botAI, std::string const& totemName, const uint32 (&ids)[N], int actionButtonId)
|
||||
: Action(botAI, "set " + totemName)
|
||||
, totemSpellIds(ids)
|
||||
, totemSpellIdsCount(N)
|
||||
, actionButtonId(actionButtonId)
|
||||
{}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
uint32 const* totemSpellIds;
|
||||
size_t totemSpellIdsCount;
|
||||
int actionButtonId;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ bool MainHandWeaponNoImbueTrigger::IsActive()
|
||||
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,17 +28,19 @@ bool OffHandWeaponNoImbueTrigger::IsActive()
|
||||
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||
if (!itemForSpell)
|
||||
return false;
|
||||
|
||||
uint32 invType = itemForSpell->GetTemplate()->InventoryType;
|
||||
bool allowedType = (invType == INVTYPE_WEAPON) || (invType == INVTYPE_WEAPONOFFHAND);
|
||||
if (itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) ||
|
||||
!allowedType)
|
||||
if (itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) || !allowedType)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShockTrigger::IsActive()
|
||||
{
|
||||
return SpellTrigger::IsActive() && !botAI->HasAura("flame shock", GetTarget(), false, true) &&
|
||||
return SpellTrigger::IsActive() &&
|
||||
!botAI->HasAura("flame shock", GetTarget(), false, true) &&
|
||||
!botAI->HasAura("frost shock", GetTarget(), false, true);
|
||||
}
|
||||
|
||||
@@ -61,13 +64,20 @@ bool EarthShockExecuteTrigger::IsActive()
|
||||
|
||||
bool TotemTrigger::IsActive()
|
||||
{
|
||||
return AI_VALUE(uint8, "attacker count") >= attackerCount && !AI_VALUE2(bool, "has totem", name) &&
|
||||
return AI_VALUE(uint8, "attacker count") >= attackerCount &&
|
||||
!AI_VALUE2(bool, "has totem", name) &&
|
||||
!botAI->HasAura(name, bot);
|
||||
}
|
||||
|
||||
bool WaterWalkingTrigger::IsActive() { return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); }
|
||||
bool WaterWalkingTrigger::IsActive()
|
||||
{
|
||||
return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
|
||||
}
|
||||
|
||||
bool WaterBreathingTrigger::IsActive() { return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target"); }
|
||||
bool WaterBreathingTrigger::IsActive()
|
||||
{
|
||||
return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
|
||||
}
|
||||
|
||||
bool WaterWalkingOnPartyTrigger::IsActive()
|
||||
{
|
||||
@@ -92,14 +102,13 @@ bool ElementalMasteryTrigger::IsActive()
|
||||
// code exists in the AC/Playerbots repo for checking if a guardian's spell is on cooldown.
|
||||
bool SpiritWalkTrigger::IsActive()
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
constexpr uint32 SPIRIT_WOLF = 29264;
|
||||
constexpr uint32 SPIRIT_WALK = 58875;
|
||||
constexpr int COOLDOWN_SECONDS = 32;
|
||||
constexpr uint32 SPIRIT_WOLF = 29264u;
|
||||
constexpr uint32 SPIRIT_WALK_SPELL_ID = 58875u;
|
||||
constexpr int COOLDOWN_IN_SECONDS = 32;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
if ((now - lastSpiritWalkTime) < COOLDOWN_SECONDS)
|
||||
if ((now - lastSpiritWalkTime) < COOLDOWN_IN_SECONDS)
|
||||
return false;
|
||||
|
||||
for (Unit* unit : bot->m_Controlled)
|
||||
@@ -107,13 +116,14 @@ bool SpiritWalkTrigger::IsActive()
|
||||
Creature* wolf = dynamic_cast<Creature*>(unit);
|
||||
if (wolf && wolf->GetEntry() == SPIRIT_WOLF && wolf->IsAlive())
|
||||
{
|
||||
if (!bot->HasAura(SPIRIT_WALK))
|
||||
if (!bot->HasAura(SPIRIT_WALK_SPELL_ID))
|
||||
{
|
||||
lastSpiritWalkTime = now;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -127,8 +137,10 @@ bool CallOfTheElementsTrigger::IsActive()
|
||||
}
|
||||
|
||||
int emptyCount = 0;
|
||||
static const uint8 slots[] = {SUMMON_SLOT_TOTEM_EARTH, SUMMON_SLOT_TOTEM_FIRE, SUMMON_SLOT_TOTEM_WATER,
|
||||
SUMMON_SLOT_TOTEM_AIR};
|
||||
static const uint8 slots[] = {
|
||||
SUMMON_SLOT_TOTEM_EARTH, SUMMON_SLOT_TOTEM_FIRE,
|
||||
SUMMON_SLOT_TOTEM_WATER, SUMMON_SLOT_TOTEM_AIR
|
||||
};
|
||||
|
||||
for (uint8 slot : slots)
|
||||
{
|
||||
@@ -173,13 +185,11 @@ bool CallOfTheElementsTrigger::IsActive()
|
||||
// 5. Finally, if any totem summon slot is not empty, the trigger will fire.
|
||||
bool TotemicRecallTrigger::IsActive()
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
if (!bot->HasSpell(SPELL_TOTEMIC_RECALL))
|
||||
return false;
|
||||
|
||||
Map* map = bot->GetMap();
|
||||
if (map->IsDungeon())
|
||||
if (map && map->IsDungeon())
|
||||
{
|
||||
InstanceScript* instance = ((InstanceMap*)map)->GetInstanceScript();
|
||||
if (instance)
|
||||
@@ -200,8 +210,10 @@ bool TotemicRecallTrigger::IsActive()
|
||||
Player* member = ref->GetSource();
|
||||
if (!member)
|
||||
continue;
|
||||
|
||||
if (member->IsInCombat())
|
||||
return false;
|
||||
|
||||
Pet* pet = member->GetPet();
|
||||
if (pet && pet->IsInCombat())
|
||||
return false;
|
||||
@@ -214,7 +226,9 @@ bool TotemicRecallTrigger::IsActive()
|
||||
Creature* totem = bot->GetMap()->GetCreature(guid);
|
||||
uint32 currentSpell = 0;
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MANA_TIDE_TOTEM_COUNT; ++i)
|
||||
{
|
||||
@@ -229,7 +243,9 @@ bool TotemicRecallTrigger::IsActive()
|
||||
Creature* totem = bot->GetMap()->GetCreature(guid);
|
||||
uint32 currentSpell = 0;
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < FIRE_ELEMENTAL_TOTEM_COUNT; ++i)
|
||||
{
|
||||
@@ -245,8 +261,8 @@ bool TotemicRecallTrigger::IsActive()
|
||||
}
|
||||
|
||||
// Find the active totem strategy for this slot, and return the highest-rank spellId the bot knows for it
|
||||
static uint32 GetRequiredTotemSpellId(PlayerbotAI* ai, const char* strategies[], const uint32* spellList[],
|
||||
const size_t spellCounts[], size_t numStrategies)
|
||||
static uint32 GetRequiredTotemSpellId(PlayerbotAI* ai, const char* strategies[],
|
||||
const uint32* spellList[], const size_t spellCounts[], size_t numStrategies)
|
||||
{
|
||||
Player* bot = ai->GetBot();
|
||||
for (size_t i = 0; i < numStrategies; ++i)
|
||||
@@ -257,10 +273,13 @@ static uint32 GetRequiredTotemSpellId(PlayerbotAI* ai, const char* strategies[],
|
||||
for (size_t j = 0; j < spellCounts[i]; ++j)
|
||||
{
|
||||
if (bot->HasSpell(spellList[i][j]))
|
||||
{
|
||||
return spellList[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // No relevant strategy active, or bot doesn't know any rank
|
||||
}
|
||||
|
||||
@@ -270,9 +289,11 @@ static uint32 GetSummonedTotemSpellId(Player* bot, uint8 slot)
|
||||
ObjectGuid guid = bot->m_SummonSlot[slot];
|
||||
if (guid.IsEmpty())
|
||||
return 0;
|
||||
|
||||
Creature* totem = bot->GetMap()->GetCreature(guid);
|
||||
if (!totem)
|
||||
return 0;
|
||||
|
||||
return totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
|
||||
@@ -289,8 +310,10 @@ bool NoEarthTotemTrigger::IsActive()
|
||||
{
|
||||
totem = bot->GetMap()->GetCreature(guid);
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
}
|
||||
|
||||
// Define supported earth totem strategies for this slot:
|
||||
static const char* names[] = {"strength of earth", "stoneskin", "tremor", "earthbind"};
|
||||
@@ -302,8 +325,10 @@ bool NoEarthTotemTrigger::IsActive()
|
||||
|
||||
// EXCEPTION: If Stoneclaw Totem is out and in range, consider the slot "occupied" (do not fire the trigger)
|
||||
for (size_t i = 0; i < STONECLAW_TOTEM_COUNT; ++i)
|
||||
{
|
||||
if (currentSpell == STONECLAW_TOTEM[i] && totem && totem->GetDistance(bot) <= 30.0f)
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no relevant strategy, only care if the slot is empty or totem is too far away
|
||||
if (!requiredSpell)
|
||||
@@ -326,8 +351,10 @@ bool NoFireTotemTrigger::IsActive()
|
||||
{
|
||||
totem = bot->GetMap()->GetCreature(guid);
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
}
|
||||
|
||||
// Define supported fire totem strategies for this slot:
|
||||
static const char* names[] = {"searing", "magma", "flametongue", "wrath", "frost resistance"};
|
||||
@@ -340,8 +367,10 @@ bool NoFireTotemTrigger::IsActive()
|
||||
|
||||
// EXCEPTION: If Fire Elemental is out and in range, consider the slot "occupied" (do not fire the trigger)
|
||||
for (size_t i = 0; i < FIRE_ELEMENTAL_TOTEM_COUNT; ++i)
|
||||
{
|
||||
if (currentSpell == FIRE_ELEMENTAL_TOTEM[i] && totem && totem->GetDistance(bot) <= 30.0f)
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no relevant strategy, only care if the slot is empty or totem is too far away
|
||||
if (!requiredSpell)
|
||||
@@ -364,8 +393,10 @@ bool NoWaterTotemTrigger::IsActive()
|
||||
{
|
||||
totem = bot->GetMap()->GetCreature(guid);
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
}
|
||||
|
||||
// Define supported water totem strategies for this slot:
|
||||
static const char* names[] = {"healing stream", "mana spring", "cleansing", "fire resistance"};
|
||||
@@ -377,12 +408,16 @@ bool NoWaterTotemTrigger::IsActive()
|
||||
|
||||
// EXCEPTION: If Mana Tide is out and in range, consider the slot "occupied" (do not fire the trigger)
|
||||
for (size_t i = 0; i < MANA_TIDE_TOTEM_COUNT; ++i)
|
||||
{
|
||||
if (currentSpell == MANA_TIDE_TOTEM[i] && totem && totem->GetDistance(bot) <= 30.0f)
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no relevant strategy, only care if the slot is empty or totem is too far away
|
||||
if (!requiredSpell)
|
||||
{
|
||||
return guid.IsEmpty() || !totem || totem->GetDistance(bot) > 30.0f;
|
||||
}
|
||||
|
||||
// Fire if slot is empty or wrong totem or totem is too far away
|
||||
return !currentSpell || currentSpell != requiredSpell || !totem || totem->GetDistance(bot) > 30.0f;
|
||||
@@ -401,8 +436,10 @@ bool NoAirTotemTrigger::IsActive()
|
||||
{
|
||||
totem = bot->GetMap()->GetCreature(guid);
|
||||
if (totem)
|
||||
{
|
||||
currentSpell = totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
|
||||
}
|
||||
}
|
||||
|
||||
// Define supported air totem strategies for this slot:
|
||||
static const char* names[] = {"wrath of air", "windfury", "nature resistance", "grounding totem"};
|
||||
@@ -414,7 +451,9 @@ bool NoAirTotemTrigger::IsActive()
|
||||
|
||||
// If no relevant strategy, only care if the slot is empty or totem is too far away
|
||||
if (!requiredSpell)
|
||||
{
|
||||
return guid.IsEmpty() || !totem || totem->GetDistance(bot) > 30.0f;
|
||||
}
|
||||
|
||||
// Fire if slot is empty or wrong totem or totem is too far away
|
||||
return !currentSpell || currentSpell != requiredSpell || !totem || totem->GetDistance(bot) > 30.0f;
|
||||
@@ -422,30 +461,26 @@ bool NoAirTotemTrigger::IsActive()
|
||||
|
||||
bool SetTotemTrigger::IsActive()
|
||||
{
|
||||
if (!bot->HasSpell(SPELL_CALL_OF_THE_ELEMENTS))
|
||||
return false;
|
||||
uint32 highestKnownSpell = 0;
|
||||
for (size_t i = 0; i < totemSpellIdsCount; ++i)
|
||||
{
|
||||
const uint32 spellId = totemSpellIds[i];
|
||||
if (bot->HasSpell(spellId))
|
||||
{
|
||||
highestKnownSpell = spellId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bot->HasSpell(requiredSpellId))
|
||||
if (!highestKnownSpell)
|
||||
return false;
|
||||
|
||||
ActionButton const* button = bot->GetActionButton(actionButtonId);
|
||||
if (!button || button->GetType() != ACTION_BUTTON_SPELL || button->GetAction() == 0)
|
||||
return true;
|
||||
|
||||
const size_t totemSpellIdsCount = sizeof(totemSpellIds) / sizeof(uint32);
|
||||
if (totemSpellIdsCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = (int)totemSpellIdsCount - 1; i >= 0; --i)
|
||||
{
|
||||
const uint32 spellId = totemSpellIds[i];
|
||||
if (bot->HasSpell(spellId))
|
||||
{
|
||||
return button->GetAction() != spellId;
|
||||
}
|
||||
}
|
||||
if (button->GetAction() != highestKnownSpell)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -359,17 +359,22 @@ public:
|
||||
class SetTotemTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
SetTotemTrigger(PlayerbotAI* ai, std::string const spellName, const uint32 requiredSpellId,
|
||||
const uint32 totemSpellIds[], int actionButtonId)
|
||||
: Trigger(ai, "set " + spellName),
|
||||
requiredSpellId(requiredSpellId),
|
||||
totemSpellIds(totemSpellIds),
|
||||
actionButtonId(actionButtonId) {}
|
||||
// Template constructor: infers N (size of the id array) at compile time
|
||||
template <size_t N>
|
||||
SetTotemTrigger(PlayerbotAI* ai, std::string const& spellName, uint32 requiredSpellId,
|
||||
const uint32 (&ids)[N], int actionButtonId)
|
||||
: Trigger(ai, "set " + spellName)
|
||||
, requiredSpellId(requiredSpellId)
|
||||
, totemSpellIds(ids)
|
||||
, totemSpellIdsCount(N)
|
||||
, actionButtonId(actionButtonId)
|
||||
{}
|
||||
bool IsActive() override;
|
||||
|
||||
private:
|
||||
uint32 requiredSpellId;
|
||||
uint32 const* totemSpellIds;
|
||||
size_t totemSpellIdsCount;
|
||||
int actionButtonId;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user