[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)

This commit is contained in:
bashermens
2026-01-19 22:45:28 +01:00
committed by GitHub
parent fd07e02a8a
commit 41c53365ae
1119 changed files with 27 additions and 27 deletions

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ActiveSpellValue.h"
#include "Playerbots.h"
uint32 ActiveSpellValue::Calculate()
{
Player* bot = botAI->GetBot();
for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; ++type)
{
if (Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type))
{
if (spell->m_spellInfo)
{
return spell->m_spellInfo->Id;
}
}
}
return 0;
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ACTIVESPELLVALUE_H
#define _PLAYERBOT_ACTIVESPELLVALUE_H
#include "Value.h"
class PlayerbotAI;
class ActiveSpellValue : public CalculatedValue<uint32>
{
public:
ActiveSpellValue(PlayerbotAI* botAI, std::string const name = "active spell") : CalculatedValue<uint32>(botAI, name)
{
}
uint32 Calculate() override;
};
#endif

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AlwaysLootListValue.h"
#include "Playerbots.h"
std::string const AlwaysLootListValue::Save()
{
std::ostringstream out;
bool first = true;
for (std::set<uint32>::iterator i = value.begin(); i != value.end(); ++i)
{
if (!first)
out << ",";
else
first = false;
out << *i;
}
return out.str();
}
bool AlwaysLootListValue::Load(std::string const text)
{
value.clear();
std::vector<std::string> ss = split(text, ',');
for (std::vector<std::string>::iterator i = ss.begin(); i != ss.end(); ++i)
{
value.insert(atoi(i->c_str()));
}
return true;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ALWAYSLOOTLISTVALUE_H
#define _PLAYERBOT_ALWAYSLOOTLISTVALUE_H
#include "Value.h"
class PlayerbotAI;
class AlwaysLootListValue : public ManualSetValue<std::set<uint32>&>
{
public:
AlwaysLootListValue(PlayerbotAI* botAI, std::string const name = "always loot list")
: ManualSetValue<std::set<uint32>&>(botAI, list, name)
{
}
std::string const Save() override;
bool Load(std::string const value) override;
private:
std::set<uint32> list;
};
#endif

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AoeHealValues.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
uint8 AoeHealValue::Calculate()
{
Group* group = bot->GetGroup();
if (!group)
return 0;
float range = 0;
if (qualifier == "low")
range = sPlayerbotAIConfig->lowHealth;
else if (qualifier == "medium")
range = sPlayerbotAIConfig->mediumHealth;
else if (qualifier == "critical")
range = sPlayerbotAIConfig->criticalHealth;
else if (qualifier == "almost full")
range = sPlayerbotAIConfig->almostFullHealth;
uint8 count = 0;
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* player = ObjectAccessor::FindPlayer(itr->guid);
if (!player || !player->IsAlive())
continue;
if (player->GetDistance(bot) >= sPlayerbotAIConfig->sightDistance)
continue;
float percent = (static_cast<float>(player->GetHealth()) / player->GetMaxHealth()) * 100;
if (percent <= range)
++count;
}
return count;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_AOEHEALVALUES_H
#define _PLAYERBOT_AOEHEALVALUES_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class AoeHealValue : public Uint8CalculatedValue, public Qualified
{
public:
AoeHealValue(PlayerbotAI* botAI, std::string const name = "aoe heal") : Uint8CalculatedValue(botAI, name) {}
uint8 Calculate() override;
};
#endif

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AoeValues.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "SpellAuraEffects.h"
GuidVector FindMaxDensity(Player* bot)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
GuidVector units = *botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets");
std::map<ObjectGuid, GuidVector> groups;
uint32 maxCount = 0;
ObjectGuid maxGroup;
for (GuidVector::iterator i = units.begin(); i != units.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
for (GuidVector::iterator j = units.begin(); j != units.end(); ++j)
{
Unit* other = botAI->GetUnit(*j);
if (!other)
continue;
float d = sServerFacade->GetDistance2d(unit, other);
if (sServerFacade->IsDistanceLessOrEqualThan(d, sPlayerbotAIConfig->aoeRadius * 2))
groups[*i].push_back(*j);
}
if (maxCount < groups[*i].size())
{
maxCount = groups[*i].size();
maxGroup = *i;
}
}
if (!maxCount)
return GuidVector();
return groups[maxGroup];
}
WorldLocation AoePositionValue::Calculate()
{
GuidVector group = FindMaxDensity(bot);
if (group.empty())
return WorldLocation();
// Note: don't know where these values come from or even used.
float x1 = 0.f;
float y1 = 0.f;
float x2 = 0.f;
float y2 = 0.f;
for (GuidVector::iterator i = group.begin(); i != group.end(); ++i)
{
Unit* unit = GET_PLAYERBOT_AI(bot)->GetUnit(*i);
if (!unit)
continue;
if (i == group.begin() || x1 > unit->GetPositionX())
x1 = unit->GetPositionX();
if (i == group.begin() || x2 < unit->GetPositionX())
x2 = unit->GetPositionX();
if (i == group.begin() || y1 > unit->GetPositionY())
y1 = unit->GetPositionY();
if (i == group.begin() || y2 < unit->GetPositionY())
y2 = unit->GetPositionY();
}
float x = (x1 + x2) / 2;
float y = (y1 + y2) / 2;
float z = bot->GetPositionZ() + CONTACT_DISTANCE;
;
bot->UpdateAllowedPositionZ(x, y, z);
return WorldLocation(bot->GetMapId(), x, y, z, 0);
}
uint8 AoeCountValue::Calculate() { return FindMaxDensity(bot).size(); }
bool HasAreaDebuffValue::Calculate()
{
for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; auraType++)
{
Unit::AuraEffectList const& auras = botAI->GetBot()->GetAuraEffectsByType((AuraType)auraType);
if (auras.empty())
continue;
for (AuraEffect const* aurEff : auras)
{
SpellInfo const* proto = aurEff->GetSpellInfo();
if (!proto)
continue;
uint32 trigger_spell_id = proto->Effects[aurEff->GetEffIndex()].TriggerSpell;
if (trigger_spell_id == 29767) // Overload
{
return true;
}
else
{
return (!proto->IsPositive() && aurEff->IsPeriodic() && proto->HasAreaAuraEffect());
}
}
}
return false;
}
Aura* AreaDebuffValue::Calculate()
{
// Unit::AuraApplicationMap& map = bot->GetAppliedAuras();
Unit::AuraEffectList const& aurasPeriodicDamage = bot->GetAuraEffectsByType(SPELL_AURA_PERIODIC_DAMAGE);
Unit::AuraEffectList const& aurasPeriodicDamagePercent =
bot->GetAuraEffectsByType(SPELL_AURA_PERIODIC_DAMAGE_PERCENT);
Unit::AuraEffectList const& aurasPeriodicTriggerSpell =
bot->GetAuraEffectsByType(SPELL_AURA_PERIODIC_TRIGGER_SPELL);
Unit::AuraEffectList const& aurasPeriodicTriggerWithValueSpell =
bot->GetAuraEffectsByType(SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE);
Unit::AuraEffectList const& aurasDummy = bot->GetAuraEffectsByType(SPELL_AURA_DUMMY);
for (const Unit::AuraEffectList& list : {aurasPeriodicDamage, aurasPeriodicDamagePercent, aurasPeriodicTriggerSpell,
aurasPeriodicTriggerWithValueSpell, aurasDummy})
{
for (auto i = list.begin(); i != list.end(); ++i)
{
AuraEffect* aurEff = *i;
if (!aurEff)
continue;
Aura* aura = aurEff->GetBase();
if (!aura)
continue;
AuraObjectType type = aura->GetType();
bool isPositive = aura->GetSpellInfo()->IsPositive();
if (type == DYNOBJ_AURA_TYPE && !isPositive)
{
DynamicObject* dynOwner = aura->GetDynobjOwner();
if (!dynOwner)
{
continue;
}
// float radius = dynOwner->GetRadius();
// if (radius > 12.0f)
// continue;
return aura;
}
}
}
return nullptr;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_AOEVALUES_H
#define _PLAYERBOT_AOEVALUES_H
#include "AiObjectContext.h"
#include "GameObject.h"
#include "Object.h"
#include "Value.h"
class PlayerbotAI;
class AoePositionValue : public CalculatedValue<WorldLocation>
{
public:
AoePositionValue(PlayerbotAI* botAI) : CalculatedValue<WorldLocation>(botAI, "aoe position") {}
WorldLocation Calculate() override;
};
class AoeCountValue : public CalculatedValue<uint8>
{
public:
AoeCountValue(PlayerbotAI* botAI) : CalculatedValue<uint8>(botAI, "aoe count") {}
uint8 Calculate() override;
};
class HasAreaDebuffValue : public BoolCalculatedValue, public Qualified
{
public:
HasAreaDebuffValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI) {}
Unit* GetTarget()
{
AiObjectContext* ctx = AiObject::context;
return ctx->GetValue<Unit*>(qualifier)->Get();
}
virtual bool Calculate();
};
class AreaDebuffValue : public CalculatedValue<Aura*>
{
public:
AreaDebuffValue(PlayerbotAI* botAI) : CalculatedValue<Aura*>(botAI, "area debuff", 1) {}
Aura* Calculate() override;
};
#endif

175
src/Ai/Base/Value/Arrow.cpp Normal file
View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "Arrow.h"
#include "Map.h"
#include "Playerbots.h"
WorldLocation ArrowFormation::GetLocationInternal()
{
if (!bot->GetGroup())
return Formation::NullLocation;
Build();
uint32 tankLines = 1 + tanks.Size() / 6;
uint32 meleeLines = 1 + melee.Size() / 6;
uint32 rangedLines = 1 + ranged.Size() / 6;
uint32 healerLines = 1 + healers.Size() / 6;
float offset = 0.f;
Player* master = botAI->GetMaster();
if (!botAI->IsSafe(master))
return Formation::NullLocation;
float orientation = master->GetOrientation();
MultiLineUnitPlacer placer(orientation);
tanks.PlaceUnits(&placer);
tanks.Move(-cos(orientation) * offset, -sin(orientation) * offset);
offset += tankLines * sPlayerbotAIConfig->followDistance + sPlayerbotAIConfig->tooCloseDistance / 2;
melee.PlaceUnits(&placer);
melee.Move(-cos(orientation) * offset, -sin(orientation) * offset);
offset += meleeLines * sPlayerbotAIConfig->followDistance + sPlayerbotAIConfig->tooCloseDistance / 2;
ranged.PlaceUnits(&placer);
ranged.Move(-cos(orientation) * offset, -sin(orientation) * offset);
offset += rangedLines * sPlayerbotAIConfig->followDistance;
healers.PlaceUnits(&placer);
healers.Move(-cos(orientation) * offset, -sin(orientation) * offset);
if (!masterUnit || !botUnit)
return Formation::NullLocation;
float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX();
float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY();
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();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
void ArrowFormation::Build()
{
if (built)
return;
FillSlotsExceptMaster();
AddMasterToSlot();
built = true;
}
FormationSlot* ArrowFormation::FindSlot(Player* member)
{
if (botAI->IsTank(member))
return &tanks;
else if (botAI->IsHeal(member))
return &healers;
else if (botAI->IsRanged(member))
return &ranged;
else
return &melee;
}
void ArrowFormation::FillSlotsExceptMaster()
{
Group* group = bot->GetGroup();
GroupReference* gref = group->GetFirstMember();
uint32 index = 0;
while (gref)
{
Player* member = gref->GetSource();
if (botAI->IsSafe(member))
{
if (member == bot)
FindSlot(member)->AddLast(botUnit = new FormationUnit(index, false));
else if (member != botAI->GetMaster())
FindSlot(member)->AddLast(new FormationUnit(index, false));
++index;
}
gref = gref->next();
}
}
void ArrowFormation::AddMasterToSlot()
{
Group* group = bot->GetGroup();
GroupReference* gref = group->GetFirstMember();
uint32 index = 0;
while (gref)
{
Player* member = gref->GetSource();
if (member == botAI->GetMaster())
{
FindSlot(member)->InsertAtCenter(masterUnit = new FormationUnit(index, true));
break;
}
gref = gref->next();
++index;
}
}
void FormationSlot::PlaceUnits(UnitPlacer* placer)
{
uint32 index = 0;
uint32 count = units.size();
for (FormationUnit* unit : units)
{
unit->SetLocation(placer->Place(unit, index, count));
++index;
}
}
UnitPosition MultiLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint32 count)
{
SingleLineUnitPlacer placer(orientation);
if (count <= 6)
return placer.Place(unit, index, count);
uint32 lineNo = index / 6;
uint32 indexInLine = index % 6;
uint32 lineSize = std::max(count - lineNo * 6, uint32(6));
float x = cos(orientation) * sPlayerbotAIConfig->followDistance * lineNo;
float y = sin(orientation) * sPlayerbotAIConfig->followDistance * lineNo;
return placer.Place(unit, indexInLine, lineSize);
}
UnitPosition SingleLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint32 count)
{
float angle = orientation - M_PI / 2.0f;
float x = cos(angle) * sPlayerbotAIConfig->followDistance * ((float)index - (float)count / 2);
float y = sin(angle) * sPlayerbotAIConfig->followDistance * ((float)index - (float)count / 2);
return UnitPosition(x, y);
}
void FormationSlot::Move(float dx, float dy)
{
for (FormationUnit* unit : units)
{
unit->SetLocation(unit->GetX() + dx, unit->GetY() + dy);
}
}
FormationSlot::~FormationSlot()
{
for (FormationUnit* unit : units)
{
delete unit;
}
units.clear();
}

127
src/Ai/Base/Value/Arrow.h Normal file
View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ARROW_H
#define _PLAYERBOT_ARROW_H
#include "Formations.h"
#include "TravelMgr.h"
class Player;
class PlayerbotAI;
class UnitPosition
{
public:
UnitPosition(float x, float y) : x(x), y(y) {}
UnitPosition(UnitPosition const& other)
{
x = other.x;
y = other.y;
}
float x, y;
};
class FormationUnit
{
public:
FormationUnit(uint32 groupIndex, bool master) : groupIndex(groupIndex), master(master), position(0, 0) {}
FormationUnit(FormationUnit const& other) : position(other.position.x, other.position.y)
{
groupIndex = other.groupIndex;
master = other.master;
}
uint32 GetGroupIdex() { return groupIndex; }
void SetLocation(UnitPosition pos) { position = pos; }
void SetLocation(float x, float y)
{
position.x = x;
position.y = y;
}
float GetX() { return position.x; }
float GetY() { return position.y; }
private:
uint32 groupIndex;
bool master;
UnitPosition position;
};
class UnitPlacer
{
public:
UnitPlacer() {}
virtual UnitPosition Place(FormationUnit* unit, uint32 index, uint32 count) = 0;
};
class FormationSlot
{
public:
FormationSlot() {}
virtual ~FormationSlot();
void AddLast(FormationUnit* unit) { units.push_back(unit); }
void InsertAtCenter(FormationUnit* unit) { units.insert(units.begin() + (units.size() + 1) / 2, unit); }
void PlaceUnits(UnitPlacer* placer);
void Move(float dx, float dy);
uint32 Size() const { return units.size(); }
private:
WorldLocation center;
std::vector<FormationUnit*> units;
};
class MultiLineUnitPlacer : public UnitPlacer
{
public:
MultiLineUnitPlacer(float orientation) : UnitPlacer(), orientation(orientation) {}
UnitPosition Place(FormationUnit* unit, uint32 index, uint32 count) override;
private:
float orientation;
};
class SingleLineUnitPlacer
{
public:
SingleLineUnitPlacer(float orientation) : orientation(orientation) {}
virtual UnitPosition Place(FormationUnit* unit, uint32 index, uint32 count);
private:
float orientation;
};
class ArrowFormation : public MoveAheadFormation
{
public:
ArrowFormation(PlayerbotAI* botAI)
: MoveAheadFormation(botAI, "arrow"), built(false), masterUnit(nullptr), botUnit(nullptr)
{
}
WorldLocation GetLocationInternal() override;
private:
void Build();
void FillSlotsExceptMaster();
void AddMasterToSlot();
FormationSlot* FindSlot(Player* member);
private:
FormationSlot tanks;
FormationSlot melee;
FormationSlot ranged;
FormationSlot healers;
FormationUnit* masterUnit;
FormationUnit* botUnit;
bool built;
};
#endif

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackerCountValues.h"
#include "Playerbots.h"
#include "SharedDefines.h"
uint8 MyAttackerCountValue::Calculate() { return bot->getAttackers().size(); }
bool HasAggroValue::Calculate()
{
Unit* target = GetTarget();
if (!target)
{
return true;
}
Unit* victim = target->GetVictim();
if (!victim)
{
return true;
}
bool isMT = botAI->IsMainTank(bot);
if (victim &&
(victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && botAI->IsTank(victim->ToPlayer()))))
{
return true;
}
return false;
}
uint8 AttackerCountValue::Calculate()
{
uint32 count = 0;
float range = sPlayerbotAIConfig->sightDistance;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
continue;
float distance = bot->GetDistance(unit);
if (distance <= range)
++count;
}
return count;
}
uint8 BalancePercentValue::Calculate()
{
float playerLevel = 0, attackerLevel = 0;
if (Group* group = bot->GetGroup())
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* player = ObjectAccessor::FindPlayer(itr->guid);
if (!player || !player->IsAlive())
continue;
playerLevel += player->GetLevel();
}
uint32 memberCount = group->GetMembersCount();
playerLevel /= memberCount;
if (memberCount <= 10)
playerLevel *= memberCount;
else
playerLevel *= 10;
}
GuidVector v = context->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : v)
{
Creature* creature = botAI->GetCreature((guid));
if (!creature || !creature->IsAlive())
continue;
uint32 level = creature->GetLevel();
switch (creature->GetCreatureTemplate()->rank)
{
case CREATURE_ELITE_RARE:
level *= 2;
break;
case CREATURE_ELITE_ELITE:
level *= 3;
break;
case CREATURE_ELITE_RAREELITE:
level *= 3;
break;
case CREATURE_ELITE_WORLDBOSS:
level *= 20;
break;
}
attackerLevel += level;
}
if (!attackerLevel)
return 100;
float percent = playerLevel * 100 / attackerLevel;
return percent <= 200 ? (uint8)percent : 200;
}
Unit* AttackerCountValue::GetTarget()
{
AiObjectContext* ctx = AiObject::context;
return ctx->GetValue<Unit*>(qualifier)->Get();
}
Unit* MyAttackerCountValue::GetTarget()
{
AiObjectContext* ctx = AiObject::context;
return ctx->GetValue<Unit*>(qualifier)->Get();
}
Unit* HasAggroValue::GetTarget()
{
AiObjectContext* ctx = AiObject::context;
return ctx->GetValue<Unit*>(qualifier)->Get();
}
Unit* BalancePercentValue::GetTarget()
{
AiObjectContext* ctx = AiObject::context;
return ctx->GetValue<Unit*>(qualifier)->Get();
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ATTACKERCOUNTVALUES_H
#define _PLAYERBOT_ATTACKERCOUNTVALUES_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class Unit;
class AttackerCountValue : public Uint8CalculatedValue, public Qualified
{
public:
AttackerCountValue(PlayerbotAI* botAI, std::string const name = "attackers count")
: Uint8CalculatedValue(botAI, name)
{
}
Unit* GetTarget();
uint8 Calculate() override;
};
class MyAttackerCountValue : public Uint8CalculatedValue, public Qualified
{
public:
MyAttackerCountValue(PlayerbotAI* botAI, std::string const name = "my attackers count")
: Uint8CalculatedValue(botAI, name)
{
}
Unit* GetTarget();
uint8 Calculate() override;
};
class HasAggroValue : public BoolCalculatedValue, public Qualified
{
public:
HasAggroValue(PlayerbotAI* botAI, std::string const name = "has aggro") : BoolCalculatedValue(botAI, name) {}
Unit* GetTarget();
bool Calculate() override;
};
class BalancePercentValue : public Uint8CalculatedValue, public Qualified
{
public:
BalancePercentValue(PlayerbotAI* botAI, std::string const name = "balance percentage")
: Uint8CalculatedValue(botAI, name)
{
}
Unit* GetTarget();
uint8 Calculate() override;
};
#endif

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackerWithoutAuraTargetValue.h"
#include "Playerbots.h"
Unit* AttackerWithoutAuraTargetValue::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->IsWithinCombatRange(unit, botAI->GetRange(range)))
continue;
if (unit->GetHealth() < max_health)
{
continue;
}
if (!botAI->HasAura(qualifier, unit, false, true))
{
max_health = unit->GetHealth();
result = unit;
}
}
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

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ATTACKERWITHOUTAURATARGETVALUE_H
#define _PLAYERBOT_ATTACKERWITHOUTAURATARGETVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class Unit;
class AttackerWithoutAuraTargetValue : public UnitCalculatedValue, public Qualified
{
public:
AttackerWithoutAuraTargetValue(PlayerbotAI* botAI, std::string range = "spell")
: UnitCalculatedValue(botAI, "attacker without aura"), range(range)
{
}
protected:
Unit* Calculate() override;
std::string range;
};
class MeleeAttackerWithoutAuraTargetValue : public AttackerWithoutAuraTargetValue
{
public:
MeleeAttackerWithoutAuraTargetValue(PlayerbotAI* botAI, bool checkArc = true) : AttackerWithoutAuraTargetValue(botAI, "melee"), checkArc(checkArc) {}
Unit* Calculate() override;
bool checkArc;
};
#endif

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackersValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
#include "ReputationMgr.h"
#include "ServerFacade.h"
GuidVector AttackersValue::Calculate()
{
std::unordered_set<Unit*> targets;
GuidVector result;
if (!botAI->AllowActivity(ALL_ACTIVITY))
return result;
AddAttackersOf(bot, targets);
if (Group* group = bot->GetGroup())
AddAttackersOf(group, targets);
RemoveNonThreating(targets);
// prioritized target
GuidVector prioritizedTargets = AI_VALUE(GuidVector, "prioritized targets");
for (ObjectGuid target : prioritizedTargets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && IsValidTarget(unit, bot))
targets.insert(unit);
}
if (Group* group = bot->GetGroup())
{
ObjectGuid skullGuid = group->GetTargetIcon(7);
Unit* skullTarget = botAI->GetUnit(skullGuid);
if (skullTarget && IsValidTarget(skullTarget, bot))
targets.insert(skullTarget);
}
for (Unit* unit : targets)
result.push_back(unit->GetGUID());
if (bot->duel && bot->duel->Opponent)
result.push_back(bot->duel->Opponent->GetGUID());
// workaround for bots of same faction not fighting in arena
if (bot->InArena())
{
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible targets");
for (ObjectGuid const guid : possibleTargets)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
result.push_back(unit->GetGUID());
}
}
return result;
}
void AttackersValue::AddAttackersOf(Group* group, std::unordered_set<Unit*>& targets)
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member == bot || member->GetMapId() != bot->GetMapId() ||
sServerFacade->GetDistance2d(bot, member) > sPlayerbotAIConfig->sightDistance)
continue;
AddAttackersOf(member, targets);
}
}
struct AddGuardiansHelper
{
explicit AddGuardiansHelper(std::vector<Unit*>& units) : units(units) {}
void operator()(Unit* target) const { units.push_back(target); }
std::vector<Unit*>& units;
};
void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& targets)
{
if (!player || !player->IsInWorld() || player->IsBeingTeleported())
return;
HostileRefMgr& refManager = player->getHostileRefMgr();
HostileReference* ref = refManager.getFirst();
if (!ref)
return;
while (ref)
{
ThreatMgr* threatMgr = ref->GetSource();
Unit* attacker = threatMgr->GetOwner();
if (player->IsValidAttackTarget(attacker) &&
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
targets.insert(attacker);
ref = ref->next();
}
}
void AttackersValue::RemoveNonThreating(std::unordered_set<Unit*>& targets)
{
for (std::unordered_set<Unit*>::iterator tIter = targets.begin(); tIter != targets.end();)
{
Unit* unit = *tIter;
if (bot->GetMapId() != unit->GetMapId() || !hasRealThreat(unit) || !IsValidTarget(unit, bot))
{
std::unordered_set<Unit*>::iterator tIter2 = tIter;
++tIter;
targets.erase(tIter2);
}
else
++tIter;
}
}
bool AttackersValue::hasRealThreat(Unit* attacker)
{
return attacker && attacker->IsInWorld() && attacker->IsAlive() && !attacker->IsPolymorphed() &&
// !attacker->isInRoots() &&
!attacker->IsFriendlyTo(bot);
(attacker->GetThreatMgr().getCurrentVictim() || dynamic_cast<Player*>(attacker));
}
bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range*/)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return false;
// Basic check
if (!attacker)
return false;
// bool inCannon = botAI->IsInVehicle(false, true);
// bool enemy = botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target")->Get();
// Validity checks
if (!attacker->IsVisible() || !attacker->IsInWorld() || attacker->GetMapId() != bot->GetMapId())
return false;
if (attacker->isDead() || attacker->HasSpiritOfRedemptionAura())
return false;
// Flag checks
if (attacker->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2))
return false;
if (attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
return false;
// Relationship checks
if (attacker->IsFriendlyTo(bot))
return false;
// Critter exception
if (attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat())
return false;
// Visibility check
if (!bot->CanSeeOrDetect(attacker))
return false;
// PvP prohibition checks (skip for duels)
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
(!bot->duel || bot->duel->Opponent != attacker) &&
(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())))
{
// This will stop aggresive pets from starting an attack.
// This will stop currently attacking pets from continuing their attack.
// This will first require the bot to change from a combat strat. It will
// not be reached if the bot only switches targets, including NPC targets.
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin();
itr != bot->m_Controlled.end(); ++itr)
{
Creature* creature = dynamic_cast<Creature*>(*itr);
if (creature && creature->GetVictim() == attacker)
{
creature->AttackStop();
if (CharmInfo* charmInfo = creature->GetCharmInfo())
charmInfo->SetIsCommandAttack(false);
}
}
return false;
}
// Unflagged player check
if (attacker->IsPlayer() && !attacker->IsPvP() && !attacker->IsFFAPvP() &&
(!bot->duel || bot->duel->Opponent != attacker))
return false;
// Creature-specific checks
Creature* c = attacker->ToCreature();
if (c)
{
if (c->IsInEvadeMode())
return false;
bool leaderHasThreat = false;
if (bot->GetGroup() && botAI->GetMaster())
leaderHasThreat = attacker->GetThreatMgr().GetThreat(botAI->GetMaster());
bool isMemberBotGroup = false;
if (bot->GetGroup() && botAI->GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(botAI->GetMaster());
if (masterBotAI && !masterBotAI->IsRealPlayer())
isMemberBotGroup = true;
}
bool canAttack = (!isMemberBotGroup && botAI->HasStrategy("attack tagged", BOT_STATE_NON_COMBAT)) ||
leaderHasThreat ||
(!c->hasLootRecipient() &&
(!c->GetVictim() ||
(c->GetVictim() &&
((!c->GetVictim()->IsPlayer() || bot->IsInSameGroupWith(c->GetVictim()->ToPlayer())) ||
(botAI->GetMaster() && c->GetVictim() == botAI->GetMaster()))))) ||
c->isTappedBy(bot);
if (!canAttack)
return false;
}
return true;
}
bool AttackersValue::IsValidTarget(Unit* attacker, Player* bot)
{
return IsPossibleTarget(attacker, bot) && bot->IsWithinLOSInMap(attacker);
// (attacker->GetThreatMgr().getCurrentVictim() || attacker->GetGuidValue(UNIT_FIELD_TARGET) ||
// attacker->GetGUID().IsPlayer() || attacker->GetGUID() ==
// GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<ObjectGuid>("pull target")->Get());
}
bool PossibleAddsValue::Calculate()
{
GuidVector possible = botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los")->Get();
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : possible)
{
if (find(attackers.begin(), attackers.end(), guid) != attackers.end())
continue;
if (Unit* add = botAI->GetUnit(guid))
{
if (!add->GetTarget() && !add->GetThreatMgr().getCurrentVictim() && add->IsHostileTo(bot))
{
for (ObjectGuid const attackerGUID : attackers)
{
Unit* attacker = botAI->GetUnit(attackerGUID);
if (!attacker)
continue;
float dist = sServerFacade->GetDistance2d(attacker, add);
if (sServerFacade->IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig->aoeRadius * 1.5f))
continue;
if (sServerFacade->IsDistanceLessOrEqualThan(dist, sPlayerbotAIConfig->aggroDistance))
return true;
}
}
}
}
return false;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ATTACKERSVALUE_H
#define _PLAYERBOT_ATTACKERSVALUE_H
#include "PlayerbotAIConfig.h"
#include "Value.h"
class Group;
class Player;
class PlayerbotAI;
class Unit;
class AttackersValue : public ObjectGuidListCalculatedValue
{
public:
AttackersValue(PlayerbotAI* botAI) : ObjectGuidListCalculatedValue(botAI, "attackers", 1 * 1000) {}
GuidVector Calculate();
static bool IsPossibleTarget(Unit* attacker, Player* bot, float range = sPlayerbotAIConfig->sightDistance);
static bool IsValidTarget(Unit* attacker, Player* bot);
private:
void AddAttackersOf(Group* group, std::unordered_set<Unit*>& targets);
void AddAttackersOf(Player* player, std::unordered_set<Unit*>& targets);
void RemoveNonThreating(std::unordered_set<Unit*>& targets);
bool hasRealThreat(Unit* attacker);
};
class PossibleAddsValue : public BoolCalculatedValue
{
public:
PossibleAddsValue(PlayerbotAI* botAI, std::string const name = "possible adds") : BoolCalculatedValue(botAI, name)
{
}
bool Calculate() override;
};
class PrioritizedTargetsValue : public ManualSetValue<GuidVector>
{
public:
PrioritizedTargetsValue(PlayerbotAI* botAI, std::string const name = "prioritized targets")
: ManualSetValue(botAI, GuidVector(), name)
{
}
};
#endif

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AvailableLootValue.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
#include "ServerFacade.h"
AvailableLootValue::AvailableLootValue(PlayerbotAI* botAI, std::string const name)
: ManualSetValue<LootObjectStack*>(botAI, nullptr, name)
{
value = new LootObjectStack(botAI->GetBot());
}
AvailableLootValue::~AvailableLootValue() { delete value; }
LootTargetValue::LootTargetValue(PlayerbotAI* botAI, std::string const name)
: ManualSetValue<LootObject>(botAI, LootObject(), name)
{
}
bool CanLootValue::Calculate()
{
LootObject loot = AI_VALUE(LootObject, "loot target");
return !loot.IsEmpty() && loot.GetWorldObject(bot) && loot.IsLootPossible(bot) &&
sServerFacade->IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", "loot target"), INTERACTION_DISTANCE - 2);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_AVAILABLELOOTVALUE_H
#define _PLAYERBOT_AVAILABLELOOTVALUE_H
#include "LootObjectStack.h"
#include "Value.h"
class PlayerbotAI;
class AvailableLootValue : public ManualSetValue<LootObjectStack*>
{
public:
AvailableLootValue(PlayerbotAI* botAI, std::string const name = "available loot");
virtual ~AvailableLootValue();
};
class LootTargetValue : public ManualSetValue<LootObject>
{
public:
LootTargetValue(PlayerbotAI* botAI, std::string const name = "loot target");
};
class CanLootValue : public BoolCalculatedValue
{
public:
CanLootValue(PlayerbotAI* botAI, std::string const name = "can loot") : BoolCalculatedValue(botAI, name) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,234 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "BudgetValues.h"
#include "Playerbots.h"
uint32 MaxGearRepairCostValue::Calculate()
{
uint32 TotalCost = 0;
for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
uint16 pos = ((INVENTORY_SLOT_BAG_0 << 8) | i);
Item* item = bot->GetItemByPos(pos);
if (!item)
continue;
uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
if (!maxDurability)
continue;
uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY);
if (i >= EQUIPMENT_SLOT_END && curDurability >= maxDurability) // Only count items equiped or already damanged.
continue;
ItemTemplate const* ditemProto = item->GetTemplate();
DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel);
if (!dcost)
continue;
uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2;
DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId);
if (!dQualitymodEntry)
continue;
uint32 dmultiplier =
dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)];
uint32 costs = uint32(maxDurability * dmultiplier * double(dQualitymodEntry->quality_mod));
TotalCost += costs;
}
return TotalCost;
}
uint32 RepairCostValue::Calculate()
{
uint32 TotalCost = 0;
for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i)
{
uint16 pos = ((INVENTORY_SLOT_BAG_0 << 8) | i);
Item* item = bot->GetItemByPos(pos);
if (!item)
continue;
uint32 maxDurability = item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY);
if (!maxDurability)
continue;
uint32 curDurability = item->GetUInt32Value(ITEM_FIELD_DURABILITY);
uint32 LostDurability = maxDurability - curDurability;
if (LostDurability == 0)
continue;
ItemTemplate const* ditemProto = item->GetTemplate();
DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(ditemProto->ItemLevel);
if (!dcost)
continue;
uint32 dQualitymodEntryId = (ditemProto->Quality + 1) * 2;
DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId);
if (!dQualitymodEntry)
continue;
uint32 dmultiplier =
dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)];
uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod));
TotalCost += costs;
}
return TotalCost;
}
uint32 TrainCostValue::Calculate()
{
uint32 TotalCost = 0;
std::set<uint32> spells;
if (CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates())
{
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
if (!trainer)
continue;
if (trainer->GetTrainerType() != Trainer::Type::Class || !trainer->IsTrainerValidForPlayer(bot))
continue;
for (auto& spell : trainer->GetSpells())
{
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
if (spells.find(spell.SpellId) != spells.end())
continue;
TotalCost += spell.MoneyCost;
spells.insert(spell.SpellId);
}
}
}
return TotalCost;
}
uint32 MoneyNeededForValue::Calculate()
{
NeedMoneyFor needMoneyFor = NeedMoneyFor(stoi(getQualifier()));
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
AiObjectContext* context = botAI->GetAiObjectContext();
uint32 moneyWanted = 0;
uint32 level = bot->GetLevel();
switch (needMoneyFor)
{
case NeedMoneyFor::none:
moneyWanted = 0;
break;
case NeedMoneyFor::repair:
moneyWanted = AI_VALUE(uint32, "max repair cost");
break;
case NeedMoneyFor::ammo:
moneyWanted = (bot->getClass() == CLASS_HUNTER)
? (level * level * level) / 10
: 0; // Or level^3 (1s @ lvl10, 30s @ lvl30, 2g @ lvl60, 5g @ lvl80): Todo replace
// (should be best ammo buyable x 8 stacks cost)
break;
case NeedMoneyFor::spells:
moneyWanted = AI_VALUE(uint32, "train cost");
break;
case NeedMoneyFor::travel:
moneyWanted =
bot->isTaxiCheater()
? 0
: 1500; // 15s for traveling half a continent. Todo: Add better calculation (Should be ???)
break;
case NeedMoneyFor::gear:
moneyWanted = level * level * level; // Or level^3 (10s @ lvl10, 3g @ lvl30, 20g @ lvl60, 50g @ lvl80):
// Todo replace (Should be ~total cost of all >green gear equiped)
break;
case NeedMoneyFor::consumables:
moneyWanted =
(level * level * level) / 10; // Or level^3 (1s @ lvl10, 30s @ lvl30, 2g @ lvl60, 5g @ lvl80): Todo
// replace (Should be best food/drink x 2 stacks cost)
break;
case NeedMoneyFor::guild:
if (botAI->HasStrategy("guild", BOT_STATE_NON_COMBAT))
{
if (bot->GetGuildId())
moneyWanted = AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)) ? 0 : 10000; // 1g (tabard)
else
moneyWanted =
AI_VALUE2(uint32, "item count", chat->FormatQItem(5863)) ? 0 : 10000; // 10s (guild charter)
}
break;
case NeedMoneyFor::tradeskill:
moneyWanted = (level * level *
level); // Or level^3 (10s @ lvl10, 3g @ lvl30, 20g @ lvl60, 50g @ lvl80): Todo replace
// (Should be buyable reagents that combined allow crafting of usefull items)
break;
default:
break;
}
return moneyWanted;
};
uint32 TotalMoneyNeededForValue::Calculate()
{
NeedMoneyFor needMoneyFor = NeedMoneyFor(stoi(getQualifier()));
uint32 moneyWanted = AI_VALUE2(uint32, "money needed for", (uint32)needMoneyFor);
auto needPtr = std::find(saveMoneyFor.begin(), saveMoneyFor.end(), needMoneyFor);
while (needPtr != saveMoneyFor.begin())
{
needPtr--;
NeedMoneyFor alsoNeed = *needPtr;
moneyWanted = moneyWanted + AI_VALUE2(uint32, "money needed for", (uint32)alsoNeed);
}
return moneyWanted;
}
uint32 FreeMoneyForValue::Calculate()
{
uint32 money = bot->GetMoney();
if (botAI->HasCheat(BotCheatMask::gold))
return 10000000;
if (botAI->HasActivePlayerMaster())
return money;
uint32 savedMoney = AI_VALUE2(uint32, "total money needed for", getQualifier()) -
AI_VALUE2(uint32, "money needed for", getQualifier());
if (savedMoney > money)
return 0;
return money - savedMoney;
};
bool ShouldGetMoneyValue::Calculate() { return !AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::anything); };

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_BUDGETVALUES_H
#define _PLAYERBOT_BUDGETVALUES_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
enum class NeedMoneyFor : uint32
{
none = 0,
repair = 1,
ammo = 2,
spells = 3,
travel = 4,
consumables = 5,
gear = 6,
guild = 7,
tradeskill = 8,
anything = 9
};
class MaxGearRepairCostValue : public Uint32CalculatedValue
{
public:
MaxGearRepairCostValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "max repair cost", 60) {}
uint32 Calculate() override;
};
class RepairCostValue : public Uint32CalculatedValue
{
public:
RepairCostValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "repair cost", 60) {}
uint32 Calculate() override;
};
class TrainCostValue : public Uint32CalculatedValue
{
public:
TrainCostValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "train cost", 60) {}
uint32 Calculate() override;
};
class MoneyNeededForValue : public Uint32CalculatedValue, public Qualified
{
public:
MoneyNeededForValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "money needed for", 60) {}
uint32 Calculate() override;
};
class TotalMoneyNeededForValue : public Uint32CalculatedValue, public Qualified
{
public:
TotalMoneyNeededForValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "total money needed for", 60) {}
uint32 Calculate() override;
private:
std::vector<NeedMoneyFor> saveMoneyFor = {NeedMoneyFor::guild, NeedMoneyFor::repair, NeedMoneyFor::ammo,
NeedMoneyFor::spells, NeedMoneyFor::travel};
};
class FreeMoneyForValue : public Uint32CalculatedValue, public Qualified
{
public:
FreeMoneyForValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "free money for") {}
uint32 Calculate() override;
};
class ShouldGetMoneyValue : public BoolCalculatedValue
{
public:
ShouldGetMoneyValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "should get money", 2) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "CcTargetValue.h"
#include "Action.h"
#include "Playerbots.h"
#include "ServerFacade.h"
class FindTargetForCcStrategy : public FindTargetStrategy
{
public:
FindTargetForCcStrategy(PlayerbotAI* botAI, std::string const spell)
: FindTargetStrategy(botAI), spell(spell), maxDistance(0.f)
{
}
public:
void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override
{
Player* bot = botAI->GetBot();
if (!botAI->CanCastSpell(spell, creature))
return;
if (*botAI->GetAiObjectContext()->GetValue<Unit*>("rti cc target") == creature)
{
result = creature;
return;
}
if (*botAI->GetAiObjectContext()->GetValue<Unit*>("current target") == creature)
return;
uint8 health = static_cast<uint8>(creature->GetHealthPct());
if (health < sPlayerbotAIConfig->mediumHealth)
return;
float minDistance = botAI->GetRange("spell");
Group* group = bot->GetGroup();
if (!group)
return;
if (*botAI->GetAiObjectContext()->GetValue<uint8>("aoe count") > 2)
{
WorldLocation aoe = *botAI->GetAiObjectContext()->GetValue<WorldLocation>("aoe position");
if (sServerFacade->IsDistanceLessOrEqualThan(
sServerFacade->GetDistance2d(creature, aoe.GetPositionX(), aoe.GetPositionY()),
sPlayerbotAIConfig->aoeRadius))
return;
}
uint32 tankCount = 0;
uint32 dpsCount = 0;
GetPlayerCount(creature, &tankCount, &dpsCount);
if (!tankCount || !dpsCount)
{
result = creature;
return;
}
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member == bot)
continue;
if (!botAI->IsTank(member))
continue;
float distance = sServerFacade->GetDistance2d(member, creature);
if (distance < minDistance)
minDistance = distance;
}
if (!result || minDistance > maxDistance)
{
result = creature;
maxDistance = minDistance;
}
}
private:
std::string const spell;
float maxDistance;
};
Unit* CcTargetValue::Calculate()
{
FindTargetForCcStrategy strategy(botAI, qualifier);
return FindTarget(&strategy);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CCTARGETVALUE_H
#define _PLAYERBOT_CCTARGETVALUE_H
#include "NamedObjectContext.h"
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class CcTargetValue : public TargetValue, public Qualified
{
public:
CcTargetValue(PlayerbotAI* botAI, std::string const name = "cc target") : TargetValue(botAI, name) {}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CHATVALUE_H
#define _PLAYERBOT_CHATVALUE_H
#include "Value.h"
class PlayerbotAI;
enum ChatMsg : uint32;
class ChatValue : public ManualSetValue<ChatMsg>
{
public:
ChatValue(PlayerbotAI* botAI, std::string const name = "chat")
: ManualSetValue<ChatMsg>(botAI, CHAT_MSG_WHISPER, name)
{
}
};
#endif

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "CollisionValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
#include "ServerFacade.h"
bool CollisionValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target)
return false;
std::list<Unit*> targets;
float range = sPlayerbotAIConfig->contactDistance;
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
for (Unit* target : targets)
{
if (bot == target)
continue;
float dist = sServerFacade->GetDistance2d(bot, target->GetPositionX(), target->GetPositionY());
if (sServerFacade->IsDistanceLessThan(dist, target->GetCombatReach()))
return true;
}
return false;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_COLLISIONVALUE_H
#define _PLAYERBOT_COLLISIONVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class CollisionValue : public BoolCalculatedValue, public Qualified
{
public:
CollisionValue(PlayerbotAI* botAI, std::string const name = "collision")
: BoolCalculatedValue(botAI, name), Qualified()
{
}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CRAFTVALUE_H
#define _PLAYERBOT_CRAFTVALUE_H
#include <map>
#include "Value.h"
class PlayerbotAI;
class CraftData
{
public:
CraftData() : itemId(0) {}
CraftData(CraftData const& other) : itemId(other.itemId)
{
required.insert(other.required.begin(), other.required.end());
obtained.insert(other.obtained.begin(), other.obtained.end());
}
uint32 itemId;
std::map<uint32, uint32> required, obtained;
bool IsEmpty() { return itemId == 0; }
void Reset() { itemId = 0; }
bool IsRequired(uint32 item) { return required.find(item) != required.end(); }
bool IsFulfilled()
{
for (std::map<uint32, uint32>::iterator i = required.begin(); i != required.end(); ++i)
{
uint32 item = i->first;
if (obtained[item] < i->second)
return false;
}
return true;
}
void AddObtained(uint32 itemId, uint32 count)
{
if (IsRequired(itemId))
{
obtained[itemId] += count;
}
}
void Crafted(uint32 count)
{
for (std::map<uint32, uint32>::iterator i = required.begin(); i != required.end(); ++i)
{
uint32 item = i->first;
if (obtained[item] >= required[item] * count)
{
obtained[item] -= required[item] * count;
}
}
}
};
class CraftValue : public ManualSetValue<CraftData&>
{
public:
CraftValue(PlayerbotAI* botAI, std::string const name = "craft") : ManualSetValue<CraftData&>(botAI, data, name) {}
private:
CraftData data;
};
#endif

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "CurrentCcTargetValue.h"
#include "Playerbots.h"
class FindCurrentCcTargetStrategy : public FindTargetStrategy
{
public:
FindCurrentCcTargetStrategy(PlayerbotAI* botAI, std::string const spell) : FindTargetStrategy(botAI), spell(spell)
{
}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (botAI->HasAura(spell, attacker))
result = attacker;
}
private:
std::string const spell;
};
Unit* CurrentCcTargetValue::Calculate()
{
FindCurrentCcTargetStrategy strategy(botAI, qualifier);
return FindTarget(&strategy);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CURRENTCCTARGETVALUE_H
#define _PLAYERBOT_CURRENTCCTARGETVALUE_H
#include "NamedObjectContext.h"
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class CurrentCcTargetValue : public TargetValue, public Qualified
{
public:
CurrentCcTargetValue(PlayerbotAI* botAI, std::string const name = "current cc target") : TargetValue(botAI, name) {}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "CurrentTargetValue.h"
#include "Playerbots.h"
Unit* CurrentTargetValue::Get()
{
if (selection.IsEmpty())
return nullptr;
Unit* unit = ObjectAccessor::GetUnit(*bot, selection);
// if (unit && !bot->IsWithinLOSInMap(unit))
// return nullptr;
return unit;
}
void CurrentTargetValue::Set(Unit* target) { selection = target ? target->GetGUID() : ObjectGuid::Empty; }

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_CURRENTTARGETVALUE_H
#define _PLAYERBOT_CURRENTTARGETVALUE_H
#include "Value.h"
class PlayerbotAI;
class Unit;
class CurrentTargetValue : public UnitManualSetValue
{
public:
CurrentTargetValue(PlayerbotAI* botAI, std::string const name = "current target")
: UnitManualSetValue(botAI, nullptr, name)
{
}
Unit* Get() override;
void Set(Unit* unit) override;
private:
ObjectGuid selection;
};
#endif

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "DistanceValue.h"
#include "Formations.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
#include "PositionValue.h"
#include "ServerFacade.h"
#include "Stances.h"
float DistanceValue::Calculate()
{
if (qualifier == "loot target")
{
LootObject loot = AI_VALUE(LootObject, qualifier);
if (loot.IsEmpty())
return 0.0f;
WorldObject* obj = loot.GetWorldObject(bot);
if (!obj || !obj->IsInWorld())
return 0.0f;
return sServerFacade->GetDistance2d(botAI->GetBot(), obj);
}
if (qualifier.find("position_") == 0)
{
std::string const position = qualifier.substr(9);
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()[position];
if (!pos.isSet())
return 0.0f;
if (botAI->GetBot()->GetMapId() != pos.mapId)
return 0.0f;
return sServerFacade->GetDistance2d(botAI->GetBot(), pos.x, pos.y);
}
Unit* target = nullptr;
if (qualifier == "rpg target")
{
GuidPosition rpgTarget = AI_VALUE(GuidPosition, qualifier);
return rpgTarget.distance(bot);
}
else if (qualifier == "travel target")
{
TravelTarget* travelTarget = AI_VALUE(TravelTarget*, qualifier);
return travelTarget->distance(botAI->GetBot());
}
else if (qualifier == "last long move")
{
WorldPosition target = AI_VALUE(WorldPosition, qualifier);
return target.distance(botAI->GetBot());
}
else if (qualifier == "home bind")
{
WorldPosition target = AI_VALUE(WorldPosition, qualifier);
return target.distance(botAI->GetBot());
}
else if (qualifier == "current target")
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target || !target->IsInWorld())
return 0.0f;
return bot->GetDistance2d(target);
}
else
{
target = AI_VALUE(Unit*, qualifier);
if (target && target == GetMaster() && target != bot)
{
Formation* formation = AI_VALUE(Formation*, "formation");
WorldLocation loc = formation->GetLocation();
return sServerFacade->GetDistance2d(botAI->GetBot(), loc.GetPositionX(), loc.GetPositionY());
}
}
if (!target || !target->IsInWorld())
return 0.0f;
if (target == botAI->GetBot())
return 0.0f;
return sServerFacade->GetDistance2d(botAI->GetBot(), target);
}
bool InsideTargetValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target || !target->IsInWorld() || target == botAI->GetBot())
return false;
float dist = sServerFacade->GetDistance2d(botAI->GetBot(), target->GetPositionX(), target->GetPositionY());
return sServerFacade->IsDistanceLessThan(dist, target->GetCombatReach());
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_DISTANCEVALUE_H
#define _PLAYERBOT_DISTANCEVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class DistanceValue : public FloatCalculatedValue, public Qualified
{
public:
DistanceValue(PlayerbotAI* botAI, std::string const name = "distance") : FloatCalculatedValue(botAI, name) {}
float Calculate() override;
};
class InsideTargetValue : public BoolCalculatedValue, public Qualified
{
public:
InsideTargetValue(PlayerbotAI* botAI, std::string const name = "inside target") : BoolCalculatedValue(botAI, name)
{
}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,350 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "DpsTargetValue.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
class FindMaxThreatGapTargetStrategy : public FindTargetStrategy
{
public:
FindMaxThreatGapTargetStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI), minThreat(0) {}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (!attacker->IsAlive())
{
return;
}
if (foundHighPriority)
{
return;
}
if (IsHighPriority(attacker))
{
result = attacker;
foundHighPriority = true;
return;
}
Unit* victim = attacker->GetVictim();
if (!result || CalcThreatGap(attacker, threatMgr) > CalcThreatGap(result, &result->GetThreatMgr()))
result = attacker;
}
float CalcThreatGap(Unit* attacker, ThreatMgr* threatMgr)
{
Unit* victim = attacker->GetVictim();
return threatMgr->GetThreat(victim) - threatMgr->GetThreat(attacker);
}
protected:
float minThreat;
};
// caster
class CasterFindTargetSmartStrategy : public FindTargetStrategy
{
public:
CasterFindTargetSmartStrategy(PlayerbotAI* botAI, float dps)
: FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000)
{
result = nullptr;
}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (Group* group = botAI->GetBot()->GetGroup())
{
ObjectGuid guid = group->GetTargetIcon(4);
if (guid && attacker->GetGUID() == guid)
return;
}
if (!attacker->IsAlive())
{
return;
}
if (foundHighPriority)
{
return;
}
if (IsHighPriority(attacker))
{
result = attacker;
foundHighPriority = true;
return;
}
float expectedLifeTime = attacker->GetHealth() / dps_;
// Unit* victim = attacker->GetVictim();
if (!result || IsBetter(attacker, result))
{
targetExpectedLifeTime = expectedLifeTime;
result = attacker;
}
}
bool IsBetter(Unit* new_unit, Unit* old_unit)
{
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
// [5-30] > (5-0] > (20-inf)
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return new_level > old_level;
}
int32_t level = new_level;
if (level % 10 == 2 || level % 10 == 0)
{
return new_time < old_time;
}
// dont switch targets when all of them with low health
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
if (currentTarget == new_unit)
{
return true;
}
if (currentTarget == old_unit)
{
return false;
}
return new_time > old_time;
}
int32_t GetIntervalLevel(Unit* unit)
{
float time = unit->GetHealth() / dps_;
float dis = unit->GetDistance(botAI->GetBot());
float attackRange =
botAI->IsRanged(botAI->GetBot()) ? sPlayerbotAIConfig->spellDistance : sPlayerbotAIConfig->meleeDistance;
attackRange += 5.0f;
int level = dis < attackRange ? 10 : 0;
if (time >= 5 && time <= 30)
{
return level + 2;
}
if (time > 30)
{
return level;
}
return level + 1;
}
protected:
float dps_;
float targetExpectedLifeTime;
};
// General
class GeneralFindTargetSmartStrategy : public FindTargetStrategy
{
public:
GeneralFindTargetSmartStrategy(PlayerbotAI* botAI, float dps)
: FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000)
{
}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (Group* group = botAI->GetBot()->GetGroup())
{
ObjectGuid guid = group->GetTargetIcon(4);
if (guid && attacker->GetGUID() == guid)
return;
}
if (!attacker->IsAlive())
{
return;
}
if (foundHighPriority)
{
return;
}
if (IsHighPriority(attacker))
{
result = attacker;
foundHighPriority = true;
return;
}
float expectedLifeTime = attacker->GetHealth() / dps_;
// Unit* victim = attacker->GetVictim();
if (!result || IsBetter(attacker, result))
{
targetExpectedLifeTime = expectedLifeTime;
result = attacker;
}
}
bool IsBetter(Unit* new_unit, Unit* old_unit)
{
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return new_level > old_level;
}
// attack enemy in range and with lowest health
int level = new_level;
if (level == 10)
{
return new_time < old_time;
}
// all targets are far away, choose the closest one
return botAI->GetBot()->GetDistance(new_unit) < botAI->GetBot()->GetDistance(old_unit);
}
int32_t GetIntervalLevel(Unit* unit)
{
float time = unit->GetHealth() / dps_;
float dis = unit->GetDistance(botAI->GetBot());
float attackRange =
botAI->IsRanged(botAI->GetBot()) ? sPlayerbotAIConfig->spellDistance : sPlayerbotAIConfig->meleeDistance;
attackRange += 5.0f;
int level = dis < attackRange ? 10 : 0;
return level;
}
protected:
float dps_;
float targetExpectedLifeTime;
};
// combo
class ComboFindTargetSmartStrategy : public FindTargetStrategy
{
public:
ComboFindTargetSmartStrategy(PlayerbotAI* botAI, float dps)
: FindTargetStrategy(botAI), dps_(dps), targetExpectedLifeTime(1000000)
{
}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (Group* group = botAI->GetBot()->GetGroup())
{
ObjectGuid guid = group->GetTargetIcon(4);
if (guid && attacker->GetGUID() == guid)
return;
}
if (!attacker->IsAlive())
{
return;
}
if (foundHighPriority)
{
return;
}
if (IsHighPriority(attacker))
{
result = attacker;
foundHighPriority = true;
return;
}
float expectedLifeTime = attacker->GetHealth() / dps_;
// Unit* victim = attacker->GetVictim();
if (!result || IsBetter(attacker, result))
{
targetExpectedLifeTime = expectedLifeTime;
result = attacker;
}
}
bool IsBetter(Unit* new_unit, Unit* old_unit)
{
float new_time = new_unit->GetHealth() / dps_;
float old_time = old_unit->GetHealth() / dps_;
// [5-20] > (5-0] > (20-inf)
int new_level = GetIntervalLevel(new_unit);
int old_level = GetIntervalLevel(old_unit);
if (new_level != old_level)
{
return new_level > old_level;
}
// attack enemy in range and with lowest health
int level = new_level;
Player* bot = botAI->GetBot();
if (level == 10)
{
Unit* combo_unit = bot->GetComboTarget();
if (new_unit == combo_unit)
{
return true;
}
return new_time < old_time;
}
// all targets are far away, choose the closest one
return bot->GetDistance(new_unit) < bot->GetDistance(old_unit);
}
int32_t GetIntervalLevel(Unit* unit)
{
float time = unit->GetHealth() / dps_;
float dis = unit->GetDistance(botAI->GetBot());
float attackRange =
botAI->IsRanged(botAI->GetBot()) ? sPlayerbotAIConfig->spellDistance : sPlayerbotAIConfig->meleeDistance;
attackRange += 5.0f;
int level = dis < attackRange ? 10 : 0;
return level;
}
protected:
float dps_;
float targetExpectedLifeTime;
};
Unit* DpsTargetValue::Calculate()
{
Unit* rti = RtiTargetValue::Calculate();
if (rti)
return rti;
float dps = AI_VALUE(float, "estimated group dps");
if (botAI->GetNearGroupMemberCount() > 3)
{
if (botAI->IsCaster(bot))
{
// Caster find target strategy avoids casting spells on enemies
// with too low health to ensure the effectiveness of casting
CasterFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
else if (botAI->IsCombo(bot))
{
ComboFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
}
GeneralFindTargetSmartStrategy strategy(botAI, dps);
return TargetValue::FindTarget(&strategy);
}
class FindMaxHpTargetStrategy : public FindTargetStrategy
{
public:
FindMaxHpTargetStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI), maxHealth(0) {}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
if (Group* group = botAI->GetBot()->GetGroup())
{
ObjectGuid guid = group->GetTargetIcon(4);
if (guid && attacker->GetGUID() == guid)
return;
}
if (!result || result->GetHealth() < attacker->GetHealth())
result = attacker;
}
protected:
float maxHealth;
};
Unit* DpsAoeTargetValue::Calculate()
{
Unit* rti = RtiTargetValue::Calculate();
if (rti)
return rti;
FindMaxHpTargetStrategy strategy(botAI);
return TargetValue::FindTarget(&strategy);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_DPSTARGETVALUE_H
#define _PLAYERBOT_DPSTARGETVALUE_H
#include "RtiTargetValue.h"
class PlayerbotAI;
class DpsTargetValue : public RtiTargetValue
{
public:
DpsTargetValue(PlayerbotAI* botAI, std::string const type = "rti", std::string const name = "dps target")
: RtiTargetValue(botAI, type, name)
{
}
Unit* Calculate() override;
};
class DpsAoeTargetValue : public RtiTargetValue
{
public:
DpsAoeTargetValue(PlayerbotAI* botAI, std::string const type = "rti", std::string const name = "dps aoe target")
: RtiTargetValue(botAI, type, name)
{
}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,10 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "DuelTargetValue.h"
#include "Playerbots.h"
Unit* DuelTargetValue::Calculate() { return bot->duel ? bot->duel->Opponent : nullptr; }

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_DUELTARGETVALUE_H
#define _PLAYERBOT_DUELTARGETVALUE_H
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class DuelTargetValue : public TargetValue
{
public:
DuelTargetValue(PlayerbotAI* botAI, std::string const name = "duel target") : TargetValue(botAI, name) {}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "EnemyHealerTargetValue.h"
#include "Playerbots.h"
#include "ServerFacade.h"
Unit* EnemyHealerTargetValue::Calculate()
{
std::string const spell = qualifier;
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
Unit* target = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || unit == target)
continue;
if (sServerFacade->GetDistance2d(bot, unit) > botAI->GetRange("spell"))
continue;
if (!botAI->IsInterruptableSpellCasting(unit, spell))
continue;
Spell* spell = unit->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (spell && spell->m_spellInfo->IsPositive())
return unit;
spell = unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (spell && spell->m_spellInfo->IsPositive())
return unit;
}
return nullptr;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ENEMYHEALERTARGETVALUE_H
#define _PLAYERBOT_ENEMYHEALERTARGETVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class Unit;
class EnemyHealerTargetValue : public UnitCalculatedValue, public Qualified
{
public:
EnemyHealerTargetValue(PlayerbotAI* botAI) : UnitCalculatedValue(botAI, "enemy healer target") {}
protected:
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "EnemyPlayerValue.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "Vehicle.h"
bool NearestEnemyPlayersValue::AcceptUnit(Unit* unit)
{
bool inCannon = botAI->IsInVehicle(false, true);
Player* enemy = dynamic_cast<Player*>(unit);
if (enemy && botAI->IsOpposing(enemy) && enemy->IsPvP() &&
!sPlayerbotAIConfig->IsPvpProhibited(enemy->GetZoneId(), enemy->GetAreaId()) &&
!enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) &&
((inCannon || !enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))) &&
/*!enemy->HasStealthAura() && !enemy->HasInvisibilityAura()*/ enemy->CanSeeOrDetect(bot) &&
!(enemy->HasSpiritOfRedemptionAura()))
return true;
return false;
}
Unit* EnemyPlayerValue::Calculate()
{
bool controllingCannon = false;
bool controllingVehicle = false;
if (Vehicle* vehicle = bot->GetVehicle())
{
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
if (!seat || !seat->CanControl()) // not in control of vehicle so cant attack anyone
return nullptr;
VehicleEntry const* vi = vehicle->GetVehicleInfo();
if (vi && vi->m_flags & VEHICLE_FLAG_FIXED_POSITION)
controllingCannon = true;
else
controllingVehicle = true;
}
// 1. Check units we are currently in combat with.
std::vector<Unit*> targets;
Unit* pVictim = bot->GetVictim();
HostileReference* pReference = bot->getHostileRefMgr().getFirst();
while (pReference)
{
ThreatMgr* threatMgr = pReference->GetSource();
if (Unit* pTarget = threatMgr->GetOwner())
{
if (pTarget != pVictim && pTarget->IsPlayer() && pTarget->CanSeeOrDetect(bot) &&
bot->IsWithinDist(pTarget, VISIBILITY_DISTANCE_NORMAL))
{
if (bot->GetTeamId() == TEAM_HORDE)
{
if (pTarget->HasAura(23333))
return pTarget;
}
else
{
if (pTarget->HasAura(23335))
return pTarget;
}
targets.push_back(pTarget);
}
}
pReference = pReference->next();
}
if (!targets.empty())
{
std::sort(targets.begin(), targets.end(),
[&](Unit const* pUnit1, Unit const* pUnit2)
{ return bot->GetDistance(pUnit1) < bot->GetDistance(pUnit2); });
return *targets.begin();
}
// 2. Find enemy player in range.
GuidVector players = AI_VALUE(GuidVector, "nearest enemy players");
float const maxAggroDistance = GetMaxAttackDistance();
for (auto const& gTarget : players)
{
Unit* pUnit = botAI->GetUnit(gTarget);
if (!pUnit)
continue;
Player* pTarget = dynamic_cast<Player*>(pUnit);
if (!pTarget)
continue;
if (pTarget == pVictim)
continue;
if (bot->GetTeamId() == TEAM_HORDE)
{
if (pTarget->HasAura(23333))
return pTarget;
}
else
{
if (pTarget->HasAura(23335))
return pTarget;
}
// Aggro weak enemies from further away.
// If controlling mobile vehicle only agro close enemies (otherwise will never reach objective)
uint32 const aggroDistance = controllingVehicle ? 5.0f
: (controllingCannon || bot->GetHealth() > pTarget->GetHealth()) ? maxAggroDistance
: 20.0f;
if (!bot->IsWithinDist(pTarget, aggroDistance))
continue;
if (bot->IsWithinLOSInMap(pTarget) &&
(controllingCannon || (fabs(bot->GetPositionZ() - pTarget->GetPositionZ()) < 30.0f)))
return pTarget;
}
// 3. Check party attackers.
if (Group* pGroup = bot->GetGroup())
{
for (GroupReference* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next())
{
if (Unit* pMember = itr->GetSource())
{
if (pMember == bot)
continue;
if (sServerFacade->GetDistance2d(bot, pMember) > 30.0f)
continue;
if (Unit* pAttacker = pMember->getAttackerForHelper())
if (pAttacker->IsPlayer() && bot->IsWithinDist(pAttacker, maxAggroDistance * 2.0f) &&
bot->IsWithinLOSInMap(pAttacker) && pAttacker != pVictim && pAttacker->CanSeeOrDetect(bot))
return pAttacker;
}
}
}
return nullptr;
}
float EnemyPlayerValue::GetMaxAttackDistance()
{
if (!bot->GetBattleground())
return 60.0f;
Battleground* bg = bot->GetBattleground();
if (!bg)
return 40.0f;
BattlegroundTypeId bgType = bg->GetBgTypeID();
if (bgType == BATTLEGROUND_RB)
bgType = bg->GetBgTypeID(true);
if (bgType == BATTLEGROUND_IC)
{
if (botAI->IsInVehicle(false, true))
return 120.0f;
}
return 40.0f;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ENEMYPLAYERVALUE_H
#define _PLAYERBOT_ENEMYPLAYERVALUE_H
#include "PlayerbotAIConfig.h"
#include "PossibleTargetsValue.h"
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class NearestEnemyPlayersValue : public PossibleTargetsValue
{
public:
NearestEnemyPlayersValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->grindDistance)
: PossibleTargetsValue(botAI, "nearest enemy players", range)
{
}
public:
bool AcceptUnit(Unit* unit) override;
};
class EnemyPlayerValue : public UnitCalculatedValue
{
public:
EnemyPlayerValue(PlayerbotAI* botAI, std::string const name = "enemy player")
: UnitCalculatedValue(botAI, name, 1 * 1000)
{
}
Unit* Calculate() override;
private:
float GetMaxAttackDistance();
};
#endif

View File

@@ -0,0 +1,149 @@
#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 = 0;
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;
// ignore real player as they may not help with damage
if (!GET_PLAYERBOT_AI(member) || GET_PLAYERBOT_AI(member)->IsRealPlayer())
continue;
if (!member || !member->IsInWorld() || !member->IsAlive())
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

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_EstimatedLifetimeValue_H
#define _PLAYERBOT_EstimatedLifetimeValue_H
#include "NamedObjectContext.h"
#include "PossibleTargetsValue.h"
#include "TargetValue.h"
#include "Value.h"
class PlayerbotAI;
class Unit;
// [target health] / [expected group single target dps] = [expected lifetime]
class EstimatedLifetimeValue : public FloatCalculatedValue, public Qualified
{
public:
EstimatedLifetimeValue(PlayerbotAI* botAI) : FloatCalculatedValue(botAI, "estimated lifetime") {}
public:
float Calculate() override;
};
class EstimatedGroupDpsValue : public FloatCalculatedValue
{
public:
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

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "FishValues.h"
#include "PlayerbotAI.h"
#include "RandomPlayerbotMgr.h"
#include "Map.h"
#include "Spell.h"
#include "FishingAction.h"
bool CanFishValue::Calculate()
{
int32 SkillFishing = bot->GetSkillValue(SKILL_FISHING);
if (SkillFishing == 0)
return false;
if (bot->isSwimming())
return false;
if (bot->IsInCombat())
return false;
return true;
}
bool CanUseFishingBobberValue::Calculate()
{
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
for (auto const& guid : gos)
{
if (GameObject* go = botAI->GetGameObject(guid))
{
if (go->GetEntry() != FISHING_BOBBER)
continue;
if (go->GetOwnerGUID() != bot->GetGUID())
continue;
if (go->getLootState() == GO_READY)
return true;
// Not ready yet → delay next check
time_t bobberActiveTime = go->GetRespawnTime() - FISHING_BOBBER_READY_TIME;
if (bobberActiveTime > time(0))
botAI->SetNextCheckDelay((bobberActiveTime - time(0)) * IN_MILLISECONDS + 500);
else
botAI->SetNextCheckDelay(1000);
return false;
}
}
return false;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_FISHVALUES_H
#define _PLAYERBOT_FISHVALUES_H
#include "Value.h"
#include "TravelMgr.h"
#include "NamedObjectContext.h"
class PlayerbotAI;
class CanFishValue : public BoolCalculatedValue
{
public:
CanFishValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can fish") {};
bool Calculate() override;
};
class CanUseFishingBobberValue : public BoolCalculatedValue
{
public:
CanUseFishingBobberValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can use fishing bobber") {};
bool Calculate() override;
};
class FishingSpotValue : public ManualSetValue<WorldPosition>
{
public:
FishingSpotValue(PlayerbotAI* botAI, WorldPosition const& pos = WorldPosition(), std::string const& name = "fishing spot")
: ManualSetValue<WorldPosition>(botAI, pos, name) {}
void Set(WorldPosition val) override
{
value = val;
_setTime = getMSTime();
}
uint32 lastUpdated() const {return _setTime;}
bool IsStale(uint32 maxDuration) const { return getMSTime() - _setTime > maxDuration; }
private:
uint32 _setTime = 0;
};
#endif

View File

@@ -0,0 +1,702 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "Formations.h"
#include "Arrow.h"
#include "Event.h"
#include "Map.h"
#include "Playerbots.h"
#include "ServerFacade.h"
WorldLocation Formation::NullLocation = WorldLocation();
bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
{
return a.GetPositionX() == b.GetPositionX() && a.GetPositionY() == b.GetPositionY() &&
a.GetPositionZ() == b.GetPositionZ() && a.GetMapId() == b.GetMapId();
}
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap)
{
if (!a || !b || a == b)
return false;
if (!a->IsInWorld() || !b->IsInWorld())
return false;
if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld())
return false;
Map* map = a->GetMap();
if (!map || map != b->GetMap())
return false;
outMap = map;
return true;
}
bool ValidateTargetContext(Unit* a, Unit* b)
{
Map* unused = nullptr;
return ValidateTargetContext(a, b, unused);
}
WorldLocation MoveAheadFormation::GetLocation()
{
Player* master = GetMaster();
if (!ValidateTargetContext(master, bot))
return Formation::NullLocation;
WorldLocation loc = GetLocationInternal();
if (Formation::IsNullLocation(loc))
return loc;
float x = loc.GetPositionX();
float y = loc.GetPositionY();
float z = loc.GetPositionZ();
// if (master->isMoving())
// {
// float ori = master->GetOrientation();
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
// float ground = map->GetHeight(x1, y1, z);
// if (ground > INVALID_HEIGHT)
// {
// x = x1;
// y = y1;
// }
// }
// float ground = map->GetHeight(x, y, z);
// if (ground <= INVALID_HEIGHT)
// return Formation::NullLocation;
// z += CONTACT_DISTANCE;
// bot->UpdateAllowedPositionZ(x, y, z);
return WorldLocation(master->GetMapId(), x, y, z);
}
class MeleeFormation : public FollowFormation
{
public:
MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {}
std::string const GetTargetName() override { return "group leader"; }
};
class QueueFormation : public FollowFormation
{
public:
QueueFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "queue") {}
std::string const GetTargetName() override { return "line target"; }
};
class NearFormation : public MoveAheadFormation
{
public:
NearFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "near") {}
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range;
float y = master->GetPositionY() + sin(angle) * range;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range;
y = master->GetPositionY() + sin(angle) * range;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
float GetMaxDistance() override { return sPlayerbotAIConfig->followDistance; }
};
class ChaosFormation : public MoveAheadFormation
{
public:
ChaosFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "chaos"), lastChangeTime(0) {}
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
time_t now = time(nullptr);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
lastChangeTime = now;
dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dr = std::sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + std::cos(angle) * range + dx;
float y = master->GetPositionY() + std::sin(angle) * range + dy;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
// Recompute a clean fallback and clamp Z
x = master->GetPositionX() + std::cos(angle) * range + dx;
y = master->GetPositionY() + std::sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
float GetMaxDistance() override { return sPlayerbotAIConfig->followDistance + dr; }
private:
time_t lastChangeTime;
float dx = 0.f;
float dy = 0.f;
float dr = 0.f;
};
class CircleFormation : public MoveFormation
{
public:
CircleFormation(PlayerbotAI* botAI) : MoveFormation(botAI, "circle") {}
WorldLocation GetLocation() override
{
Unit* target = AI_VALUE(Unit*, "current target");
Player* master = GetMaster();
// Fix: if no target OR target is the bot, fall back to master
if (!target || target == bot)
target = master;
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = 2.0f;
switch (bot->getClass())
{
case CLASS_HUNTER:
case CLASS_MAGE:
case CLASS_PRIEST:
case CLASS_WARLOCK:
range = botAI->GetRange("flee");
break;
case CLASS_DRUID:
if (!botAI->IsTank(bot))
range = botAI->GetRange("flee");
break;
case CLASS_SHAMAN:
if (botAI->IsHeal(bot))
range = botAI->GetRange("flee");
break;
}
float angle = GetFollowAngle();
float x = target->GetPositionX() + cos(angle) * range;
float y = target->GetPositionY() + sin(angle) * range;
float z = target->GetPositionZ();
if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), x, y, z))
{
x = target->GetPositionX() + cos(angle) * range;
y = target->GetPositionY() + sin(angle) * range;
z = target->GetPositionZ();
target->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
class LineFormation : public MoveAheadFormation
{
public:
LineFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "line") {}
WorldLocation GetLocationInternal() override
{
Group* group = bot->GetGroup();
if (!group)
return Formation::NullLocation;
float range = 2.0f;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
float x = master->GetPositionX();
float y = master->GetPositionY();
float z = master->GetPositionZ();
float orientation = master->GetOrientation();
std::vector<Player*> players;
players.reserve(group->GetMembersCount());
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || member == master)
continue;
players.push_back(member);
}
players.insert(players.begin() + players.size() / 2, master);
return MoveLine(players, 0.0f, x, y, z, orientation, range);
}
};
class ShieldFormation : public MoveFormation
{
public:
ShieldFormation(PlayerbotAI* botAI) : MoveFormation(botAI, "shield") {}
WorldLocation GetLocation() override
{
Group* group = bot->GetGroup();
if (!group)
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
float x = master->GetPositionX();
float y = master->GetPositionY();
float z = master->GetPositionZ();
float orientation = master->GetOrientation();
std::vector<Player*> tanks;
std::vector<Player*> dps;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || member == master)
continue;
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
if (botAI->IsTank(master))
tanks.insert(tanks.begin() + (tanks.size() + 1) / 2, master);
else
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
if (botAI->IsTank(bot) && botAI->IsTank(master))
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
if (botAI->IsTank(bot) && !botAI->IsTank(master))
{
float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
range);
}
if (!botAI->IsTank(bot) && botAI->IsTank(master))
{
float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
range);
}
return Formation::NullLocation;
}
};
class FarFormation : public FollowFormation
{
public:
FarFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "far") {}
WorldLocation GetLocation() override
{
Player* master = GetMaster();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->farDistance;
float followRange = sPlayerbotAIConfig->followDistance;
if (sServerFacade->GetDistance2d(bot, master) <= range)
return Formation::NullLocation;
float angleToBot = master->GetAngle(bot);
float followAngle = GetFollowAngle();
float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
float z = master->GetPositionZ();
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground <= INVALID_HEIGHT)
{
float minDist = 0.f;
float minX = 0.f, minY = 0.f;
for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f)
{
float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange;
float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, tx, ty);
float tg = master->GetMapHeight(tx, ty, z + 30.0f);
if (tg > INVALID_HEIGHT && (!minDist || dist < minDist))
{
minDist = dist;
minX = tx;
minY = ty;
}
}
if (!minDist)
return Formation::NullLocation;
float lz = z;
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), minX, minY, lz))
{
lz = z + master->GetHoverHeight();
master->UpdateAllowedPositionZ(minX, minY, lz);
}
return WorldLocation(bot->GetMapId(), minX, minY, lz);
}
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
float Formation::GetFollowAngle()
{
Player* master = GetMaster();
Group* group = bot->GetGroup();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// If there's no master and no group
if (!master && !group)
return 0.0f;
uint32 index = 1;
uint32 total = 1;
std::vector<Player*> roster;
if (group)
{
bool left = true; // Used for alternating tanks' positions
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
// Skip invalid, dead, or out-of-map members
if (!member || !member->IsAlive() || bot->GetMapId() != member->GetMapId())
continue;
// Skip the master
if (member == master)
continue;
// Put DPS in the middle
if (!botAI->IsTank(member) && !botAI->IsHeal(member))
{
roster.insert(roster.begin() + roster.size() / 2, member);
}
// Put Healers in the middle
else if (botAI->IsHeal(member))
{
roster.insert(roster.begin() + roster.size() / 2, member);
}
// Handle tanks (alternate between front and back)
else if (botAI->IsTank(member))
{
if (left)
roster.push_back(member); // Place tank at the back
else
roster.insert(roster.begin(), member); // Place tank at the front
left = !left; // Alternate for the next tank
}
total++;
}
}
else if (master)
{
// If the bot is following a master, look up the bot's position in the master's list
PlayerbotMgr* masterBotMgr = GET_PLAYERBOT_MGR(master);
if (masterBotMgr && !GET_PLAYERBOT_AI(master))
{
for (auto it = masterBotMgr->GetPlayerBotsBegin(); it != masterBotMgr->GetPlayerBotsEnd(); ++it)
{
if (it->second == bot)
{
index = total; // Found bot in master's list, set the index
break;
}
++total;
}
}
}
// Find the bot's position in the roster
auto it = std::find(roster.begin(), roster.end(), bot);
if (it != roster.end())
{
index = std::distance(roster.begin(), it) + 1; // Find bot's index in the roster
}
// Return
float start = (master ? master->GetOrientation() : 0.0f);
return start + (0.125f + 1.75f * index / total + (total == 2 ? 0.125f : 0.0f)) * M_PI;
}
FormationValue::FormationValue(PlayerbotAI* botAI)
: ManualSetValue<Formation*>(botAI, new ChaosFormation(botAI), "formation")
{
}
FormationValue::~FormationValue()
{
if (value)
{
delete value;
value = nullptr;
}
}
std::string const FormationValue::Save() { return value ? value->getName() : "?"; }
bool FormationValue::Load(std::string const formation)
{
if (formation == "melee")
{
if (value)
delete value;
value = new MeleeFormation(botAI);
}
else if (formation == "queue")
{
if (value)
delete value;
value = new QueueFormation(botAI);
}
else if (formation == "chaos" || formation == "default")
{
if (value)
delete value;
value = new ChaosFormation(botAI);
}
else if (formation == "circle")
{
if (value)
delete value;
value = new CircleFormation(botAI);
}
else if (formation == "line")
{
if (value)
delete value;
value = new LineFormation(botAI);
}
else if (formation == "shield")
{
if (value)
delete value;
value = new ShieldFormation(botAI);
}
else if (formation == "arrow")
{
if (value)
delete value;
value = new ArrowFormation(botAI);
}
else if (formation == "near")
{
if (value)
delete value;
value = new NearFormation(botAI);
}
else if (formation == "far")
{
if (value)
delete value;
value = new FarFormation(botAI);
}
else
return false;
return true;
}
bool SetFormationAction::Execute(Event event)
{
std::string const formation = event.getParam();
FormationValue* value = (FormationValue*)context->GetValue<Formation*>("formation");
if (formation == "?" || formation.empty())
{
std::ostringstream str;
str << "Formation: |cff00ff00" << value->Get()->getName();
botAI->TellMaster(str);
return true;
}
if (formation == "show")
{
WorldLocation loc = value->Get()->GetLocation();
if (!Formation::IsNullLocation(loc))
botAI->Ping(loc.GetPositionX(), loc.GetPositionY());
return true;
}
if (!value->Load(formation))
{
std::ostringstream str;
str << "Invalid formation: |cffff0000" << formation;
botAI->TellMaster(str);
botAI->TellMaster(
"Please set to any of:|cffffffff chaos (default), near, queue, circle, line, shield, arrow, melee, far");
return false;
}
std::ostringstream str;
str << "Formation set to: " << formation;
botAI->TellMaster(str);
return true;
}
WorldLocation MoveFormation::MoveLine(std::vector<Player*> line, float diff, float cx, float cy, float cz,
float orientation, float range)
{
if (line.size() < 5)
{
return MoveSingleLine(line, diff, cx, cy, cz, orientation, range);
}
uint32 lines = ceil((double)line.size() / 5.0);
for (uint32 i = 0; i < lines; i++)
{
float radius = range * i;
float x = cx + cos(orientation) * radius;
float y = cy + sin(orientation) * radius;
std::vector<Player*> singleLine;
for (uint32 j = 0; j < 5 && !line.empty(); j++)
{
singleLine.push_back(line[line.size() - 1]);
line.pop_back();
}
WorldLocation loc = MoveSingleLine(singleLine, diff, x, y, cz, orientation, range);
if (!Formation::IsNullLocation(loc))
return loc;
}
return Formation::NullLocation;
}
WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float diff, float cx, float cy, float cz,
float orientation, float range)
{
float count = line.size();
float angleLeft = orientation - M_PI / 2.0f;
float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff);
float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff);
uint32 index = 0;
for (Player* member : line)
{
if (member == bot)
{
float angleRight = orientation + M_PI / 2.0f;
float radius = range * index;
float lx = x0 + std::cos(angleRight) * radius;
float ly = y0 + std::sin(angleRight) * radius;
float lz = cz;
Player* master = botAI->GetMaster();
Map* map = master ? master->GetMap() : nullptr;
// if not fully in world ignore collision corrections.
if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() ||
master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}
// if fully loaded check collision and applies coordinate corrections if needed
map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), lx, ly, lz);
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}
++index;
}
return Formation::NullLocation;
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_FORMATIONS_H
#define _PLAYERBOT_FORMATIONS_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "PlayerbotAIConfig.h"
#include "TravelMgr.h"
class Player;
class PlayerbotAI;
class Formation : public AiNamedObject
{
public:
Formation(PlayerbotAI* botAI, std::string const name) : AiNamedObject(botAI, name) {}
virtual ~Formation() = default;
virtual std::string const GetTargetName() { return ""; }
virtual WorldLocation GetLocation() { return NullLocation; }
virtual float GetMaxDistance() { return sPlayerbotAIConfig->followDistance; }
static WorldLocation NullLocation;
static bool IsNullLocation(WorldLocation const& loc);
protected:
float GetFollowAngle();
};
class FollowFormation : public Formation
{
public:
FollowFormation(PlayerbotAI* botAI, std::string const name) : Formation(botAI, name) {}
};
class MoveFormation : public Formation
{
public:
MoveFormation(PlayerbotAI* botAI, std::string const name) : Formation(botAI, name) {}
protected:
WorldLocation MoveLine(std::vector<Player*> line, float diff, float cx, float cy, float cz, float orientation,
float range);
WorldLocation MoveSingleLine(std::vector<Player*> line, float diff, float cx, float cy, float cz, float orientation,
float range);
};
class MoveAheadFormation : public MoveFormation
{
public:
MoveAheadFormation(PlayerbotAI* botAI, std::string const name) : MoveFormation(botAI, name) {}
WorldLocation GetLocation() override;
virtual WorldLocation GetLocationInternal() { return NullLocation; }
};
class FormationValue : public ManualSetValue<Formation*>
{
public:
FormationValue(PlayerbotAI* botAI);
~FormationValue();
std::string const Save() override;
bool Load(std::string const value) override;
};
class SetFormationAction : public Action
{
public:
SetFormationAction(PlayerbotAI* botAI) : Action(botAI, "set formation") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "GrindTargetValue.h"
#include "NewRpgInfo.h"
#include "Playerbots.h"
#include "ReputationMgr.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
Unit* GrindTargetValue::Calculate()
{
uint32 memberCount = 1;
Group* group = bot->GetGroup();
if (group)
memberCount = group->GetMembersCount();
Unit* target = nullptr;
uint32 assistCount = 0;
while (!target && assistCount < memberCount)
{
target = FindTargetForGrinding(assistCount++);
}
return target;
}
Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
{
uint32 memberCount = 1;
Group* group = bot->GetGroup();
Player* master = GetMaster();
if (master && (master == bot || master->GetMapId() != bot->GetMapId() || master->IsBeingTeleported() ||
!GET_PLAYERBOT_AI(master)))
master = nullptr;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive())
continue;
return unit;
}
GuidVector targets = *context->GetValue<GuidVector>("possible targets");
if (targets.empty())
return nullptr;
float distance = 0;
Unit* result = nullptr;
std::unordered_map<uint32, bool> needForQuestMap;
for (ObjectGuid const guid : targets)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit)
continue;
auto& rep = bot->ToPlayer()->GetReputationMgr();
if (unit->ToCreature() && !unit->ToCreature()->GetCreatureTemplate()->lootid &&
bot->GetReactionTo(unit) >= REP_NEUTRAL)
{
continue;
}
if (!bot->IsHostileTo(unit) && unit->GetNpcFlags() != UNIT_NPC_FLAG_NONE)
{
continue;
}
if (!bot->isHonorOrXPTarget(unit))
{
continue;
}
if (abs(bot->GetPositionZ() - unit->GetPositionZ()) > INTERACTION_DISTANCE)
continue;
if (!bot->InBattleground() && GetTargetingPlayerCount(unit) > assistCount)
continue;
// if (!bot->InBattleground() && master && master->GetDistance(unit) >= sPlayerbotAIConfig->grindDistance &&
// !sRandomPlayerbotMgr->IsRandomBot(bot)) continue;
// Bots in bot-groups no have a more limited range to look for grind target
if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT) &&
sServerFacade->GetDistance2d(master, unit) > sPlayerbotAIConfig->lootDistance)
{
if (botAI->HasStrategy("debug grind", BotState::BOT_STATE_NON_COMBAT))
botAI->TellMaster(chat->FormatWorldobject(unit) + " ignored (far from master).");
continue;
}
if (!bot->InBattleground() && (int)unit->GetLevel() - (int)bot->GetLevel() > 4 && !unit->GetGUID().IsPlayer())
continue;
if (Creature* creature = unit->ToCreature())
if (CreatureTemplate const* CreatureTemplate = creature->GetCreatureTemplate())
if (CreatureTemplate->rank > CREATURE_ELITE_NORMAL && !AI_VALUE(bool, "can fight elite"))
continue;
if (!bot->IsWithinLOSInMap(unit))
{
continue;
}
bool inactiveGrindStatus = botAI->rpgInfo.status != RPG_WANDER_RANDOM && botAI->rpgInfo.status != RPG_IDLE;
float aggroRange = 30.0f;
if (unit->ToCreature())
aggroRange = std::min(30.0f, unit->ToCreature()->GetAggroRange(bot) + 10.0f);
bool outOfAggro = unit->ToCreature() && bot->GetDistance(unit) > aggroRange;
if (inactiveGrindStatus && outOfAggro)
{
if (needForQuestMap.find(unit->GetEntry()) == needForQuestMap.end())
needForQuestMap[unit->GetEntry()] = needForQuest(unit);
if (!needForQuestMap[unit->GetEntry()])
continue;
}
if (group)
{
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive())
continue;
float d = member->GetDistance(unit);
if (!result || d < distance)
{
distance = d;
result = unit;
}
}
}
else
{
float newdistance = bot->GetDistance(unit);
if (!result || (newdistance < distance))
{
distance = newdistance;
result = unit;
}
}
}
return result;
}
bool GrindTargetValue::needForQuest(Unit* target)
{
QuestStatusMap& questMap = bot->getQuestStatusMap();
for (auto& quest : questMap)
{
Quest const* questTemplate = sObjectMgr->GetQuestTemplate(quest.first);
if (!questTemplate)
continue;
uint32 questId = questTemplate->GetQuestId();
if (!questId)
continue;
QuestStatus status = bot->GetQuestStatus(questId);
if (status == QUEST_STATUS_INCOMPLETE)
{
const QuestStatusData* questStatus = &bot->getQuestStatusMap()[questId];
if (questTemplate->GetQuestLevel() > bot->GetLevel() + 5)
continue;
for (int j = 0; j < QUEST_OBJECTIVES_COUNT; j++)
{
int32 entry = questTemplate->RequiredNpcOrGo[j];
if (entry && entry > 0)
{
int required = questTemplate->RequiredNpcOrGoCount[j];
int available = questStatus->CreatureOrGOCount[j];
if (required && available < required && target->GetEntry() == entry)
return true;
}
}
}
}
if (CreatureTemplate const* data = sObjectMgr->GetCreatureTemplate(target->GetEntry()))
{
if (uint32 lootId = data->lootid)
{
if (LootTemplates_Creature.HaveQuestLootForPlayer(lootId, bot))
{
return true;
}
}
}
return false;
}
uint32 GrindTargetValue::GetTargetingPlayerCount(Unit* unit)
{
Group* group = bot->GetGroup();
if (!group)
return 0;
uint32 count = 0;
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* member = ObjectAccessor::FindPlayer(itr->guid);
if (!member || !member->IsAlive() || member == bot)
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(member);
if ((botAI && *botAI->GetAiObjectContext()->GetValue<Unit*>("current target") == unit) ||
(!botAI && member->GetTarget() == unit->GetGUID()))
++count;
}
return count;
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_GRINDTARGETVALUE_H
#define _PLAYERBOT_GRINDTARGETVALUE_H
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class GrindTargetValue : public TargetValue
{
public:
GrindTargetValue(PlayerbotAI* botAI, std::string const name = "grind target") : TargetValue(botAI, name) {}
Unit* Calculate() override;
private:
uint32 GetTargetingPlayerCount(Unit* unit);
Unit* FindTargetForGrinding(uint32 assistCount);
bool needForQuest(Unit* target);
};
#endif

View File

@@ -0,0 +1,10 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "GroupLeaderValue.h"
#include "Playerbots.h"
Unit* GroupLeaderValue::Calculate() { return botAI->GetGroupLeader(); }

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_GROUPLEADERVALUE_H
#define _PLAYERBOT_GROUPLEADERVALUE_H
#include "Value.h"
class PlayerbotAI;
class Unit;
class GroupLeaderValue : public UnitCalculatedValue
{
public:
GroupLeaderValue(PlayerbotAI* botAI, std::string const name = "group leader") : UnitCalculatedValue(botAI, name)
{
}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,174 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "GroupValues.h"
#include "Playerbots.h"
#include "ServerFacade.h"
GuidVector GroupMembersValue::Calculate()
{
GuidVector members;
Group* group = bot->GetGroup();
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
members.push_back(ref->GetSource()->GetGUID());
}
}
else
members.push_back(bot->GetGUID());
return members;
}
bool IsFollowingPartyValue::Calculate()
{
if (botAI->GetGroupLeader() == bot)
return true;
if (botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return true;
return false;
};
bool IsNearLeaderValue::Calculate()
{
Player* groupLeader = botAI->GetGroupLeader();
if (!groupLeader)
return false;
if (groupLeader == bot)
return true;
return sServerFacade->GetDistance2d(bot, botAI->GetGroupLeader()) < sPlayerbotAIConfig->sightDistance;
}
bool BoolANDValue::Calculate()
{
std::vector<std::string> values = split(getQualifier(), ',');
for (auto value : values)
{
if (!AI_VALUE(bool, value))
return false;
}
return true;
}
uint32 GroupBoolCountValue::Calculate()
{
uint32 count = 0;
for (ObjectGuid guid : AI_VALUE(GuidVector, "group members"))
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
if (player->GetMapId() != bot->GetMapId())
continue;
if (!GET_PLAYERBOT_AI(player))
continue;
if (PAI_VALUE2(bool, "and", getQualifier()))
return count++;
}
return count;
};
bool GroupBoolANDValue::Calculate()
{
for (ObjectGuid guid : AI_VALUE(GuidVector, "group members"))
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
if (player->GetMapId() != bot->GetMapId())
continue;
if (!GET_PLAYERBOT_AI(player))
continue;
if (!PAI_VALUE2(bool, "and", getQualifier()))
return false;
}
return true;
};
bool GroupBoolORValue::Calculate()
{
for (ObjectGuid guid : AI_VALUE(GuidVector, "group members"))
{
Player* player = ObjectAccessor::FindPlayer(guid);
if (!player)
continue;
if (player->GetMapId() != bot->GetMapId())
continue;
if (!GET_PLAYERBOT_AI(player))
continue;
if (PAI_VALUE2(bool, "and", getQualifier()))
return true;
}
return false;
};
bool GroupReadyValue::Calculate()
{
bool inDungeon = !WorldPosition(bot).isOverworld();
for (ObjectGuid guid : AI_VALUE(GuidVector, "group members"))
{
Player* member = ObjectAccessor::FindPlayer(guid);
if (!member)
continue;
if (inDungeon) // In dungeons all following members need to be alive before continueing.
{
PlayerbotAI* memberAi = GET_PLAYERBOT_AI(member);
bool isFollowing = memberAi ? memberAi->HasStrategy("follow", BOT_STATE_NON_COMBAT) : true;
if (!member->IsAlive() && isFollowing)
return false;
}
// We only wait for members that are in range otherwise we might be waiting for bots stuck in dead loops
// forever.
if (botAI->GetGroupLeader() &&
sServerFacade->GetDistance2d(member, botAI->GetGroupLeader()) > sPlayerbotAIConfig->sightDistance)
continue;
if (member->GetHealthPct() < sPlayerbotAIConfig->almostFullHealth)
return false;
if (!member->GetPower(POWER_MANA))
continue;
float mana = (static_cast<float>(member->GetPower(POWER_MANA)) / member->GetMaxPower(POWER_MANA)) * 100;
if (mana < sPlayerbotAIConfig->mediumMana)
return false;
}
return true;
};

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class GroupMembersValue : public ObjectGuidListCalculatedValue
{
public:
GroupMembersValue(PlayerbotAI* botAI) : ObjectGuidListCalculatedValue(botAI, "group members", 2 * 1000) {}
GuidVector Calculate() override;
};
class IsFollowingPartyValue : public BoolCalculatedValue
{
public:
IsFollowingPartyValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "following party") {}
bool Calculate() override;
};
class IsNearLeaderValue : public BoolCalculatedValue
{
public:
IsNearLeaderValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "near leader") {}
bool Calculate() override;
};
class BoolANDValue : public BoolCalculatedValue, public Qualified
{
public:
BoolANDValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "bool and") {}
bool Calculate() override;
};
class GroupBoolCountValue : public Uint32CalculatedValue, public Qualified
{
public:
GroupBoolCountValue(PlayerbotAI* botAI) : Uint32CalculatedValue(botAI, "group count") {}
uint32 Calculate() override;
};
class GroupBoolANDValue : public BoolCalculatedValue, public Qualified
{
public:
GroupBoolANDValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "group bool and") {}
bool Calculate() override;
};
class GroupBoolORValue : public BoolCalculatedValue, public Qualified
{
public:
GroupBoolORValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "group bool or") {}
bool Calculate() override;
};
class GroupReadyValue : public BoolCalculatedValue, public Qualified
{
public:
GroupReadyValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "group ready", 2 * 2000) {}
bool Calculate() override;
};

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "GuildValues.h"
#include "Playerbots.h"
uint8 PetitionSignsValue::Calculate()
{
if (bot->GetGuildId())
return 0;
std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863));
if (petitions.empty())
return 0;
QueryResult result = CharacterDatabase.Query("SELECT playerguid FROM petition_sign WHERE petitionguid = {}",
petitions.front()->GetGUID().GetCounter());
return result ? (uint8)result->GetRowCount() : 0;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_GUILDVALUES_H
#define _PLAYERBOT_GUILDVALUES_H
#include "Value.h"
class PlayerbotAI;
class PetitionSignsValue : public SingleCalculatedValue<uint8>
{
public:
PetitionSignsValue(PlayerbotAI* botAI) : SingleCalculatedValue<uint8>(botAI, "petition signs") {}
uint8 Calculate();
};
#endif

View File

@@ -0,0 +1,15 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "HasAvailableLootValue.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
bool HasAvailableLootValue::Calculate()
{
return !AI_VALUE(bool, "can loot") &&
AI_VALUE(LootObjectStack*, "available loot")->CanLoot(sPlayerbotAIConfig->lootDistance);
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_HASAVAILABLELOOTVALUE_H
#define _PLAYERBOT_HASAVAILABLELOOTVALUE_H
#include "Value.h"
class PlayerbotAI;
class HasAvailableLootValue : public BoolCalculatedValue
{
public:
HasAvailableLootValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "HasTotemValue.h"
#include "Playerbots.h"
char* strstri(char const* str1, char const* str2);
bool HasTotemValue::Calculate()
{
for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i)
{
if (!bot->m_SummonSlot[i])
{
continue;
}
if (Creature* OldTotem = bot->GetMap()->GetCreature(bot->m_SummonSlot[i]))
{
if (OldTotem->IsSummon() && OldTotem->GetDistance(bot) <= 30.0f)
{
if (strstri(OldTotem->GetName().c_str(), qualifier.c_str()))
return true;
}
}
}
return false;
}
// bool HasTotemValue::Calculate()
// {
// for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i)
// {
// if (!bot->m_SummonSlot[i])
// {
// continue;
// }
// if (Creature* OldTotem = bot->GetMap()->GetCreature(bot->m_SummonSlot[i]))
// {
// if (OldTotem->IsSummon())
// {
// if (strstri(creature->GetName().c_str(), qualifier.c_str()))
// return true;
// }
// }
// }
// GuidVector units = *context->GetValue<GuidVector>("nearest totems");
// for (ObjectGuid const guid : units)
// {
// Unit* unit = botAI->GetUnit(guid);
// if (!unit)
// continue;
// Creature* creature = dynamic_cast<Creature*>(unit);
// if (creature->GetOwner() != bot)
// {
// continue;
// }
// if (strstri(creature->GetName().c_str(), qualifier.c_str()))
// return true;
// }
// return false;
// }

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_HASTOTEMVALUE_H
#define _PLAYERBOT_HASTOTEMVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class HasTotemValue : public BoolCalculatedValue, public Qualified
{
public:
HasTotemValue(PlayerbotAI* botAI, std::string const name = "has totem") : BoolCalculatedValue(botAI, name) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "InvalidTargetValue.h"
#include "AttackersValue.h"
#include "Playerbots.h"
#include "Unit.h"
bool InvalidTargetValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
Unit* enemy = AI_VALUE(Unit*, "enemy player target");
if (target && enemy && target == enemy && target->IsAlive())
return false;
if (target && qualifier == "current target")
{
return target->GetMapId() != bot->GetMapId() || target->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) ||
target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) || target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE_2) ||
!target->IsVisible() || !target->IsAlive() || target->IsPolymorphed() || target->IsCharmed() ||
target->HasFearAura() || target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsFriendlyTo(bot) ||
!AttackersValue::IsValidTarget(target, bot);
}
return !target;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_INVALIDTARGETVALUE_H
#define _PLAYERBOT_INVALIDTARGETVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class InvalidTargetValue : public BoolCalculatedValue, public Qualified
{
public:
InvalidTargetValue(PlayerbotAI* botAI, std::string const name = "invalid target") : BoolCalculatedValue(botAI, name)
{
}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "IsBehindValue.h"
#include <cmath>
#include "Playerbots.h"
bool IsBehindValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target)
return false;
float targetOrientation = target->GetOrientation();
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
if (deltaAngle > M_PI)
deltaAngle -= 2.0f * M_PI; // -PI..PI
return fabs(deltaAngle) > M_PI_2;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ISBEHINDVALUE_H
#define _PLAYERBOT_ISBEHINDVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class IsBehindValue : public BoolCalculatedValue, public Qualified
{
public:
IsBehindValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,18 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "IsFacingValue.h"
#include <cmath>
#include "Playerbots.h"
bool IsFacingValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target)
return false;
return bot->HasInArc(M_PI_2, target);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ISFACINGVALUE_H
#define _PLAYERBOT_ISFACINGVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class IsFacingValue : public BoolCalculatedValue, public Qualified
{
public:
IsFacingValue(PlayerbotAI* botAI, std::string const name = "is facing") : BoolCalculatedValue(botAI, name) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "IsMovingValue.h"
#include "Playerbots.h"
bool IsMovingValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target)
return false;
return target->isMoving();
}
bool IsSwimmingValue::Calculate()
{
Unit* target = AI_VALUE(Unit*, qualifier);
if (!target)
return false;
int8 targetInLiquidState = target->GetLiquidData().Status;
return targetInLiquidState == LIQUID_MAP_UNDER_WATER || (targetInLiquidState == LIQUID_MAP_IN_WATER && target->CanSwim());
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ISMOVINGVALUE_H
#define _PLAYERBOT_ISMOVINGVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class IsMovingValue : public BoolCalculatedValue, public Qualified
{
public:
IsMovingValue(PlayerbotAI* botAI, std::string const name = "is moving") : BoolCalculatedValue(botAI, name) {}
bool Calculate() override;
};
class IsSwimmingValue : public BoolCalculatedValue, public Qualified
{
public:
IsSwimmingValue(PlayerbotAI* botAI, std::string const name = "is swimming") : BoolCalculatedValue(botAI, name) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ItemCountValue.h"
#include "Playerbots.h"
std::vector<Item*> InventoryItemValueBase::Find(std::string const qualifier)
{
std::vector<Item*> result;
Player* bot = InventoryAction::botAI->GetBot();
std::vector<Item*> items = InventoryAction::parseItems(qualifier);
for (Item* item : items)
result.push_back(item);
return result;
}
uint32 ItemCountValue::Calculate()
{
uint32 count = 0;
std::vector<Item*> items = Find(qualifier);
for (Item* item : items)
{
count += item->GetCount();
}
return count;
}
std::vector<Item*> InventoryItemValue::Calculate() { return Find(qualifier); }

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ITEMCOUNTVALUE_H
#define _PLAYERBOT_ITEMCOUNTVALUE_H
#include "InventoryAction.h"
#include "Item.h"
#include "NamedObjectContext.h"
class PlayerbotAI;
class InventoryItemValueBase : public InventoryAction
{
public:
InventoryItemValueBase(PlayerbotAI* botAI) : InventoryAction(botAI, "empty") {}
bool Execute(Event event) override { return false; }
protected:
std::vector<Item*> Find(std::string const qualifier);
};
class ItemCountValue : public Uint32CalculatedValue, public Qualified, InventoryItemValueBase
{
public:
ItemCountValue(PlayerbotAI* botAI, std::string const name = "inventory items")
: Uint32CalculatedValue(botAI, name), InventoryItemValueBase(botAI)
{
}
uint32 Calculate() override;
};
class InventoryItemValue : public CalculatedValue<std::vector<Item*>>, public Qualified, InventoryItemValueBase
{
public:
InventoryItemValue(PlayerbotAI* botAI) : CalculatedValue<std::vector<Item*>>(botAI), InventoryItemValueBase(botAI)
{
}
std::vector<Item*> Calculate() override;
};
#endif

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ItemForSpellValue.h"
#include "Playerbots.h"
#ifndef WIN32
inline int strcmpi(char const* s1, char const* s2)
{
for (; *s1 && *s2 && (toupper(*s1) == toupper(*s2)); ++s1, ++s2)
{
}
return *s1 - *s2;
}
#endif
Item* ItemForSpellValue::Calculate()
{
uint32 spellid = atoi(qualifier.c_str());
if (!spellid)
return nullptr;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
if (!spellInfo)
return nullptr;
Item* itemForSpell = nullptr;
Player* trader = bot->GetTrader();
if (trader)
{
itemForSpell = trader->GetTradeData()->GetItem(TRADE_SLOT_NONTRADED);
if (itemForSpell && itemForSpell->IsFitToSpellRequirements(spellInfo))
return itemForSpell;
}
Player* master = botAI->GetMaster();
if (master)
{
trader = master->GetTrader();
if (trader)
{
itemForSpell = trader->GetTradeData()->GetItem(TRADE_SLOT_NONTRADED);
if (itemForSpell && itemForSpell->IsFitToSpellRequirements(spellInfo))
return itemForSpell;
}
}
// Workaround as some spells have no item mask (e.g. shaman weapon enhancements)
if (!strcmpi(spellInfo->SpellName[0], "rockbiter weapon") ||
!strcmpi(spellInfo->SpellName[0], "flametongue weapon") ||
!strcmpi(spellInfo->SpellName[0], "earthliving weapon") ||
!strcmpi(spellInfo->SpellName[0], "frostbrand weapon") || !strcmpi(spellInfo->SpellName[0], "windfury weapon"))
{
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_MAINHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_OFFHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
return nullptr;
}
if (!(spellInfo->Targets & TARGET_FLAG_ITEM))
return nullptr;
if (!strcmpi(spellInfo->SpellName[0], "disenchant"))
return nullptr;
if (!strcmpi(spellInfo->SpellName[0], "pick lock"))
return nullptr;
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
{
itemForSpell = GetItemFitsToSpellRequirements(slot, spellInfo);
if (itemForSpell)
return itemForSpell;
}
return nullptr;
}
Item* ItemForSpellValue::GetItemFitsToSpellRequirements(uint8 slot, SpellInfo const* spellInfo)
{
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return nullptr;
if (itemForSpell->IsFitToSpellRequirements(spellInfo))
return itemForSpell;
return nullptr;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ITEMFORSPELLVALUE_H
#define _PLAYERBOT_ITEMFORSPELLVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class Item;
class PlayerbotAI;
class SpellInfo;
class ItemForSpellValue : public CalculatedValue<Item*>, public Qualified
{
public:
ItemForSpellValue(PlayerbotAI* botAI, std::string const name = "item for spell")
: CalculatedValue<Item*>(botAI, name, 1)
{
}
Item* Calculate() override;
private:
Item* GetItemFitsToSpellRequirements(uint8 slot, SpellInfo const* spellInfo);
};
#endif

View File

@@ -0,0 +1,915 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ItemUsageValue.h"
#include "AiFactory.h"
#include "ChatHelper.h"
#include "GuildTaskMgr.h"
#include "Item.h"
#include "LootObjectStack.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "RandomItemMgr.h"
#include "ServerFacade.h"
#include "StatsWeightCalculator.h"
ItemUsage ItemUsageValue::Calculate()
{
uint32 itemId = 0;
uint32 randomPropertyId = 0;
size_t pos = qualifier.find(",");
if (pos != std::string::npos)
{
itemId = atoi(qualifier.substr(0, pos).c_str());
randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
}
else
{
itemId = atoi(qualifier.c_str());
}
if (!itemId)
return ITEM_USAGE_NONE;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
return ITEM_USAGE_NONE;
if (botAI->HasActivePlayerMaster())
{
if (IsItemUsefulForSkill(proto) || IsItemNeededForSkill(proto))
return ITEM_USAGE_SKILL;
}
else
{
bool needItem = false;
if (IsItemNeededForSkill(proto))
needItem = true;
else
{
bool lowBagSpace = AI_VALUE(uint8, "bag space") > 50;
if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_MISC ||
proto->Class == ITEM_CLASS_REAGENT)
needItem = IsItemNeededForUsefullSpell(proto, lowBagSpace);
else if (proto->Class == ITEM_CLASS_RECIPE)
{
if (bot->HasSpell(proto->Spells[2].SpellId))
needItem = false;
else
needItem = bot->BotCanUseItem(proto) == EQUIP_ERR_OK;
}
}
if (needItem)
{
float stacks = CurrentStacks(proto);
if (stacks < 1)
return ITEM_USAGE_SKILL; // Buy more.
if (stacks < 2)
return ITEM_USAGE_KEEP; // Keep current amount.
}
}
if (proto->Class == ITEM_CLASS_KEY)
return ITEM_USAGE_USE;
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount))
{
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
if (!foodType.empty() && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{
float stacks = BetterStacks(proto, foodType);
if (stacks < 2)
{
stacks += CurrentStacks(proto);
if (stacks < 2)
return ITEM_USAGE_USE; // Buy some to get to 2 stacks
else if (stacks < 3) // Keep the item if less than 3 stacks
return ITEM_USAGE_KEEP;
}
}
}
if (bot->GetGuildId() && sGuildTaskMgr->IsGuildTaskItem(itemId, bot->GetGuildId()))
return ITEM_USAGE_GUILD_TASK;
ItemUsage equip = QueryItemUsageForEquip(proto, randomPropertyId);
if (equip != ITEM_USAGE_NONE)
return equip;
// Get item instance to check if it's soulbound
Item* item = bot->GetItemByEntry(proto->ItemId);
bool isSoulbound = item && item->IsSoulBound();
if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) &&
botAI->HasSkill(SKILL_ENCHANTING) &&
proto->Quality >= ITEM_QUALITY_UNCOMMON)
{
// Retrieve the bot's Enchanting skill level
uint32 enchantingSkill = bot->GetSkillValue(SKILL_ENCHANTING);
// Check if the bot has a high enough skill to disenchant this item
if (proto->RequiredDisenchantSkill > 0 && enchantingSkill < proto->RequiredDisenchantSkill)
return ITEM_USAGE_NONE; // Not skilled enough to disenchant
// BoE (Bind on Equip) items should NOT be disenchanted unless they are already bound
if (proto->Bonding == BIND_WHEN_PICKED_UP || (proto->Bonding == BIND_WHEN_EQUIPPED && isSoulbound))
return ITEM_USAGE_DISENCHANT;
}
Player* master = botAI->GetMaster();
bool isSelfBot = (master == bot);
bool botNeedsItemForQuest = IsItemUsefulForQuest(bot, proto);
bool masterNeedsItemForQuest = master && sPlayerbotAIConfig->syncQuestWithPlayer && IsItemUsefulForQuest(master, proto);
// Identify the source of loot
LootObject lootObject = AI_VALUE(LootObject, "loot target");
// Get GUID of loot source
ObjectGuid lootGuid = lootObject.guid;
// Check if loot source is an item
bool isLootFromItem = lootGuid.IsItem();
// If the loot is from an item in the bots bags, ignore syncQuestWithPlayer
if (isLootFromItem && botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
// If the bot is NOT acting alone and the master needs this quest item, defer to the master
if (!isSelfBot && masterNeedsItemForQuest)
{
return ITEM_USAGE_NONE;
}
// If the bot itself needs the item for a quest, allow looting
if (botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST;
}
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{
if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR)
{
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
{
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
}
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
{
return ITEM_USAGE_EQUIP;
}
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
{
return ITEM_USAGE_NONE;
}
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
}
}
// Need to add something like free bagspace or item value.
if (proto->SellPrice > 0)
{
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
{
return ITEM_USAGE_AH;
}
else
{
return ITEM_USAGE_VENDOR;
}
}
return ITEM_USAGE_NONE;
}
ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto, int32 randomPropertyId)
{
if (bot->BotCanUseItem(itemProto) != EQUIP_ERR_OK)
return ITEM_USAGE_NONE;
if (itemProto->InventoryType == INVTYPE_NON_EQUIP)
return ITEM_USAGE_NONE;
Item* pItem = Item::CreateItem(itemProto->ItemId, 1, bot, false, 0, true);
if (!pItem)
return ITEM_USAGE_NONE;
uint16 dest;
InventoryResult result = botAI->CanEquipItem(NULL_SLOT, dest, pItem, true, true);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;
if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
return ITEM_USAGE_NONE;
}
// Check is unique items are equipped or not
bool needToCheckUnique = false;
if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
needToCheckUnique = true;
}
else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
needToCheckUnique = true;
}
if (needToCheckUnique)
{
// Count the total number of the item (equipped + in bags)
uint32 totalItemCount = bot->GetItemCount(itemProto->ItemId, true);
// Count the number of the item in bags only
uint32 bagItemCount = bot->GetItemCount(itemProto->ItemId, false);
// Determine if the unique item is already equipped
bool isEquipped = (totalItemCount > bagItemCount);
if (isEquipped)
{
return ITEM_USAGE_NONE; // Item is already equipped
}
// If not equipped, continue processing
}
if (itemProto->Class == ITEM_CLASS_QUIVER)
if (bot->getClass() != CLASS_HUNTER)
return ITEM_USAGE_NONE;
if (itemProto->Class == ITEM_CLASS_CONTAINER)
{
if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER)
return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and
// only replace if non-bag is larger than bag.
if (GetSmallestBagSize() >= itemProto->ContainerSlots)
return ITEM_USAGE_NONE;
return ITEM_USAGE_EQUIP;
}
bool shouldEquip = false;
// uint32 statWeight = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId);
StatsWeightCalculator calculator(bot);
calculator.SetItemSetBonus(false);
calculator.SetOverflowPenalty(false);
float itemScore = calculator.CalculateItem(itemProto->ItemId, randomPropertyId);
if (itemScore)
shouldEquip = true;
if (itemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), itemProto))
shouldEquip = false;
if (itemProto->Class == ITEM_CLASS_ARMOR &&
!sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), itemProto))
shouldEquip = false;
uint8 possibleSlots = 1;
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead
// This occurs with unique items that are already in the bots bags when CanEquipItem is called
if (dest == 0)
{
if (dstSlot != NULL_SLOT)
{
// Construct dest from dstSlot
dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
}
}
if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1)
{
possibleSlots = 2;
}
// Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false;
bool isValidTGWeapon = false;
if (dstSlot == EQUIPMENT_SLOT_MAINHAND)
{
Item* currentWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
have2HWeapon = currentWeapon && currentWeapon->GetTemplate()->InventoryType == INVTYPE_2HWEAPON;
// Determine if the new weapon is a valid Titan Grip weapon
isValidTGWeapon = (itemProto->SubClass == ITEM_SUBCLASS_WEAPON_AXE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MACE2 ||
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
// If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe.
if (bot->CanTitanGrip())
{
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all.
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and:
// - The weapon is not 2H and we currently don't have a 2H weapon equipped
// OR
// - The bot can Titan Grip and it is a valid TG weapon
// Then we can consider the offhand slot as well.
if (bot->CanDualWield() &&
((itemProto->InventoryType != INVTYPE_2HWEAPON && !have2HWeapon) ||
(bot->CanTitanGrip() && isValidTGWeapon)))
{
possibleSlots = 2;
}
}
for (uint8 i = 0; i < possibleSlots; i++)
{
bool shouldEquipInSlot = shouldEquip;
Item* oldItem = bot->GetItemByPos(dest + i);
// No item equipped
if (!oldItem)
{
if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP;
else
{
return ITEM_USAGE_BAD_EQUIP;
}
}
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
float oldScore = calculator.CalculateItem(oldItemProto->ItemId, oldItem->GetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID));
if (oldItem)
{
// uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore)
{
shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig->equipUpgradeThreshold;
}
}
// Bigger quiver
if (itemProto->Class == ITEM_CLASS_QUIVER)
{
if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots)
{
return ITEM_USAGE_EQUIP;
}
else
{
return ITEM_USAGE_NONE;
}
}
bool existingShouldEquip = true;
if (oldItemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr->CanEquipWeapon(bot->getClass(), oldItemProto))
existingShouldEquip = false;
if (oldItemProto->Class == ITEM_CLASS_ARMOR &&
!sRandomItemMgr->CanEquipArmor(bot->getClass(), bot->GetLevel(), oldItemProto))
existingShouldEquip = false;
// uint32 oldItemPower = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId);
// uint32 newItemPower = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId);
// Compare items based on item level, quality or itemId.
bool isBetter = false;
if (itemScore > oldScore)
isBetter = true;
// else if (newItemPower == oldScore && itemProto->Quality > oldItemProto->Quality)
// isBetter = true;
// else if (newItemPower == oldScore && itemProto->Quality == oldItemProto->Quality && itemProto->ItemId >
// oldItemProto->ItemId)
// isBetter = true;
Item* item = CurrentItem(itemProto);
bool itemIsBroken =
item && item->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0;
bool oldItemIsBroken =
oldItem->GetUInt32Value(ITEM_FIELD_DURABILITY) == 0 && oldItem->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0;
if (itemProto->ItemId != oldItemProto->ItemId && (shouldEquipInSlot || !existingShouldEquip) && isBetter)
{
switch (itemProto->Class)
{
case ITEM_CLASS_ARMOR:
if (oldItemProto->SubClass <= itemProto->SubClass)
{
// Need to add some logic to check second slot before returning, but as it happens, all three of these
// return vals will result in an attempted equip action so it wouldn't have much effect currently
if (itemIsBroken && !oldItemIsBroken)
return ITEM_USAGE_BROKEN_EQUIP;
else if (shouldEquipInSlot)
return ITEM_USAGE_REPLACE;
else
return ITEM_USAGE_BAD_EQUIP;
break;
}
default:
{
if (itemIsBroken && !oldItemIsBroken)
return ITEM_USAGE_BROKEN_EQUIP;
else if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP;
else
return ITEM_USAGE_BAD_EQUIP;
}
}
}
// Item is not better but current item is broken and new one is not.
if (oldItemIsBroken && !itemIsBroken)
return ITEM_USAGE_EQUIP;
}
return ITEM_USAGE_NONE;
}
// Return smaltest bag size equipped
uint32 ItemUsageValue::GetSmallestBagSize()
{
int8 curSlot = 0;
uint32 curSlots = 0;
for (uint8 bag = INVENTORY_SLOT_BAG_START + 1; bag < INVENTORY_SLOT_BAG_END; ++bag)
{
if (Bag const* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag))
{
if (curSlot > 0 && curSlots < pBag->GetBagSize())
continue;
curSlot = pBag->GetSlot();
curSlots = pBag->GetBagSize();
}
else
return 0;
}
return curSlots;
}
bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* proto)
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI)
return false;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
uint32 entry = player->GetQuestSlotQuestId(slot);
Quest const* quest = sObjectMgr->GetQuestTemplate(entry);
if (!quest)
continue;
// Check if the item itself is needed for the quest
for (uint8 i = 0; i < 4; i++)
{
if (quest->RequiredItemId[i] == proto->ItemId)
{
if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i])
continue;
return true; // Item is directly required for a quest
}
}
// Check if the item has spells that create a required quest item
for (uint8 i = 0; i < MAX_ITEM_SPELLS; i++)
{
uint32 spellId = proto->Spells[i].SpellId;
if (!spellId)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
for (uint8 effectIndex = 0; effectIndex < MAX_SPELL_EFFECTS; effectIndex++)
{
if (spellInfo->Effects[effectIndex].Effect == SPELL_EFFECT_CREATE_ITEM)
{
uint32 createdItemId = spellInfo->Effects[effectIndex].ItemType;
// Check if the created item is required for a quest
for (uint8 j = 0; j < 4; j++)
{
if (quest->RequiredItemId[j] == createdItemId)
{
if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j])
continue;
return true; // Item is useful because it creates a required quest item
}
}
}
}
}
}
return false; // Item is not useful for any active quests
}
bool ItemUsageValue::IsItemNeededForSkill(ItemTemplate const* proto)
{
switch (proto->ItemId)
{
case 756: // Tunnel Pick
return botAI->HasSkill(SKILL_MINING);
case 778: // Kobold Excavation Pick
return botAI->HasSkill(SKILL_MINING);
case 1819: // Gouging Pick
return botAI->HasSkill(SKILL_MINING);
case 1893: // Miner's Revenge
return botAI->HasSkill(SKILL_MINING);
case 1959: // Cold Iron Pick
return botAI->HasSkill(SKILL_MINING);
case 2901: // Mining Pick
return botAI->HasSkill(SKILL_MINING);
case 9465: // Digmaster 5000
return botAI->HasSkill(SKILL_MINING);
case 20723: // Brann's Trusty Pick
return botAI->HasSkill(SKILL_MINING);
case 40772: // Gnomish Army Knife
return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_ENGINEERING) || botAI->HasSkill(SKILL_BLACKSMITHING) || botAI->HasSkill(SKILL_COOKING) || botAI->HasSkill(SKILL_SKINNING);
case 40892: // Hammer Pick
return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_BLACKSMITHING);
case 40893: // Bladed Pickaxe
return botAI->HasSkill(SKILL_MINING) || botAI->HasSkill(SKILL_SKINNING);
case 5956: // Blacksmith Hammer
return botAI->HasSkill(SKILL_BLACKSMITHING) || botAI->HasSkill(SKILL_ENGINEERING);
case 6219: // Arclight Spanner
return botAI->HasSkill(SKILL_ENGINEERING);
case 6218: // Runed copper rod
return botAI->HasSkill(SKILL_ENCHANTING);
case 6339: // Runed silver rod
return botAI->HasSkill(SKILL_ENCHANTING);
case 11130: // Runed golden rod
return botAI->HasSkill(SKILL_ENCHANTING);
case 11145: // Runed truesilver rod
return botAI->HasSkill(SKILL_ENCHANTING);
case 16207: // Runed Arcanite Rod
return botAI->HasSkill(SKILL_ENCHANTING);
case 7005: // Skinning Knife
return botAI->HasSkill(SKILL_SKINNING);
case 12709:
return botAI->HasSkill(SKILL_SKINNING);
case 19901:
return botAI->HasSkill(SKILL_SKINNING);
case 4471: // Flint and Tinder
return botAI->HasSkill(SKILL_COOKING);
case 4470: // Simple Wood
return botAI->HasSkill(SKILL_COOKING);
case 6256: // Fishing Rod
return botAI->HasSkill(SKILL_FISHING);
}
return false;
}
bool ItemUsageValue::IsItemUsefulForSkill(ItemTemplate const* proto)
{
switch (proto->Class)
{
case ITEM_CLASS_TRADE_GOODS:
case ITEM_CLASS_MISC:
case ITEM_CLASS_REAGENT:
case ITEM_CLASS_GEM:
{
if (botAI->HasSkill(SKILL_TAILORING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_TAILORING))
return true;
if (botAI->HasSkill(SKILL_LEATHERWORKING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_LEATHERWORKING))
return true;
if (botAI->HasSkill(SKILL_ENGINEERING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ENGINEERING))
return true;
if (botAI->HasSkill(SKILL_BLACKSMITHING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_BLACKSMITHING))
return true;
if (botAI->HasSkill(SKILL_ALCHEMY) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ALCHEMY))
return true;
if (botAI->HasSkill(SKILL_ENCHANTING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_ENCHANTING))
return true;
if (botAI->HasSkill(SKILL_FISHING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_FISHING))
return true;
if (botAI->HasSkill(SKILL_FIRST_AID) && RandomItemMgr::IsUsedBySkill(proto, SKILL_FIRST_AID))
return true;
if (botAI->HasSkill(SKILL_COOKING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_COOKING))
return true;
if (botAI->HasSkill(SKILL_JEWELCRAFTING) && RandomItemMgr::IsUsedBySkill(proto, SKILL_JEWELCRAFTING))
return true;
if (botAI->HasSkill(SKILL_MINING) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_MINING) ||
RandomItemMgr::IsUsedBySkill(proto, SKILL_BLACKSMITHING) ||
RandomItemMgr::IsUsedBySkill(proto, SKILL_JEWELCRAFTING) ||
RandomItemMgr::IsUsedBySkill(proto, SKILL_ENGINEERING)))
return true;
if (botAI->HasSkill(SKILL_SKINNING) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_SKINNING) ||
RandomItemMgr::IsUsedBySkill(proto, SKILL_LEATHERWORKING)))
return true;
if (botAI->HasSkill(SKILL_HERBALISM) && (RandomItemMgr::IsUsedBySkill(proto, SKILL_HERBALISM) ||
RandomItemMgr::IsUsedBySkill(proto, SKILL_ALCHEMY)))
return true;
return false;
}
case ITEM_CLASS_RECIPE:
{
if (bot->HasSpell(proto->Spells[2].SpellId))
break;
switch (proto->SubClass)
{
case ITEM_SUBCLASS_LEATHERWORKING_PATTERN:
return botAI->HasSkill(SKILL_LEATHERWORKING);
case ITEM_SUBCLASS_TAILORING_PATTERN:
return botAI->HasSkill(SKILL_TAILORING);
case ITEM_SUBCLASS_ENGINEERING_SCHEMATIC:
return botAI->HasSkill(SKILL_ENGINEERING);
case ITEM_SUBCLASS_BLACKSMITHING:
return botAI->HasSkill(SKILL_BLACKSMITHING);
case ITEM_SUBCLASS_COOKING_RECIPE:
return botAI->HasSkill(SKILL_COOKING);
case ITEM_SUBCLASS_ALCHEMY_RECIPE:
return botAI->HasSkill(SKILL_ALCHEMY);
case ITEM_SUBCLASS_FIRST_AID_MANUAL:
return botAI->HasSkill(SKILL_FIRST_AID);
case ITEM_SUBCLASS_ENCHANTING_FORMULA:
return botAI->HasSkill(SKILL_ENCHANTING);
case ITEM_SUBCLASS_FISHING_MANUAL:
return botAI->HasSkill(SKILL_FISHING);
}
}
}
return false;
}
bool ItemUsageValue::IsItemNeededForUsefullSpell(ItemTemplate const* proto, bool checkAllReagents)
{
for (auto spellId : SpellsUsingItem(proto->ItemId, bot))
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
if (checkAllReagents && !HasItemsNeededForSpell(spellId, proto))
continue;
if (SpellGivesSkillUp(spellId, bot))
return true;
uint32 newItemId = spellInfo->Effects[EFFECT_0].ItemType;
if (newItemId && newItemId != proto->ItemId)
{
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", newItemId);
if (usage != ITEM_USAGE_REPLACE && usage != ITEM_USAGE_EQUIP && usage != ITEM_USAGE_AMMO &&
usage != ITEM_USAGE_QUEST && usage != ITEM_USAGE_SKILL && usage != ITEM_USAGE_USE)
continue;
return true;
}
}
return false;
}
bool ItemUsageValue::HasItemsNeededForSpell(uint32 spellId, ItemTemplate const* proto)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
for (uint8 i = 0; i < MAX_SPELL_REAGENTS; i++)
if (spellInfo->ReagentCount[i] > 0 && spellInfo->Reagent[i])
{
if (proto && proto->ItemId == spellInfo->Reagent[i] &&
spellInfo->ReagentCount[i] == 1) // If we only need 1 item then current item does not need to be
// checked since we are looting/buying or already have it.
continue;
ItemTemplate const* reqProto = sObjectMgr->GetItemTemplate(spellInfo->Reagent[i]);
uint32 count = AI_VALUE2(uint32, "item count", reqProto->Name1);
if (count < spellInfo->ReagentCount[i])
return false;
}
return true;
}
Item* ItemUsageValue::CurrentItem(ItemTemplate const* proto)
{
Item* bestItem = nullptr;
std::vector<Item*> found = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatItem(proto));
for (auto item : found)
{
if (bestItem && item->GetUInt32Value(ITEM_FIELD_DURABILITY) < bestItem->GetUInt32Value(ITEM_FIELD_DURABILITY))
continue;
if (bestItem && item->GetCount() < bestItem->GetCount())
continue;
bestItem = item;
}
return bestItem;
}
float ItemUsageValue::CurrentStacks(ItemTemplate const* proto)
{
uint32 maxStack = proto->GetMaxStackSize();
std::vector<Item*> found = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatItem(proto));
float itemCount = 0;
for (auto stack : found)
{
itemCount += stack->GetCount();
}
return itemCount / maxStack;
}
float ItemUsageValue::BetterStacks(ItemTemplate const* proto, std::string const itemType)
{
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", itemType);
float stacks = 0;
for (auto& otherItem : items)
{
ItemTemplate const* otherProto = otherItem->GetTemplate();
if (otherProto->Class != proto->Class || otherProto->SubClass != proto->SubClass)
continue;
if (otherProto->ItemLevel < proto->ItemLevel)
continue;
if (otherProto->ItemId == proto->ItemId)
continue;
stacks += CurrentStacks(otherProto);
}
return stacks;
}
std::vector<uint32> ItemUsageValue::SpellsUsingItem(uint32 itemId, Player* bot)
{
std::vector<uint32> retSpells;
PlayerSpellMap const& spellMap = bot->GetSpellMap();
for (auto& spell : spellMap)
{
uint32 spellId = spell.first;
if (spell.second->State == PLAYERSPELL_REMOVED || !spell.second->Active)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
if (spellInfo->IsPassive())
continue;
if (spellInfo->Effects[EFFECT_0].Effect != SPELL_EFFECT_CREATE_ITEM)
continue;
for (uint8 i = 0; i < MAX_SPELL_REAGENTS; i++)
if (spellInfo->ReagentCount[i] > 0 && spellInfo->Reagent[i] == itemId)
retSpells.push_back(spellId);
}
return retSpells;
}
inline int32 SkillGainChance(uint32 SkillValue, uint32 GrayLevel, uint32 GreenLevel, uint32 YellowLevel)
{
if (SkillValue >= GrayLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREY) * 10;
if (SkillValue >= GreenLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_GREEN) * 10;
if (SkillValue >= YellowLevel)
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_YELLOW) * 10;
return sWorld->getIntConfig(CONFIG_SKILL_CHANCE_ORANGE) * 10;
}
bool ItemUsageValue::SpellGivesSkillUp(uint32 spellId, Player* bot)
{
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
{
SkillLineAbilityEntry const* skill = _spell_idx->second;
if (skill->SkillLine)
{
uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine);
uint32 craft_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING);
if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh,
(skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2,
skill->TrivialSkillLineRankLow) > 0)
return true;
}
}
return false;
}
std::string const ItemUsageValue::GetConsumableType(ItemTemplate const* proto, bool hasMana)
{
std::string const foodType = "";
if ((proto->SubClass == ITEM_SUBCLASS_CONSUMABLE || proto->SubClass == ITEM_SUBCLASS_FOOD))
{
if (proto->Spells[0].SpellCategory == 11)
return "food";
else if (proto->Spells[0].SpellCategory == 59 && hasMana)
return "drink";
}
if (proto->SubClass == ITEM_SUBCLASS_POTION || proto->SubClass == ITEM_SUBCLASS_FLASK)
{
for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId);
if (spellInfo)
for (int i = 0; i < 3; i++)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_ENERGIZE && hasMana)
return "mana potion";
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL)
return "healing potion";
}
}
}
if (proto->SubClass == ITEM_SUBCLASS_BANDAGE)
{
return "bandage";
}
return "";
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ITEMUSAGEVALUE_H
#define _PLAYERBOT_ITEMUSAGEVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class Item;
class Player;
class PlayerbotAI;
struct ItemTemplate;
enum ItemUsage : uint32
{
ITEM_USAGE_NONE = 0,
ITEM_USAGE_EQUIP = 1,
ITEM_USAGE_REPLACE = 2,
ITEM_USAGE_BAD_EQUIP = 3,
ITEM_USAGE_BROKEN_EQUIP = 4,
ITEM_USAGE_QUEST = 5,
ITEM_USAGE_SKILL = 6,
ITEM_USAGE_USE = 7,
ITEM_USAGE_GUILD_TASK = 8,
ITEM_USAGE_DISENCHANT = 9,
ITEM_USAGE_AH = 10,
ITEM_USAGE_KEEP = 11,
ITEM_USAGE_VENDOR = 12,
ITEM_USAGE_AMMO = 13
};
class ItemUsageValue : public CalculatedValue<ItemUsage>, public Qualified
{
public:
ItemUsageValue(PlayerbotAI* botAI, std::string const name = "item usage") : CalculatedValue<ItemUsage>(botAI, name)
{
}
ItemUsage Calculate() override;
private:
ItemUsage QueryItemUsageForEquip(ItemTemplate const* proto, int32 randomPropertyId = 0);
uint32 GetSmallestBagSize();
bool IsItemUsefulForQuest(Player* player, ItemTemplate const* proto);
bool IsItemNeededForSkill(ItemTemplate const* proto);
bool IsItemUsefulForSkill(ItemTemplate const* proto);
bool IsItemNeededForUsefullSpell(ItemTemplate const* proto, bool checkAllReagents = false);
bool HasItemsNeededForSpell(uint32 spellId, ItemTemplate const* proto);
Item* CurrentItem(ItemTemplate const* proto);
float CurrentStacks(ItemTemplate const* proto);
float BetterStacks(ItemTemplate const* proto, std::string const usageType = "");
public:
static std::vector<uint32> SpellsUsingItem(uint32 itemId, Player* bot);
static bool SpellGivesSkillUp(uint32 spellId, Player* bot);
static std::string const GetConsumableType(ItemTemplate const* proto, bool hasMana);
};
#endif

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LastMovementValue.h"
#include "Playerbots.h"
#include "Timer.h"
LastMovement::LastMovement() { clear(); }
LastMovement::LastMovement(LastMovement& other)
: taxiNodes(other.taxiNodes),
taxiMaster(other.taxiMaster),
lastFollow(other.lastFollow),
lastAreaTrigger(other.lastAreaTrigger),
lastMoveToX(other.lastMoveToX),
lastMoveToY(other.lastMoveToY),
lastMoveToZ(other.lastMoveToZ),
lastMoveToOri(other.lastMoveToOri),
lastFlee(other.lastFlee)
{
lastMoveShort = other.lastMoveShort;
nextTeleport = other.nextTeleport;
lastPath = other.lastPath;
priority = other.priority;
}
void LastMovement::clear()
{
lastMoveShort = WorldPosition();
lastPath.clear();
lastMoveToMapId = 0;
lastMoveToX = 0;
lastMoveToY = 0;
lastMoveToZ = 0;
lastMoveToOri = 0;
lastFollow = nullptr;
lastAreaTrigger = 0;
lastFlee = 0;
nextTeleport = 0;
msTime = 0;
lastdelayTime = 0;
priority = MovementPriority::MOVEMENT_NORMAL;
}
void LastMovement::Set(Unit* follow)
{
Set(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
setShort(WorldPosition());
setPath(TravelPath());
lastFollow = follow;
}
void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori, float delayTime, MovementPriority pri)
{
lastMoveToMapId = mapId;
lastMoveToX = x;
lastMoveToY = y;
lastMoveToZ = z;
lastMoveToOri = ori;
lastFollow = nullptr;
lastMoveShort = WorldPosition(mapId, x, y, z, ori);
msTime = getMSTime();
lastdelayTime = delayTime;
priority = pri;
}
void LastMovement::setShort(WorldPosition point)
{
lastMoveShort = point;
lastFollow = nullptr;
}
void LastMovement::setPath(TravelPath path) { lastPath = path; }

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LASTMOVEMENTVALUE_H
#define _PLAYERBOT_LASTMOVEMENTVALUE_H
#include "ObjectGuid.h"
#include "TravelNode.h"
#include "Value.h"
class PlayerbotAI;
class Unit;
// High priority movement can override the previous low priority one
enum class MovementPriority
{
MOVEMENT_IDLE,
MOVEMENT_WANDER,
MOVEMENT_NORMAL,
MOVEMENT_COMBAT,
MOVEMENT_FORCED
};
class LastMovement
{
public:
LastMovement();
LastMovement(LastMovement& other);
LastMovement& operator=(LastMovement const& other)
{
taxiNodes = other.taxiNodes;
taxiMaster = other.taxiMaster;
lastFollow = other.lastFollow;
lastAreaTrigger = other.lastAreaTrigger;
lastMoveShort = other.lastMoveShort;
lastPath = other.lastPath;
nextTeleport = other.nextTeleport;
priority = other.priority;
return *this;
};
void clear();
void Set(Unit* follow);
void Set(uint32 mapId, float x, float y, float z, float ori, float delayTime, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
void setShort(WorldPosition point);
void setPath(TravelPath path);
std::vector<uint32> taxiNodes;
ObjectGuid taxiMaster;
Unit* lastFollow;
uint32 lastAreaTrigger;
time_t lastFlee;
uint32 lastMoveToMapId;
float lastMoveToX;
float lastMoveToY;
float lastMoveToZ;
float lastMoveToOri;
float lastdelayTime;
WorldPosition lastMoveShort;
uint32 msTime;
MovementPriority priority;
TravelPath lastPath;
time_t nextTeleport;
std::future<TravelPath> future;
};
class LastMovementValue : public ManualSetValue<LastMovement&>
{
public:
LastMovementValue(PlayerbotAI* botAI) : ManualSetValue<LastMovement&>(botAI, data) {}
private:
LastMovement data = LastMovement();
};
class StayTimeValue : public ManualSetValue<time_t>
{
public:
StayTimeValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, 0) {}
};
#endif

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LASTSAIDVALUE_H
#define _PLAYERBOT_LASTSAIDVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class LastSaidValue : public ManualSetValue<time_t>, public Qualified
{
public:
LastSaidValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, time(nullptr) - 120, "last said") {}
};
class LastEmoteValue : public ManualSetValue<time_t>, public Qualified
{
public:
LastEmoteValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, time(nullptr) - 120, "last emote") {}
};
#endif

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LASTSPELLCASTTIMEVALUE_H
#define _PLAYERBOT_LASTSPELLCASTTIMEVALUE_H
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
class LastSpellCastTimeValue : public ManualSetValue<time_t>, public Qualified
{
public:
LastSpellCastTimeValue(PlayerbotAI* botAI) : ManualSetValue<time_t>(botAI, 0), Qualified() {}
};
#endif

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LastSpellCastValue.h"
#include "Playerbots.h"
LastSpellCast::LastSpellCast() : id(0), timer(0) {}
void LastSpellCast::Set(uint32 newId, ObjectGuid newTarget, time_t newTime)
{
id = newId;
target = newTarget;
timer = newTime;
}
void LastSpellCast::Reset()
{
id = 0;
target.Clear();
timer = 0;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LASTSPELLCASTVALUE_H
#define _PLAYERBOT_LASTSPELLCASTVALUE_H
#include "Value.h"
class PlayerbotAI;
class LastSpellCast
{
public:
LastSpellCast();
void Set(uint32 id, ObjectGuid target, time_t time);
void Reset();
uint32 id;
ObjectGuid target;
time_t timer;
};
class LastSpellCastValue : public ManualSetValue<LastSpellCast&>
{
public:
LastSpellCastValue(PlayerbotAI* botAI, std::string const name = "last spell cast")
: ManualSetValue<LastSpellCast&>(botAI, data, name)
{
}
private:
LastSpellCast data;
};
#endif

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LeastHpTargetValue.h"
#include "AttackersValue.h"
#include "Playerbots.h"
class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy
{
public:
FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {}
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{
Player* bot = botAI->GetBot();
if (IsCcTarget(attacker))
return;
if (!result || result->GetHealth() > attacker->GetHealth())
result = attacker;
}
protected:
float minHealth;
};
Unit* LeastHpTargetValue::Calculate()
{
FindLeastHpTargetStrategy strategy(botAI);
return FindTarget(&strategy);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LEASTHPTARGETVALUE_H
#define _PLAYERBOT_LEASTHPTARGETVALUE_H
#include "TargetValue.h"
class PlayerbotAI;
class Unit;
class LeastHpTargetValue : public TargetValue
{
public:
LeastHpTargetValue(PlayerbotAI* botAI, std::string const name = "least hp target") : TargetValue(botAI, name) {}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LFGVALUES_H
#define _PLAYERBOT_LFGVALUES_H
#include "Value.h"
class PlayerbotAI;
class LfgProposalValue : public ManualSetValue<uint32>
{
public:
LfgProposalValue(PlayerbotAI* botAI) : ManualSetValue<uint32>(botAI, 0, "lfg proposal") {}
};
#endif

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LineTargetValue.h"
#include "Playerbots.h"
Unit* LineTargetValue::Calculate()
{
Player* master = GetMaster();
if (!master)
return nullptr;
Group* group = master->GetGroup();
if (!group)
return nullptr;
Player* prev = master;
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
{
Player* player = ObjectAccessor::FindPlayer(itr->guid);
if (!player || !player->IsAlive() || player == master)
continue;
if (player == bot)
return prev;
prev = player;
}
return master;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LINETARGETVALUE_H
#define _PLAYERBOT_LINETARGETVALUE_H
#include "Value.h"
class PlayerbotAI;
class Unit;
class LineTargetValue : public UnitCalculatedValue
{
public:
LineTargetValue(PlayerbotAI* botAI, std::string const name = "line target") : UnitCalculatedValue(botAI, name) {}
Unit* Calculate() override;
};
#endif

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LOGLEVELVALUE_H
#define _PLAYERBOT_LOGLEVELVALUE_H
#include "Value.h"
class PlayerbotAI;
class LogLevelValue : public ManualSetValue<LogLevel>
{
public:
LogLevelValue(PlayerbotAI* botAI, std::string const name = "log level")
: ManualSetValue<LogLevel>(botAI, LOG_LEVEL_DEBUG, name)
{
}
};
#endif

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LootStrategyValue.h"
#include "AiObjectContext.h"
#include "ItemUsageValue.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
class NormalLootStrategy : public LootStrategy
{
public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override
{
// Identify the source of loot, loot it if the source is an item in the bots inventory
LootObject lootObject = AI_VALUE(LootObject, "loot target");
ObjectGuid lootGuid = lootObject.guid;
if (lootGuid.IsItem())
{
return true;
}
// Otherwise, continue with the normal loot logic
std::ostringstream out;
out << proto->ItemId;
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str());
return usage != ITEM_USAGE_NONE;
}
std::string const GetName() override { return "normal"; }
};
class GrayLootStrategy : public NormalLootStrategy
{
public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override
{
return NormalLootStrategy::CanLoot(proto, context) || proto->Quality == ITEM_QUALITY_POOR;
}
std::string const GetName() override { return "gray"; }
};
class DisenchantLootStrategy : public NormalLootStrategy
{
public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override
{
return NormalLootStrategy::CanLoot(proto, context) ||
(proto->Quality >= ITEM_QUALITY_UNCOMMON && proto->Bonding != BIND_WHEN_PICKED_UP &&
(proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON));
}
std::string const GetName() override { return "disenchant"; }
};
class AllLootStrategy : public LootStrategy
{
public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override { return true; }
std::string const GetName() override { return "all"; }
};
LootStrategyValue::~LootStrategyValue()
{
// delete defaultValue;
}
LootStrategy* LootStrategyValue::normal = new NormalLootStrategy();
LootStrategy* LootStrategyValue::gray = new GrayLootStrategy();
LootStrategy* LootStrategyValue::disenchant = new DisenchantLootStrategy();
LootStrategy* LootStrategyValue::all = new AllLootStrategy();
LootStrategy* LootStrategyValue::instance(std::string const strategy)
{
if (strategy == "*" || strategy == "all")
return all;
if (strategy == "g" || strategy == "gray")
return gray;
if (strategy == "d" || strategy == "e" || strategy == "disenchant" || strategy == "enchant")
return disenchant;
return normal;
}
std::string const LootStrategyValue::Save() { return value ? value->GetName() : "?"; }
bool LootStrategyValue::Load(std::string const text)
{
value = LootStrategyValue::instance(text);
return true;
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LOOTSTRATEGYVALUE_H
#define _PLAYERBOT_LOOTSTRATEGYVALUE_H
#include "Value.h"
class LootStrategy;
class PlayerbotAI;
class LootStrategyValue : public ManualSetValue<LootStrategy*>
{
public:
LootStrategyValue(PlayerbotAI* botAI, std::string const name = "loot strategy")
: ManualSetValue<LootStrategy*>(botAI, normal, name)
{
}
virtual ~LootStrategyValue();
std::string const Save() override;
bool Load(std::string const value) override;
static LootStrategy* normal;
static LootStrategy* gray;
static LootStrategy* all;
static LootStrategy* disenchant;
static LootStrategy* instance(std::string const name);
};
#endif

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "LootValues.h"
#include "Playerbots.h"
#include "SharedValueContext.h"
LootTemplateAccess const* DropMapValue::GetLootTemplate(ObjectGuid guid, LootType type)
{
LootTemplate const* lTemplate = nullptr;
if (guid.IsCreature())
{
CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(guid.GetEntry());
if (info)
{
if (type == LOOT_CORPSE)
lTemplate = LootTemplates_Creature.GetLootFor(info->lootid);
else if (type == LOOT_PICKPOCKETING && info->pickpocketLootId)
lTemplate = LootTemplates_Pickpocketing.GetLootFor(info->pickpocketLootId);
else if (type == LOOT_SKINNING && info->SkinLootId)
lTemplate = LootTemplates_Skinning.GetLootFor(info->SkinLootId);
}
}
else if (guid.IsGameObject())
{
GameObjectTemplate const* info = sObjectMgr->GetGameObjectTemplate(guid.GetEntry());
if (info && info->GetLootId() != 0)
{
if (type == LOOT_CORPSE)
lTemplate = LootTemplates_Gameobject.GetLootFor(info->GetLootId());
else if (type == LOOT_FISHINGHOLE)
lTemplate = LootTemplates_Fishing.GetLootFor(info->GetLootId());
}
}
else if (guid.IsItem())
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(guid.GetEntry());
if (proto)
{
if (type == LOOT_CORPSE)
lTemplate = LootTemplates_Item.GetLootFor(proto->ItemId);
else if (type == LOOT_DISENCHANTING && proto->DisenchantID)
lTemplate = LootTemplates_Disenchant.GetLootFor(proto->DisenchantID);
if (type == LOOT_MILLING)
lTemplate = LootTemplates_Milling.GetLootFor(proto->ItemId);
if (type == LOOT_PROSPECTING)
lTemplate = LootTemplates_Prospecting.GetLootFor(proto->ItemId);
}
}
LootTemplateAccess const* lTemplateA = reinterpret_cast<LootTemplateAccess const*>(lTemplate);
return lTemplateA;
}
DropMap* DropMapValue::Calculate()
{
DropMap* dropMap = new DropMap;
int32 sEntry = 0;
if (CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates())
{
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
sEntry = itr->first;
if (LootTemplateAccess const* lTemplateA =
GetLootTemplate(ObjectGuid::Create<HighGuid::Unit>(sEntry, uint32(1)), LOOT_CORPSE))
for (auto const& lItem : lTemplateA->Entries)
dropMap->insert(std::make_pair(lItem->itemid, sEntry));
}
}
if (GameObjectTemplateContainer const* gameobjects = sObjectMgr->GetGameObjectTemplates())
{
for (auto const& itr : *gameobjects)
{
sEntry = itr.first;
if (LootTemplateAccess const* lTemplateA =
GetLootTemplate(ObjectGuid::Create<HighGuid::GameObject>(sEntry, uint32(1)), LOOT_CORPSE))
for (auto const& lItem : lTemplateA->Entries)
dropMap->insert(std::make_pair(lItem->itemid, -sEntry));
}
}
return dropMap;
}
// What items does this entry have in its loot list?
std::vector<int32> ItemDropListValue::Calculate()
{
uint32 itemId = stoi(getQualifier());
DropMap* dropMap = GAI_VALUE(DropMap*, "drop map");
std::vector<int32> entries;
auto range = dropMap->equal_range(itemId);
for (auto itr = range.first; itr != range.second; ++itr)
entries.push_back(itr->second);
return entries;
}
// What items does this entry have in its loot list?
std::vector<uint32> EntryLootListValue::Calculate()
{
int32 entry = stoi(getQualifier());
std::vector<uint32> items;
LootTemplateAccess const* lTemplateA;
if (entry > 0)
lTemplateA = DropMapValue::GetLootTemplate(ObjectGuid::Create<HighGuid::Unit>(entry, uint32(1)), LOOT_CORPSE);
else
lTemplateA =
DropMapValue::GetLootTemplate(ObjectGuid::Create<HighGuid::GameObject>(entry, uint32(1)), LOOT_CORPSE);
if (lTemplateA)
for (auto const& lItem : lTemplateA->Entries)
items.push_back(lItem->itemid);
return items;
}
itemUsageMap EntryLootUsageValue::Calculate()
{
itemUsageMap items;
for (auto itemId : GAI_VALUE2(std::vector<uint32>, "entry loot list", getQualifier()))
{
items[AI_VALUE2(ItemUsage, "item usage", itemId)].push_back(itemId);
}
return items;
};
bool HasUpgradeValue::Calculate()
{
itemUsageMap uMap = AI_VALUE2(itemUsageMap, "entry loot usage", getQualifier());
return uMap.find(ITEM_USAGE_EQUIP) != uMap.end() || uMap.find(ITEM_USAGE_REPLACE) != uMap.end();
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_LOOTVALUES_H
#define _PLAYERBOT_LOOTVALUES_H
#include "ItemUsageValue.h"
#include "LootMgr.h"
#include "NamedObjectContext.h"
#include "Value.h"
class PlayerbotAI;
// Cheat class copy to hack into the loot system
class LootTemplateAccess
{
public:
class LootGroup; // A set of loot definitions for items (refs are not allowed inside)
typedef std::vector<LootGroup> LootGroups;
LootStoreItemList Entries; // not grouped only
LootGroups Groups; // groups have own (optimized) processing, grouped entries go there
};
// itemId, entry
typedef std::unordered_map<uint32, int32> DropMap;
// Returns the loot map of all entries
class DropMapValue : public SingleCalculatedValue<DropMap*>
{
public:
DropMapValue(PlayerbotAI* botAI) : SingleCalculatedValue(botAI, "drop map") {}
static LootTemplateAccess const* GetLootTemplate(ObjectGuid guid, LootType type = LOOT_CORPSE);
DropMap* Calculate() override;
};
// Returns the entries that drop a specific item
class ItemDropListValue : public SingleCalculatedValue<std::vector<int32>>, public Qualified
{
public:
ItemDropListValue(PlayerbotAI* botAI) : SingleCalculatedValue(botAI, "item drop list") {}
std::vector<int32> Calculate() override;
};
// Returns the items a specific entry can drop
class EntryLootListValue : public SingleCalculatedValue<std::vector<uint32>>, public Qualified
{
public:
EntryLootListValue(PlayerbotAI* botAI) : SingleCalculatedValue(botAI, "entry loot list") {}
std::vector<uint32> Calculate() override;
};
typedef std::unordered_map<ItemUsage, std::vector<uint32>> itemUsageMap;
class EntryLootUsageValue : public CalculatedValue<itemUsageMap>, public Qualified
{
public:
EntryLootUsageValue(PlayerbotAI* botAI) : CalculatedValue(botAI, "entry loot usage", 2 * 1000) {}
itemUsageMap Calculate() override;
};
class HasUpgradeValue : public BoolCalculatedValue, public Qualified
{
public:
HasUpgradeValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "has upgrade", 2 * 1000) {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "MaintenanceValues.h"
#include "BudgetValues.h"
#include "ItemUsageValue.h"
#include "Playerbots.h"
bool CanMoveAroundValue::Calculate()
{
if (bot->IsInCombat())
return false;
if (bot->GetTradeData())
return false;
if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT))
return false;
if (!AI_VALUE(bool, "group ready"))
return false;
return true;
}
bool ShouldHomeBindValue::Calculate() { return AI_VALUE2(float, "distance", "home bind") > 1000.0f; }
bool ShouldRepairValue::Calculate() { return AI_VALUE(uint8, "durability") < 80; }
bool CanRepairValue::Calculate()
{
return AI_VALUE(uint8, "durability") < 100 &&
AI_VALUE(uint32, "repair cost") < AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::repair);
}
bool ShouldSellValue::Calculate() { return AI_VALUE(uint8, "bag space") > 80; }
bool CanSellValue::Calculate()
{
return (AI_VALUE2(uint32, "item count", "usage " + std::to_string(ITEM_USAGE_VENDOR)) +
AI_VALUE2(uint32, "item count", "usage " + std::to_string(ITEM_USAGE_AH))) > 1;
}
bool CanFightEqualValue::Calculate() { return AI_VALUE(uint8, "durability") > 20; }
bool CanFightEliteValue::Calculate()
{
return bot->GetGroup() && AI_VALUE2(bool, "group and", "can fight equal") &&
AI_VALUE2(bool, "group and", "following party") && !AI_VALUE2(bool, "group or", "should sell,can sell");
}
bool CanFightBossValue::Calculate()
{
return bot->GetGroup() && bot->GetGroup()->GetMembersCount() > 3 &&
AI_VALUE2(bool, "group and", "can fight equal") && AI_VALUE2(bool, "group and", "following party") &&
!AI_VALUE2(bool, "group or", "should sell,can sell");
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_MAINTANCEVALUE_H
#define _PLAYERBOT_MAINTANCEVALUE_H
#include "Value.h"
class PlayerbotAI;
class CanMoveAroundValue : public BoolCalculatedValue
{
public:
CanMoveAroundValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can move around", 2 * 2000) {}
bool Calculate() override;
};
class ShouldHomeBindValue : public BoolCalculatedValue
{
public:
ShouldHomeBindValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "should home bind", 2 * 2000) {}
bool Calculate() override;
};
class ShouldRepairValue : public BoolCalculatedValue
{
public:
ShouldRepairValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "should repair", 2 * 2000) {}
bool Calculate() override;
};
class CanRepairValue : public BoolCalculatedValue
{
public:
CanRepairValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can repair", 2 * 2000) {}
bool Calculate() override;
};
class ShouldSellValue : public BoolCalculatedValue
{
public:
ShouldSellValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "should sell", 2 * 2000) {}
bool Calculate() override;
};
class CanSellValue : public BoolCalculatedValue
{
public:
CanSellValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can sell", 2 * 2000) {}
bool Calculate() override;
};
class CanFightEqualValue : public BoolCalculatedValue
{
public:
CanFightEqualValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can fight equal", 2 * 2000) {}
bool Calculate() override;
};
class CanFightEliteValue : public BoolCalculatedValue
{
public:
CanFightEliteValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can fight elite") {}
bool Calculate() override;
};
class CanFightBossValue : public BoolCalculatedValue
{
public:
CanFightBossValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can fight boss") {}
bool Calculate() override;
};
#endif

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_MANASAVELEVELVALUE_H
#define _PLAYERBOT_MANASAVELEVELVALUE_H
#include "Value.h"
class PlayerbotAI;
class ManaSaveLevelValue : public ManualSetValue<double>
{
public:
ManaSaveLevelValue(PlayerbotAI* botAI) : ManualSetValue<double>(botAI, 1.0, "mana save level") {}
std::string const Save()
{
std::ostringstream out;
out << value;
return out.str();
}
bool Load(std::string const text)
{
value = atof(text.c_str());
return true;
}
};
#endif

View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NearestAdsValue.h"
#include "Playerbots.h"
bool NearestAddsValue::AcceptUnit(Unit* unit)
{
Unit* target = AI_VALUE(Unit*, "current target");
return unit != target;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_NEARESTADSVALUE_H
#define _PLAYERBOT_NEARESTADSVALUE_H
#include "PlayerbotAIConfig.h"
#include "PossibleTargetsValue.h"
class PlayerbotAI;
class NearestAddsValue : public PossibleTargetsValue
{
public:
NearestAddsValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->tooCloseDistance)
: PossibleTargetsValue(botAI, "nearest adds", range, true)
{
}
protected:
bool AcceptUnit(Unit* unit) override;
};
#endif

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NearestCorpsesValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
class AnyDeadUnitInObjectRangeCheck
{
public:
AnyDeadUnitInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(Unit* u) { return !u->IsAlive(); }
private:
WorldObject const* i_obj;
float i_range;
};
void NearestCorpsesValue::FindUnits(std::list<Unit*>& targets)
{
AnyDeadUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<AnyDeadUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestCorpsesValue::AcceptUnit(Unit* unit) { return true; }

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_NEARESTCORPSESVALUE_H
#define _PLAYERBOT_NEARESTCORPSESVALUE_H
#include "NearestUnitsValue.h"
#include "PlayerbotAIConfig.h"
class PlayerbotAI;
class NearestCorpsesValue : public NearestUnitsValue
{
public:
NearestCorpsesValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance)
: NearestUnitsValue(botAI, "nearest corpses", range, true)
{
}
protected:
void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override;
};
#endif

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NearestFriendlyPlayersValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
void NearestFriendlyPlayersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyFriendlyUnitInObjectRangeCheck u_check(bot, bot, range);
Acore::UnitListSearcher<Acore::AnyFriendlyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestFriendlyPlayersValue::AcceptUnit(Unit* unit)
{
ObjectGuid guid = unit->GetGUID();
return guid.IsPlayer() && guid != botAI->GetBot()->GetGUID();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_NEARESTFRIENDLYPLAYERSVALUES_H
#define _PLAYERBOT_NEARESTFRIENDLYPLAYERSVALUES_H
#include "NearestUnitsValue.h"
#include "PlayerbotAIConfig.h"
class PlayerbotAI;
class NearestFriendlyPlayersValue : public NearestUnitsValue
{
public:
NearestFriendlyPlayersValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance)
: NearestUnitsValue(botAI, "nearest friendly players", range)
{
}
protected:
void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override;
};
#endif

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NearestGameObjects.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
#include "SharedDefines.h"
#include "SpellMgr.h"
GuidVector NearestGameObjects::Calculate()
{
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
GuidVector result;
for (GameObject* go : targets)
{
// if (ignoreLos || bot->IsWithinLOSInMap(go))
result.push_back(go->GetGUID());
}
return result;
}
GuidVector NearestTrapWithDamageValue::Calculate()
{
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(bot, range);
Acore::GameObjectListSearcher<AnyGameObjectInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
GuidVector result;
for (GameObject* go : targets)
{
if (go->GetGoType() != GAMEOBJECT_TYPE_TRAP)
{
continue;
}
Unit* owner = go->GetOwner();
if (owner && owner->IsFriendlyTo(bot))
{
continue;
}
const GameObjectTemplate* goInfo = go->GetGOInfo();
if (!goInfo)
{
continue;
}
uint32 spellId = goInfo->trap.spellId;
if (!spellId)
{
continue;
}
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo || spellInfo->IsPositive())
{
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)
{
result.push_back(go->GetGUID());
break;
}
}
else if (spellInfo->Effects[i].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
{
result.push_back(go->GetGUID());
break;
}
}
}
return result;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_NEARESTGAMEOBJECTS_H
#define _PLAYERBOT_NEARESTGAMEOBJECTS_H
#include "PlayerbotAIConfig.h"
#include "Value.h"
#include "GameObject.h"
class PlayerbotAI;
class AnyGameObjectInObjectRangeCheck
{
public:
AnyGameObjectInObjectRangeCheck(WorldObject const* obj, float range) : i_obj(obj), i_range(range) {}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(GameObject* u)
{
if (u && i_obj->IsWithinDistInMap(u, i_range) && u->isSpawned() && u->GetGOInfo())
return true;
return false;
}
private:
WorldObject const* i_obj;
float i_range;
};
class NearestGameObjects : public ObjectGuidListCalculatedValue
{
public:
NearestGameObjects(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance, bool ignoreLos = false,
std::string const name = "nearest game objects")
: ObjectGuidListCalculatedValue(botAI, name, 1 * 1000), range(range), ignoreLos(ignoreLos)
{
}
protected:
GuidVector Calculate() override;
private:
float range;
bool ignoreLos;
};
class NearestTrapWithDamageValue : public ObjectGuidListCalculatedValue
{
public:
NearestTrapWithDamageValue(PlayerbotAI* botAI, float range = 15.0f)
: ObjectGuidListCalculatedValue(botAI, "nearest trap with damage", 1 * 1000), range(range)
{
}
protected:
GuidVector Calculate() override;
private:
float range;
};
#endif

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "NearestNonBotPlayersValue.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Playerbots.h"
void NearestNonBotPlayersValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitObjects(bot, searcher, range);
}
bool NearestNonBotPlayersValue::AcceptUnit(Unit* unit)
{
ObjectGuid guid = unit->GetGUID();
return guid.IsPlayer() && !GET_PLAYERBOT_AI(((Player*)unit)) &&
(!((Player*)unit)->IsGameMaster() || ((Player*)unit)->isGMVisible());
}

Some files were not shown because too many files have changed in this diff Show More