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

View File

@@ -338,6 +338,7 @@ class PlayerbotAI : public PlayerbotAIBase
bool IsAssistTankOfIndex(Player* player, int index);
bool IsHealAssistantOfIndex(Player* player, int index);
bool IsRangedDpsAssistantOfIndex(Player* player, int index);
bool HasAggro(Unit* unit);
int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player);
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()) {
// PlayerbotFactory factory(bot, master->getLevel());
// 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);
factory.Randomize(false);
}
@@ -584,14 +584,13 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
}
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);
factory.Randomize(false);
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)
{
// uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, false, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
PlayerbotFactory factory(bot, master->getLevel(), ITEM_QUALITY_LEGENDARY, gs);
factory.Randomize(false);
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
};
enum ActionPriority
{
ACTION_IDLE = 0,
ACTION_NORMAL = 10,
ACTION_HIGH = 20,
ACTION_MOVE = 30,
ACTION_INTERRUPT = 40,
ACTION_DISPEL = 50,
ACTION_RAID = 60,
ACTION_LIGHT_HEAL = 10,
ACTION_MEDIUM_HEAL = 20,
ACTION_CRITICAL_HEAL = 30,
ACTION_EMERGENCY = 90
};
// enum ActionPriority
// {
// ACTION_IDLE = 0,
// ACTION_DEFAULT = 5,
// ACTION_NORMAL = 10,
// ACTION_HIGH = 20,
// ACTION_MOVE = 30,
// ACTION_INTERRUPT = 40,
// ACTION_DISPEL = 50,
// ACTION_RAID = 60,
// ACTION_LIGHT_HEAL = 10,
// ACTION_MEDIUM_HEAL = 20,
// 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
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,8 @@ bool HasAggroValue::Calculate()
if (!victim) {
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 false;

View File

@@ -58,13 +58,70 @@ class FindMaxThreatGapTargetStrategy : public FindTargetStrategy
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* rti = RtiTargetValue::Calculate();
if (rti)
return rti;
FindLeastHpTargetStrategy strategy(botAI);
// FindLeastHpTargetStrategy strategy(botAI);
float dps = AI_VALUE(float, "expected group dps");
FindTargetSmartStrategy strategy(botAI, dps);
// FindMaxThreatGapTargetStrategy strategy(botAI);
return TargetValue::FindTarget(&strategy);
}

View File

@@ -21,11 +21,11 @@ float ExpectedGroupDpsValue::Calculate()
float dps_num;
Group* group = bot->GetGroup();
if (!group) {
dps_num = 1;
dps_num = 0.7;
} else {
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?
// float dps_efficiency = 1;
float basic_dps;

View File

@@ -3,6 +3,7 @@
*/
#include "AttackersValue.h"
#include "PlayerbotAIConfig.h"
#include "TankTargetValue.h"
#include "Playerbots.h"
@@ -41,8 +42,53 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy
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()
{
FindTargetForTankStrategy strategy(botAI);
// FindTargetForTankStrategy strategy(botAI);
FindTankTargetSmartStrategy strategy(botAI);
return FindTarget(&strategy);
}