diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index 33b320a5..b0ee121f 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -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); } } diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 86341693..65d5612c 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -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(); diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 0f618cfd..e268914f 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -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); diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 9c82eeea..4083938a 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -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)"; diff --git a/src/strategy/Strategy.h b/src/strategy/Strategy.h index 854a7b89..4de7b671 100644 --- a/src/strategy/Strategy.h +++ b/src/strategy/Strategy.h @@ -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 { diff --git a/src/strategy/deathknight/BloodDKStrategy.cpp b/src/strategy/deathknight/BloodDKStrategy.cpp index 079d4ad9..b63908cb 100644 --- a/src/strategy/deathknight/BloodDKStrategy.cpp +++ b/src/strategy/deathknight/BloodDKStrategy.cpp @@ -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); } diff --git a/src/strategy/deathknight/FrostDKStrategy.cpp b/src/strategy/deathknight/FrostDKStrategy.cpp index 8966fa69..8feba413 100644 --- a/src/strategy/deathknight/FrostDKStrategy.cpp +++ b/src/strategy/deathknight/FrostDKStrategy.cpp @@ -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 ); } diff --git a/src/strategy/deathknight/UnholyDKStrategy.cpp b/src/strategy/deathknight/UnholyDKStrategy.cpp index 4369689f..638a9967 100644 --- a/src/strategy/deathknight/UnholyDKStrategy.cpp +++ b/src/strategy/deathknight/UnholyDKStrategy.cpp @@ -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); } diff --git a/src/strategy/hunter/DpsHunterStrategy.cpp b/src/strategy/hunter/DpsHunterStrategy.cpp index 7ec2220a..4ef8a3f0 100644 --- a/src/strategy/hunter/DpsHunterStrategy.cpp +++ b/src/strategy/hunter/DpsHunterStrategy.cpp @@ -48,7 +48,7 @@ void DpsHunterStrategy::InitTriggers(std::vector& 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))); diff --git a/src/strategy/triggers/GenericTriggers.cpp b/src/strategy/triggers/GenericTriggers.cpp index be35d17b..1e3d470b 100644 --- a/src/strategy/triggers/GenericTriggers.cpp +++ b/src/strategy/triggers/GenericTriggers.cpp @@ -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() diff --git a/src/strategy/values/AttackerCountValues.cpp b/src/strategy/values/AttackerCountValues.cpp index 9faeeb2a..15cb6efb 100644 --- a/src/strategy/values/AttackerCountValues.cpp +++ b/src/strategy/values/AttackerCountValues.cpp @@ -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; diff --git a/src/strategy/values/DpsTargetValue.cpp b/src/strategy/values/DpsTargetValue.cpp index aee84ee2..258f8dfe 100644 --- a/src/strategy/values/DpsTargetValue.cpp +++ b/src/strategy/values/DpsTargetValue.cpp @@ -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("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); } diff --git a/src/strategy/values/ExpectedLifetimeValue.cpp b/src/strategy/values/ExpectedLifetimeValue.cpp index 3518af2e..3868bbcf 100644 --- a/src/strategy/values/ExpectedLifetimeValue.cpp +++ b/src/strategy/values/ExpectedLifetimeValue.cpp @@ -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; diff --git a/src/strategy/values/TankTargetValue.cpp b/src/strategy/values/TankTargetValue.cpp index 817dbd50..173c84da 100644 --- a/src/strategy/values/TankTargetValue.cpp +++ b/src/strategy/values/TankTargetValue.cpp @@ -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); }