smarter dps target and tank target

This commit is contained in:
Yunfan Li
2023-10-22 01:27:37 +08:00
parent f303a9e83a
commit a63c6b6709
14 changed files with 184 additions and 53 deletions

View File

@@ -292,9 +292,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (tab == 2) if (tab == 2)
engine->addStrategies("tank", "tank assist", "aoe", "mark rti", nullptr); engine->addStrategies("tank", "tank assist", "aoe", "mark rti", nullptr);
else if (player->getLevel() < 30 || tab == 0) else if (player->getLevel() < 30 || tab == 0)
engine->addStrategies("arms", "aoe", "dps assist", "threat", "behind", nullptr); engine->addStrategies("arms", "aoe", "dps assist", "threat", /*"behind",*/ nullptr);
else else
engine->addStrategies("fury", "aoe", "dps assist", "threat", "behind", nullptr); engine->addStrategies("fury", "aoe", "dps assist", "threat", /*"behind",*/ nullptr);
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (tab == 0) if (tab == 0)
@@ -335,9 +335,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION) { if (tab == ROGUE_TAB_ASSASSINATION) {
engine->addStrategies("melee", "threat", "dps assist", "aoe", "behind", nullptr); engine->addStrategies("melee", "threat", "dps assist", "aoe", /*"behind",*/ nullptr);
} else { } else {
engine->addStrategies("dps", "threat", "dps assist", "aoe", "behind", nullptr); engine->addStrategies("dps", "threat", "dps assist", "aoe", /*"behind",*/ nullptr);
} }
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
@@ -436,10 +436,10 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategies("caster", "caster aoe", nullptr); engine->addStrategies("caster", "caster aoe", nullptr);
if (player->getClass() == CLASS_DRUID && tab == 1) if (player->getClass() == CLASS_DRUID && tab == 1)
engine->addStrategies("behind", "dps", nullptr); engine->addStrategies(/*"behind",*/ "dps", nullptr);
if (player->getClass() == CLASS_ROGUE) if (player->getClass() == CLASS_ROGUE)
engine->addStrategies("behind", "stealth", nullptr); engine->addStrategies(/*"behind",*/ "stealth", nullptr);
} }
} }

View File

@@ -1399,6 +1399,19 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
return false; return false;
} }
bool PlayerbotAI::HasAggro(Unit* unit)
{
if (!unit) {
return false;
}
bool isMT = IsMainTank(bot);
Unit* victim = unit->GetVictim();
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer())))) {
return true;
}
return false;
}
int32 PlayerbotAI::GetGroupSlotIndex(Player* player) int32 PlayerbotAI::GetGroupSlotIndex(Player* player)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();

View File

@@ -338,6 +338,7 @@ class PlayerbotAI : public PlayerbotAIBase
bool IsAssistTankOfIndex(Player* player, int index); bool IsAssistTankOfIndex(Player* player, int index);
bool IsHealAssistantOfIndex(Player* player, int index); bool IsHealAssistantOfIndex(Player* player, int index);
bool IsRangedDpsAssistantOfIndex(Player* player, int index); bool IsRangedDpsAssistantOfIndex(Player* player, int index);
bool HasAggro(Unit* unit);
int32 GetGroupSlotIndex(Player* player); int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player); int32 GetRangedIndex(Player* player);
int32 GetClassIndex(Player* player, uint8_t cls); int32 GetClassIndex(Player* player, uint8_t cls);

View File

@@ -443,7 +443,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
if (master && isRandomAccount && master->GetLevel() < bot->GetLevel()) { if (master && isRandomAccount && master->GetLevel() < bot->GetLevel()) {
// PlayerbotFactory factory(bot, master->getLevel()); // PlayerbotFactory factory(bot, master->getLevel());
// factory.Randomize(false); // factory.Randomize(false);
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, false, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
} }
@@ -584,14 +584,13 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
} }
else if (cmd == "init=auto") else if (cmd == "init=auto")
{ {
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, false, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(mixedGearScore / (ITEM_QUALITY_EPIC + 1)) + "(for epic)"; return "ok, gear score limit: " + std::to_string(mixedGearScore / (ITEM_QUALITY_EPIC + 1)) + "(for epic)";
} }
else if (cmd.starts_with("init=") && sscanf(cmd.c_str(), "init=%d", &gs) != -1) else if (cmd.starts_with("init=") && sscanf(cmd.c_str(), "init=%d", &gs) != -1)
{ {
// uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, false, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, gs); PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, gs);
factory.Randomize(false); factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(gs / (ITEM_QUALITY_EPIC + 1)) + "(for epic)"; return "ok, gear score limit: " + std::to_string(gs / (ITEM_QUALITY_EPIC + 1)) + "(for epic)";

View File

@@ -23,20 +23,34 @@ enum StrategyType : uint32
STRATEGY_TYPE_MELEE = 64 STRATEGY_TYPE_MELEE = 64
}; };
enum ActionPriority // enum ActionPriority
{ // {
ACTION_IDLE = 0, // ACTION_IDLE = 0,
ACTION_NORMAL = 10, // ACTION_DEFAULT = 5,
ACTION_HIGH = 20, // ACTION_NORMAL = 10,
ACTION_MOVE = 30, // ACTION_HIGH = 20,
ACTION_INTERRUPT = 40, // ACTION_MOVE = 30,
ACTION_DISPEL = 50, // ACTION_INTERRUPT = 40,
ACTION_RAID = 60, // ACTION_DISPEL = 50,
ACTION_LIGHT_HEAL = 10, // ACTION_RAID = 60,
ACTION_MEDIUM_HEAL = 20, // ACTION_LIGHT_HEAL = 10,
ACTION_CRITICAL_HEAL = 30, // ACTION_MEDIUM_HEAL = 20,
ACTION_EMERGENCY = 90 // ACTION_CRITICAL_HEAL = 30,
}; // ACTION_EMERGENCY = 90
// };
static float ACTION_IDLE = 0.0f;
static float ACTION_DEFAULT = 5.0f;
static float ACTION_NORMAL = 10.0f;
static float ACTION_HIGH = 20.0f;
static float ACTION_MOVE = 30.0f;
static float ACTION_INTERRUPT = 40.0f;
static float ACTION_DISPEL = 50.0f;
static float ACTION_RAID = 60.0f;
static float ACTION_LIGHT_HEAL = 10.0f;
static float ACTION_MEDIUM_HEAL = 20.0f;
static float ACTION_CRITICAL_HEAL = 30.0f;
static float ACTION_EMERGENCY = 90.0f;
class Strategy : public PlayerbotAIAware class Strategy : public PlayerbotAIAware
{ {

View File

@@ -80,15 +80,15 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
NextAction** BloodDKStrategy::getDefaultActions() NextAction** BloodDKStrategy::getDefaultActions()
{ {
return NextAction::array(0, return NextAction::array(0,
new NextAction("rune strike", ACTION_NORMAL + 8), new NextAction("rune strike", ACTION_DEFAULT + 0.8f),
new NextAction("icy touch", ACTION_NORMAL + 7), new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
new NextAction("heart strike", ACTION_NORMAL + 6), new NextAction("heart strike", ACTION_DEFAULT + 0.6f),
new NextAction("blood strike", ACTION_NORMAL + 5), new NextAction("blood strike", ACTION_DEFAULT + 0.5f),
new NextAction("dancing rune weapon", ACTION_NORMAL + 4), new NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
new NextAction("death coil", ACTION_NORMAL + 3), new NextAction("death coil", ACTION_DEFAULT + 0.3f),
new NextAction("plague strike", ACTION_NORMAL + 2), new NextAction("plague strike", ACTION_DEFAULT + 0.2f),
new NextAction("horn of winter", ACTION_NORMAL + 1), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_NORMAL), new NextAction("melee", ACTION_DEFAULT),
NULL); NULL);
} }

View File

@@ -78,12 +78,12 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
NextAction** FrostDKStrategy::getDefaultActions() NextAction** FrostDKStrategy::getDefaultActions()
{ {
return NextAction::array(0, return NextAction::array(0,
new NextAction("obliterate", ACTION_NORMAL + 5), new NextAction("obliterate", ACTION_DEFAULT + 0.5f),
new NextAction("frost strike", ACTION_NORMAL + 4), new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
// new NextAction("death strike", ACTION_NORMAL + 3), // new NextAction("death strike", ACTION_NORMAL + 3),
new NextAction("empower rune weapon", ACTION_NORMAL + 2), new NextAction("empower rune weapon", ACTION_DEFAULT + 0.2f),
new NextAction("horn of winter", ACTION_NORMAL), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_NORMAL), new NextAction("melee", ACTION_DEFAULT),
NULL NULL
); );
} }

View File

@@ -72,15 +72,15 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI
NextAction** UnholyDKStrategy::getDefaultActions() NextAction** UnholyDKStrategy::getDefaultActions()
{ {
return NextAction::array(0, return NextAction::array(0,
new NextAction("scourge strike", ACTION_NORMAL + 7), new NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
new NextAction("blood strike", ACTION_NORMAL + 6), new NextAction("blood strike", ACTION_DEFAULT + 0.7f),
new NextAction("ghoul frenzy", ACTION_NORMAL + 5), new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.6f),
new NextAction("summon gargoyle", ACTION_NORMAL + 4), new NextAction("summon gargoyle", ACTION_DEFAULT + 0.5f),
new NextAction("death coil", ACTION_NORMAL + 3), new NextAction("death coil", ACTION_DEFAULT + 0.4f),
new NextAction("plague strike", ACTION_NORMAL + 2), new NextAction("plague strike", ACTION_DEFAULT + 0.3f),
new NextAction("icy touch", ACTION_NORMAL + 1), new NextAction("icy touch", ACTION_DEFAULT + 0.2f),
new NextAction("horn of winter", ACTION_NORMAL), new NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_NORMAL), new NextAction("melee", ACTION_DEFAULT),
nullptr); nullptr);
} }

View File

@@ -48,7 +48,7 @@ void DpsHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("black arrow", NextAction::array(0, new NextAction("black arrow", 15.0f), nullptr))); triggers.push_back(new TriggerNode("black arrow", NextAction::array(0, new NextAction("black arrow", 15.0f), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("viper sting", 23.0f), nullptr))); triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("viper sting", 23.0f), nullptr)));
triggers.push_back(new TriggerNode("hunter's mark", NextAction::array(0, new NextAction("hunter's mark", 11.0f), nullptr))); triggers.push_back(new TriggerNode("hunter's mark", NextAction::array(0, new NextAction("hunter's mark", 31.0f), nullptr)));
triggers.push_back(new TriggerNode("concussive shot on snare target", NextAction::array(0, new NextAction("concussive shot", 20.0f), nullptr))); triggers.push_back(new TriggerNode("concussive shot on snare target", NextAction::array(0, new NextAction("concussive shot", 20.0f), nullptr)));
// triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 21.0f), NULL))); // triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 21.0f), NULL)));
triggers.push_back(new TriggerNode("hunters pet low health", NextAction::array(0, new NextAction("mend pet", 21.0f), NULL))); triggers.push_back(new TriggerNode("hunters pet low health", NextAction::array(0, new NextAction("mend pet", 21.0f), NULL)));

View File

@@ -406,7 +406,7 @@ bool TankAssistTrigger::IsActive()
if (!tankTarget || currentTarget == tankTarget) if (!tankTarget || currentTarget == tankTarget)
return false; return false;
return currentTarget->GetVictim() == AI_VALUE(Unit*, "self target"); return AI_VALUE2(bool, "has aggro", "current target");
} }
bool IsBehindTargetTrigger::IsActive() bool IsBehindTargetTrigger::IsActive()

View File

@@ -21,7 +21,8 @@ bool HasAggroValue::Calculate()
if (!victim) { if (!victim) {
return true; return true;
} }
if (victim && (victim->GetGUID() == bot->GetGUID() || (victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer())))) { bool isMT = botAI->IsMainTank(bot);
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && botAI->IsTank(victim->ToPlayer())))) {
return true; return true;
} }
return false; return false;

View File

@@ -58,13 +58,70 @@ class FindMaxThreatGapTargetStrategy : public FindTargetStrategy
float minThreat; float minThreat;
}; };
class FindTargetSmartStrategy : public FindTargetStrategy
{
public:
FindTargetSmartStrategy(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;
}
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)
if (GetIntervalLevel(new_time) > GetIntervalLevel(old_time)) {
return true;
}
int32_t interval = GetIntervalLevel(new_time);
if (interval == 2 || interval == 0) {
return new_time < old_time;
}
// dont switch targets when all of them with low health
if (botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get() == old_unit) {
return false;
}
return new_time > old_time;
}
int32_t GetIntervalLevel(float time) {
if (time >= 5 && time <= 20) {
return 2;
}
if (time < 5) {
return 1;
}
return 0;
}
protected:
float dps_;
float targetExpectedLifeTime;
};
Unit* DpsTargetValue::Calculate() Unit* DpsTargetValue::Calculate()
{ {
Unit* rti = RtiTargetValue::Calculate(); Unit* rti = RtiTargetValue::Calculate();
if (rti) if (rti)
return rti; return rti;
FindLeastHpTargetStrategy strategy(botAI); // FindLeastHpTargetStrategy strategy(botAI);
float dps = AI_VALUE(float, "expected group dps");
FindTargetSmartStrategy strategy(botAI, dps);
// FindMaxThreatGapTargetStrategy strategy(botAI); // FindMaxThreatGapTargetStrategy strategy(botAI);
return TargetValue::FindTarget(&strategy); return TargetValue::FindTarget(&strategy);
} }

View File

@@ -21,11 +21,11 @@ float ExpectedGroupDpsValue::Calculate()
float dps_num; float dps_num;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) { if (!group) {
dps_num = 1; dps_num = 0.7;
} else { } else {
dps_num = group->GetMembersCount() * 0.7; dps_num = group->GetMembersCount() * 0.7;
} }
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(bot, false, false, 12); uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(bot, true, false, 12);
// efficiency record based on rare gear level, is there better calculation method? // efficiency record based on rare gear level, is there better calculation method?
// float dps_efficiency = 1; // float dps_efficiency = 1;
float basic_dps; float basic_dps;

View File

@@ -3,6 +3,7 @@
*/ */
#include "AttackersValue.h" #include "AttackersValue.h"
#include "PlayerbotAIConfig.h"
#include "TankTargetValue.h" #include "TankTargetValue.h"
#include "Playerbots.h" #include "Playerbots.h"
@@ -41,8 +42,53 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy
float minThreat; float minThreat;
}; };
class FindTankTargetSmartStrategy : public FindTargetStrategy
{
public:
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) { }
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 (!result || IsBetter(attacker, result)) {
result = attacker;
}
}
bool IsBetter(Unit* new_unit, Unit* old_unit) {
Player* bot = botAI->GetBot();
float new_threat = new_unit->GetThreatMgr().GetThreat(bot);
float old_threat = old_unit->GetThreatMgr().GetThreat(bot);
float new_dis = bot->GetDistance(new_unit);
float old_dis = bot->GetDistance(old_unit);
// hasAggro? -> withinMelee? -> threat
if (GetIntervalLevel(new_unit) > GetIntervalLevel(old_unit)) {
return true;
}
int32_t interval = GetIntervalLevel(new_unit);
if (interval == 1) {
return new_dis < old_dis;
}
return new_threat < old_threat;
}
int32_t GetIntervalLevel(Unit* unit) {
if (!botAI->HasAggro(unit)) {
return 1;
}
return 0;
}
};
Unit* TankTargetValue::Calculate() Unit* TankTargetValue::Calculate()
{ {
FindTargetForTankStrategy strategy(botAI); // FindTargetForTankStrategy strategy(botAI);
FindTankTargetSmartStrategy strategy(botAI);
return FindTarget(&strategy); return FindTarget(&strategy);
} }