diff --git a/.github/workflows/codestyle_cpp.yml b/.github/workflows/codestyle_cpp.yml index 06573289..37f28acf 100644 --- a/.github/workflows/codestyle_cpp.yml +++ b/.github/workflows/codestyle_cpp.yml @@ -6,10 +6,6 @@ on: - reopened - synchronize - ready_for_review - paths: - - src/** - - "!README.md" - - "!docs/**" concurrency: group: "codestyle-cppcheck-${{ github.event.pull_request.number }}" @@ -22,13 +18,27 @@ jobs: if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + cpp: + - 'src/**' + - '!README.md' + - '!docs/**' + - name: Setup python + if: steps.filter.outputs.cpp == 'true' uses: actions/setup-python@v5 with: python-version: '3.10' - name: AzerothCore codestyle + if: steps.filter.outputs.cpp == 'true' run: python ./apps/codestyle/codestyle-cpp.py + - name: C++ Advanced + if: steps.filter.outputs.cpp == 'true' run: | sudo apt update -y sudo apt install -y cppcheck diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index a2f06885..5acf71ea 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -192,9 +192,12 @@ AiPlayerbot.AutoInitOnly = 0 # Default: 1.0 (same with the player) AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0 -# Bot automatically trains spells when talking to trainer -# yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells -AiPlayerbot.AutoTrainSpells = yes +# +# AllowLearnTrainerSpells +# Description: Allow the bot to learn trainers' spells as long as it has the money. +# Default: 1 - (Enabled) +# 0 - (Disabled) +AiPlayerbot.AllowLearnTrainerSpells = 1 # # @@ -563,6 +566,34 @@ AiPlayerbot.AutoGearScoreLimit = 0 # Default: food, taxi, and raid are enabled AiPlayerbot.BotCheats = "food,taxi,raid" +# Attunement quests (comma-separated list of quest IDs) +# Default: +# Caverns of Time - Part 1 +# - 10279, To The Master's Lair +# - 10277, The Caverns of Time +# +# Caverns of Time - Part 2 (Escape from Durnholde Keep) +# - 10282, Old Hillsbrad +# - 10283, Taretha's Diversion +# - 10284, Escape from Durnholde +# - 10285, Return to Andormu +# +# Caverns of Time - Part 2 (The Black Morass) +# - 10296, The Black Morass +# - 10297, The Opening of the Dark Portal +# - 10298, Hero of the Brood +# +# Magister's Terrace Attunement +# - 11481, Crisis at the Sunwell +# - 11482, Duty Calls +# - 11488, Magisters' Terrace +# - 11490, The Scryer's Scryer +# - 11492, Hard to Kill +# +# Serpentshrine Cavern +# - 10901, The Cudgel of Kar'desh +AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901 + # # # diff --git a/src/Ai/Base/ActionContext.h b/src/Ai/Base/ActionContext.h index 93bba4af..6431ee87 100644 --- a/src/Ai/Base/ActionContext.h +++ b/src/Ai/Base/ActionContext.h @@ -125,6 +125,7 @@ public: creators["runaway"] = &ActionContext::runaway; creators["stay"] = &ActionContext::stay; creators["sit"] = &ActionContext::sit; + creators["aggressive target"] = &ActionContext::aggressive_target; creators["attack anything"] = &ActionContext::attack_anything; creators["attack least hp target"] = &ActionContext::attack_least_hp_target; creators["attack enemy player"] = &ActionContext::attack_enemy_player; @@ -315,6 +316,7 @@ private: static Action* suggest_what_to_do(PlayerbotAI* botAI) { return new SuggestWhatToDoAction(botAI); } static Action* suggest_trade(PlayerbotAI* botAI) { return new SuggestTradeAction(botAI); } static Action* suggest_dungeon(PlayerbotAI* botAI) { return new SuggestDungeonAction(botAI); } + static Action* aggressive_target(PlayerbotAI* botAI) { return new AggressiveTargetAction(botAI); } static Action* attack_anything(PlayerbotAI* botAI) { return new AttackAnythingAction(botAI); } static Action* attack_least_hp_target(PlayerbotAI* botAI) { return new AttackLeastHpTargetAction(botAI); } static Action* attack_enemy_player(PlayerbotAI* botAI) { return new AttackEnemyPlayerAction(botAI); } diff --git a/src/Ai/Base/Actions/ChooseTargetActions.cpp b/src/Ai/Base/Actions/ChooseTargetActions.cpp index f7538c03..debcfac0 100644 --- a/src/Ai/Base/Actions/ChooseTargetActions.cpp +++ b/src/Ai/Base/Actions/ChooseTargetActions.cpp @@ -30,6 +30,14 @@ bool AttackEnemyFlagCarrierAction::isUseful() PlayerHasFlag::IsCapturingFlag(bot); } +bool AggressiveTargetAction::isUseful() +{ + if (bot->IsInCombat()) + return false; + + return true; +} + bool DropTargetAction::Execute(Event /*event*/) { Unit* target = context->GetValue("current target")->Get(); diff --git a/src/Ai/Base/Actions/ChooseTargetActions.h b/src/Ai/Base/Actions/ChooseTargetActions.h index 5f905019..5822543e 100644 --- a/src/Ai/Base/Actions/ChooseTargetActions.h +++ b/src/Ai/Base/Actions/ChooseTargetActions.h @@ -35,6 +35,15 @@ public: std::string const GetTargetName() override { return "tank target"; } }; +class AggressiveTargetAction : public AttackAction +{ +public: + AggressiveTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "aggressive target") {} + + std::string const GetTargetName() override { return "aggressive target"; } + bool isUseful() override; +}; + class AttackAnythingAction : public AttackAction { public: diff --git a/src/Ai/Base/Actions/ResetInstancesAction.cpp b/src/Ai/Base/Actions/ResetInstancesAction.cpp index eef29fc9..9afe3676 100644 --- a/src/Ai/Base/Actions/ResetInstancesAction.cpp +++ b/src/Ai/Base/Actions/ResetInstancesAction.cpp @@ -7,10 +7,13 @@ #include "PlayerbotAI.h" +#include "InstancePackets.h" + bool ResetInstancesAction::Execute(Event /*event*/) { WorldPacket packet(CMSG_RESET_INSTANCES, 0); - bot->GetSession()->HandleResetInstancesOpcode(packet); + WorldPackets::Instance::ResetInstances resetInstance(std::move(packet)); + bot->GetSession()->HandleResetInstancesOpcode(resetInstance); return true; } diff --git a/src/Ai/Base/Actions/RpgSubActions.cpp b/src/Ai/Base/Actions/RpgSubActions.cpp index de16ed17..727f67a8 100644 --- a/src/Ai/Base/Actions/RpgSubActions.cpp +++ b/src/Ai/Base/Actions/RpgSubActions.cpp @@ -5,6 +5,7 @@ #include "RpgSubActions.h" +#include "BudgetValues.h" #include "ChooseRpgTargetAction.h" #include "EmoteAction.h" #include "Formations.h" @@ -51,10 +52,15 @@ GuidPosition RpgHelper::guidP() { return AI_VALUE(GuidPosition, "rpg target"); } ObjectGuid RpgHelper::guid() { return (ObjectGuid)guidP(); } -bool RpgHelper::InRange() -{ - return guidP() ? (guidP().sqDistance2d(bot) < INTERACTION_DISTANCE * INTERACTION_DISTANCE) : false; -} + bool RpgHelper::InRange() + { + GuidPosition targetGuid = guidP(); + if (!targetGuid) + return false; + + return bot->GetExactDist2dSq(targetGuid.GetPositionX(), targetGuid.GetPositionY()) < + INTERACTION_DISTANCE * INTERACTION_DISTANCE; + } void RpgHelper::setFacingTo(GuidPosition guidPosition) { @@ -250,6 +256,60 @@ Event RpgSellAction::ActionEvent(Event /*event*/) { return Event("rpg action", " std::string const RpgRepairAction::ActionName() { return "repair"; } +bool RpgTrainAction::isUseful() +{ + if (!rpg->InRange()) + return false; + + Creature* creature = rpg->guidP().GetCreature(); + if (!creature) + return false; + + if (!creature->IsInWorld() || creature->IsDuringRemoveFromWorld() || !creature->IsAlive()) + return false; + + return true; +} + +bool RpgTrainAction::isPossible() +{ + GuidPosition gp = rpg->guidP(); + + CreatureTemplate const* cinfo = gp.GetCreatureTemplate(); + if (!cinfo) + return false; + + Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cinfo->Entry); + if (!trainer) + return false; + + if (!trainer->IsTrainerValidForPlayer(bot)) + return false; + + FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cinfo->faction); + float reputationDiscount = bot->GetReputationPriceDiscount(factionTemplate); + uint32 currentGold = AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells); + + for (auto& spell : trainer->GetSpells()) + { + Trainer::Spell const* trainerSpell = trainer->GetSpell(spell.SpellId); + if (!trainerSpell) + continue; + + if (!trainer->CanTeachSpell(bot, trainerSpell)) + continue; + + if (currentGold < static_cast(floor(trainerSpell->MoneyCost * reputationDiscount))) + continue; + + // we only check if at least one spell can be learned from the trainer; + // otherwise, the train action should not be allowed + return true; + } + + return false; +} + std::string const RpgTrainAction::ActionName() { return "trainer"; } bool RpgHealAction::Execute(Event /*event*/) diff --git a/src/Ai/Base/Actions/RpgSubActions.h b/src/Ai/Base/Actions/RpgSubActions.h index 8d5fbda4..d5add642 100644 --- a/src/Ai/Base/Actions/RpgSubActions.h +++ b/src/Ai/Base/Actions/RpgSubActions.h @@ -165,6 +165,9 @@ class RpgTrainAction : public RpgSubAction public: RpgTrainAction(PlayerbotAI* botAI, std::string const name = "rpg train") : RpgSubAction(botAI, name) {} + bool isPossible() override; + bool isUseful() override; + private: std::string const ActionName() override; }; diff --git a/src/Ai/Base/Actions/TrainerAction.cpp b/src/Ai/Base/Actions/TrainerAction.cpp index 0f325609..4f46bba1 100644 --- a/src/Ai/Base/Actions/TrainerAction.cpp +++ b/src/Ai/Base/Actions/TrainerAction.cpp @@ -9,77 +9,120 @@ #include "Event.h" #include "PlayerbotFactory.h" #include "Playerbots.h" +#include "Trainer.h" -void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg) +bool TrainerAction::Execute(Event event) { - if (sPlayerbotAIConfig.autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold)) - { - if (AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells) < cost) - { - msg << " - too expensive"; - return; - } + std::string const param = event.getParam(); - bot->ModifyMoney(-int32(cost)); - } + Creature* target = GetCreatureTarget(); + if (!target) + return false; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId); - if (!spellInfo) - return; + Trainer::Trainer* trainer = sObjectMgr->GetTrainer(target->GetEntry()); + if (!trainer) + return false; - bool learned = false; - for (uint8 j = 0; j < 3; ++j) - { - if (spellInfo->Effects[j].Effect == SPELL_EFFECT_LEARN_SPELL) - { - uint32 learnedSpell = spellInfo->Effects[j].TriggerSpell; - if (!bot->HasSpell(learnedSpell)) - { - bot->learnSpell(learnedSpell); - learned = true; - } - } - } + // NOTE: Original version uses SpellIds here, but occasionally only inserts + // a single spell ID value from parameters. If someone wants to impl multiple + // spells as parameters, check SkipSpellsListAction::parseIds as an example. + uint32 spellId = chat->parseSpell(param); - if (!learned && !bot->HasSpell(tSpell.SpellId)) - bot->learnSpell(tSpell.SpellId); + bool learnSpells = param.find("learn") != std::string::npos || sRandomPlayerbotMgr.IsRandomBot(bot) || + (sPlayerbotAIConfig.allowLearnTrainerSpells && + // TODO: Rewrite to only exclude start primary profession skills and make config dependent. + (trainer->GetTrainerType() != Trainer::Type::Tradeskill || !botAI->HasActivePlayerMaster())); - msg << " - learned"; + Iterate(target, learnSpells, spellId); + + return true; } -void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells) +bool TrainerAction::isUseful() +{ + Creature* target = GetCreatureTarget(); + if (!target) + return false; + + if (!target->IsInWorld() || target->IsDuringRemoveFromWorld() || !target->IsAlive()) + return false; + + return target->IsTrainer(); +} + +bool TrainerAction::isPossible() +{ + Creature* target = GetCreatureTarget(); + if (!target) + return false; + + Trainer::Trainer* trainer = sObjectMgr->GetTrainer(target->GetEntry()); + if (!trainer) + return false; + + if (!trainer->IsTrainerValidForPlayer(bot)) + return false; + + if (trainer->GetSpells().empty()) + return false; + + return true; +} + +Unit* TrainerAction::GetTarget() +{ + // There are just two scenarios: the bot has a master or it doesn't. If the + // bot has a master, the master should target a unit; otherwise, the bot + // should target the unit itself. + if (Player* master = GetMaster()) + return master->GetSelectedUnit(); + + return bot->GetSelectedUnit(); +} + +Creature* TrainerAction::GetCreatureTarget() +{ + Unit* target = GetTarget(); + return target ? target->ToCreature() : nullptr; +} + +void TrainerAction::Iterate(Creature* creature, bool learnSpells, uint32 spellId) { TellHeader(creature); Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); - if (!trainer) return; - float fDiscountMod = bot->GetReputationPriceDiscount(creature); + float reputationDiscount = bot->GetReputationPriceDiscount(creature); uint32 totalCost = 0; for (auto& spell : trainer->GetSpells()) { - if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId))) + // simplified version of Trainer::TeachSpell method + + Trainer::Spell const* trainerSpell = trainer->GetSpell(spell.SpellId); + if (!trainerSpell) continue; - if (!spells.empty() && spells.find(spell.SpellId) == spells.end()) + if (!trainer->CanTeachSpell(bot, trainerSpell)) continue; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId); + if (spellId && trainerSpell->SpellId != spellId) + continue; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(trainerSpell->SpellId); if (!spellInfo) continue; - uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod)); + uint32 cost = static_cast(floor(trainerSpell->MoneyCost * reputationDiscount)); totalCost += cost; std::ostringstream out; out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost); - if (action) - (this->*action)(cost, spell, out); + if (learnSpells) + Learn(spellInfo, cost, out); botAI->TellMaster(out); } @@ -87,55 +130,25 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell TellFooter(totalCost); } -bool TrainerAction::Execute(Event event) +void TrainerAction::Learn(SpellInfo const* spellInfo, uint32 cost, std::ostringstream& out) { - std::string const text = event.getParam(); - - Player* master = GetMaster(); - - Creature* creature = botAI->GetCreature(bot->GetTarget()); - - if (master) + if (!botAI->HasCheat(BotCheatMask::gold)) { - creature = master->GetSelectedUnit() ? master->GetSelectedUnit()->ToCreature() : nullptr; - } - // if (AI_VALUE(GuidPosition, "rpg target") != bot->GetTarget()) - // if (master) - // creature = botAI->GetCreature(master->GetTarget()); - // else - // return false; + if (AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells) < cost) + { + out << " - too expensive"; + return; + } - if (!creature || !creature->IsTrainer()) - return false; - - Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); - - if (!trainer || !trainer->IsTrainerValidForPlayer(bot)) - return false; - - std::vector trainer_spells = trainer->GetSpells(); - - if (trainer_spells.empty()) - { - botAI->TellError("No spells can be learned from this trainer"); - return false; + bot->ModifyMoney(-static_cast(cost)); } - uint32 spell = chat->parseSpell(text); - SpellIds spells; - if (spell) - spells.insert(spell); - - if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr.IsRandomBot(bot) || - (sPlayerbotAIConfig.autoTrainSpells != "no" && - (trainer->GetTrainerType() != Trainer::Type::Tradeskill || - !botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make - // config dependent. - Iterate(creature, &TrainerAction::Learn, spells); + if (spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL)) + bot->CastSpell(bot, spellInfo->Id, true); else - Iterate(creature, nullptr, spells); + bot->learnSpell(spellInfo->Id, false); - return true; + out << " - learned"; } void TrainerAction::TellHeader(Creature* creature) @@ -245,7 +258,8 @@ bool MaintenanceAction::Execute(Event /*event*/) if (sPlayerbotAIConfig.altMaintenanceKeyring) factory.InitKeyring(); - if (sPlayerbotAIConfig.altMaintenanceGemsEnchants && bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel) + if (sPlayerbotAIConfig.altMaintenanceGemsEnchants && + bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel) factory.ApplyEnchantAndGemsNew(); } diff --git a/src/Ai/Base/Actions/TrainerAction.h b/src/Ai/Base/Actions/TrainerAction.h index e6046c3a..bfb6538d 100644 --- a/src/Ai/Base/Actions/TrainerAction.h +++ b/src/Ai/Base/Actions/TrainerAction.h @@ -8,7 +8,6 @@ #include "Action.h" #include "ChatHelper.h" -#include "Trainer.h" class Creature; class PlayerbotAI; @@ -21,11 +20,14 @@ public: TrainerAction(PlayerbotAI* botAI) : Action(botAI, "trainer") {} bool Execute(Event event) override; + bool isUseful() override; + bool isPossible() override; + Unit* GetTarget() override; private: - typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg); - void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells); - void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg); + Creature* GetCreatureTarget(); + void Iterate(Creature* creature, bool learnSpells, uint32 spellId); + void Learn(SpellInfo const* spellInfo, uint32 cost, std::ostringstream& out); void TellHeader(Creature* creature); void TellFooter(uint32 totalCost); }; diff --git a/src/Ai/Base/Strategy/AggressiveStrategy.cpp b/src/Ai/Base/Strategy/AggressiveStrategy.cpp new file mode 100644 index 00000000..385e9408 --- /dev/null +++ b/src/Ai/Base/Strategy/AggressiveStrategy.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 "AggressiveStrategy.h" + +#include "Playerbots.h" + +void AggressiveStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode( + "no target", + { + NextAction("aggressive target", 4.0f) + } + ) + ); +} diff --git a/src/Ai/Base/Strategy/AggressiveStrategy.h b/src/Ai/Base/Strategy/AggressiveStrategy.h new file mode 100644 index 00000000..8a81192e --- /dev/null +++ b/src/Ai/Base/Strategy/AggressiveStrategy.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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_AGGRESSIVESTRATEGY_H +#define _PLAYERBOT_AGGRESSIVESTRATEGY_H + +#include "NonCombatStrategy.h" + +class PlayerbotAI; + +class AggressiveStrategy : public NonCombatStrategy +{ +public: + AggressiveStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {} + + std::string const getName() override { return "aggressive"; } + void InitTriggers(std::vector& triggers) override; +}; + +#endif diff --git a/src/Ai/Base/StrategyContext.h b/src/Ai/Base/StrategyContext.h index 0cc6855f..60e6808c 100644 --- a/src/Ai/Base/StrategyContext.h +++ b/src/Ai/Base/StrategyContext.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_STRATEGYCONTEXT_H #define _PLAYERBOT_STRATEGYCONTEXT_H +#include "AggressiveStrategy.h" #include "AttackEnemyPlayersStrategy.h" #include "BattlegroundStrategy.h" #include "CastTimeStrategy.h" @@ -61,6 +62,7 @@ public: creators["gather"] = &StrategyContext::gather; creators["emote"] = &StrategyContext::emote; creators["passive"] = &StrategyContext::passive; + creators["aggressive"] = &StrategyContext::aggressive; creators["save mana"] = &StrategyContext::auto_save_mana; creators["food"] = &StrategyContext::food; creators["chat"] = &StrategyContext::chat; @@ -144,6 +146,7 @@ private: static Strategy* gather(PlayerbotAI* botAI) { return new GatherStrategy(botAI); } static Strategy* emote(PlayerbotAI* botAI) { return new EmoteStrategy(botAI); } static Strategy* passive(PlayerbotAI* botAI) { return new PassiveStrategy(botAI); } + static Strategy* aggressive(PlayerbotAI* botAI) { return new AggressiveStrategy(botAI); } // static Strategy* conserve_mana(PlayerbotAI* botAI) { return new ConserveManaStrategy(botAI); } static Strategy* auto_save_mana(PlayerbotAI* botAI) { return new HealerAutoSaveManaStrategy(botAI); } static Strategy* food(PlayerbotAI* botAI) { return new UseFoodStrategy(botAI); } diff --git a/src/Ai/Base/Trigger/RpgTriggers.cpp b/src/Ai/Base/Trigger/RpgTriggers.cpp index 0151c6c5..1031bd5e 100644 --- a/src/Ai/Base/Trigger/RpgTriggers.cpp +++ b/src/Ai/Base/Trigger/RpgTriggers.cpp @@ -163,52 +163,19 @@ bool RpgRepairTrigger::IsActive() return false; } -bool RpgTrainTrigger::IsTrainerOf(CreatureTemplate const* cInfo, Player* pPlayer) -{ - Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cInfo->Entry); - - if (trainer->GetTrainerType() == Trainer::Type::Mount && trainer->GetTrainerRequirement() != pPlayer->getRace()) - { - if (FactionTemplateEntry const* faction_template = sFactionTemplateStore.LookupEntry(cInfo->faction)) - if (pPlayer->GetReputationRank(faction_template->faction) == REP_EXALTED) - return true; - - return false; - } - - return trainer->IsTrainerValidForPlayer(pPlayer); -} - bool RpgTrainTrigger::IsActive() { - GuidPosition guidP(getGuidP()); - - if (!guidP.HasNpcFlag(UNIT_NPC_FLAG_TRAINER)) + GuidPosition gp = getGuidP(); + if (!gp) return false; - CreatureTemplate const* cInfo = guidP.GetCreatureTemplate(); - - if (!IsTrainerOf(cInfo, bot)) + if (!gp.HasNpcFlag(UNIT_NPC_FLAG_TRAINER)) return false; - Trainer::Trainer* trainer = sObjectMgr->GetTrainer(cInfo->Entry); - FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction); - float fDiscountMod = bot->GetReputationPriceDiscount(factionTemplate); + if (!AI_VALUE(bool, "can train")) + return false; - for (auto& spell : trainer->GetSpells()) - { - if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId))) - continue; - - uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod)); - - if (cost > AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells)) - continue; - - return true; - } - - return false; + return true; } bool RpgHealTrigger::IsActive() diff --git a/src/Ai/Base/Trigger/RpgTriggers.h b/src/Ai/Base/Trigger/RpgTriggers.h index ce8cd2c1..31734836 100644 --- a/src/Ai/Base/Trigger/RpgTriggers.h +++ b/src/Ai/Base/Trigger/RpgTriggers.h @@ -134,8 +134,6 @@ class RpgTrainTrigger : public RpgTrigger public: RpgTrainTrigger(PlayerbotAI* botAI, std::string const name = "rpg train") : RpgTrigger(botAI, name) {} - static bool IsTrainerOf(CreatureTemplate const* cInfo, Player* pPlayer); - bool IsActive() override; }; diff --git a/src/Ai/Base/Value/AggressiveTargetValue.cpp b/src/Ai/Base/Value/AggressiveTargetValue.cpp new file mode 100644 index 00000000..bbf39348 --- /dev/null +++ b/src/Ai/Base/Value/AggressiveTargetValue.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 "AggressiveTargetValue.h" + +#include "Playerbots.h" +#include "ServerFacade.h" +#include "SharedDefines.h" + +Unit* AggressiveTargetValue::Calculate() +{ + Player* master = GetMaster(); + + if (master && (master == bot || master->GetMapId() != bot->GetMapId() || master->IsBeingTeleported() || + !GET_PLAYERBOT_AI(master))) + master = nullptr; + + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + if (targets.empty()) + return nullptr; + + float aggroRange = sPlayerbotAIConfig.aggroDistance; + float distance = 0; + Unit* result = nullptr; + + for (ObjectGuid const guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + + if (!unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) + continue; + + 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 (abs(bot->GetPositionZ() - unit->GetPositionZ()) > INTERACTION_DISTANCE) + continue; + + if (!bot->InBattleground() && master && botAI->HasStrategy("follow", BotState::BOT_STATE_NON_COMBAT) && + ServerFacade::instance().GetDistance2d(master, unit) > aggroRange) + continue; + + if (!bot->IsWithinLOSInMap(unit)) + continue; + + if (bot->GetDistance(unit) > aggroRange) + continue; + + float newdistance = bot->GetDistance(unit); + if (!result || (newdistance < distance)) + { + distance = newdistance; + result = unit; + } + } + + return result; +} diff --git a/src/Ai/Base/Value/AggressiveTargetValue.h b/src/Ai/Base/Value/AggressiveTargetValue.h new file mode 100644 index 00000000..61b4d695 --- /dev/null +++ b/src/Ai/Base/Value/AggressiveTargetValue.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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_AGGRESSIVETARGETVALUE_H +#define _PLAYERBOT_AGGRESSIVETARGETVALUE_H + +#include "TargetValue.h" + +class PlayerbotAI; +class Unit; + +class AggressiveTargetValue : public TargetValue +{ +public: + AggressiveTargetValue(PlayerbotAI* botAI, std::string const name = "aggressive target") : TargetValue(botAI, name) {} + + Unit* Calculate() override; +}; + +#endif diff --git a/src/Ai/Base/Value/BudgetValues.cpp b/src/Ai/Base/Value/BudgetValues.cpp index daa7aca8..fbc04f49 100644 --- a/src/Ai/Base/Value/BudgetValues.cpp +++ b/src/Ai/Base/Value/BudgetValues.cpp @@ -9,7 +9,8 @@ uint32 MaxGearRepairCostValue::Calculate() { - uint32 TotalCost = 0; + uint32 totalCost = 0; + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) { uint16 pos = ((INVENTORY_SLOT_BAG_0 << 8) | i); @@ -43,15 +44,16 @@ uint32 MaxGearRepairCostValue::Calculate() uint32 costs = uint32(maxDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); - TotalCost += costs; + totalCost += costs; } - return TotalCost; + return totalCost; } uint32 RepairCostValue::Calculate() { - uint32 TotalCost = 0; + uint32 totalCost = 0; + for (int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) { uint16 pos = ((INVENTORY_SLOT_BAG_0 << 8) | i); @@ -86,45 +88,49 @@ uint32 RepairCostValue::Calculate() dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)]; uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod)); - TotalCost += costs; + totalCost += costs; } - return TotalCost; + return totalCost; } uint32 TrainCostValue::Calculate() { - uint32 TotalCost = 0; + uint32 totalCost = 0; - std::set spells; + std::unordered_set spells; - if (CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates()) + CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates(); + for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr) { - for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr) + if (!(itr->second.npcflag & UNIT_NPC_FLAG_TRAINER)) + continue; + + 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()) { - Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first); - - if (!trainer) + Trainer::Spell const* trainerSpell = trainer->GetSpell(spell.SpellId); + if (!trainerSpell) continue; - if (trainer->GetTrainerType() != Trainer::Type::Class || !trainer->IsTrainerValidForPlayer(bot)) + if (!trainer->CanTeachSpell(bot, trainerSpell)) continue; - for (auto& spell : trainer->GetSpells()) - { - if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId))) - continue; + if (spells.find(trainerSpell->SpellId) != spells.end()) + continue; - if (spells.find(spell.SpellId) != spells.end()) - continue; - - TotalCost += spell.MoneyCost; - spells.insert(spell.SpellId); - } + totalCost += trainerSpell->MoneyCost; + spells.insert(trainerSpell->SpellId); } } - return TotalCost; + return totalCost; } uint32 MoneyNeededForValue::Calculate() diff --git a/src/Ai/Base/Value/MaintenanceValues.cpp b/src/Ai/Base/Value/MaintenanceValues.cpp index 6439bef2..35ce1ee4 100644 --- a/src/Ai/Base/Value/MaintenanceValues.cpp +++ b/src/Ai/Base/Value/MaintenanceValues.cpp @@ -44,6 +44,11 @@ bool CanSellValue::Calculate() AI_VALUE2(uint32, "item count", "usage " + std::to_string(ITEM_USAGE_AH))) > 1; } +bool CanTrainValue::Calculate() +{ + return AI_VALUE2(uint32, "free money for", (uint32)NeedMoneyFor::spells) > 0; +} + bool CanFightEqualValue::Calculate() { return AI_VALUE(uint8, "durability") > 20; } bool CanFightEliteValue::Calculate() diff --git a/src/Ai/Base/Value/MaintenanceValues.h b/src/Ai/Base/Value/MaintenanceValues.h index 5916f7aa..7efcb593 100644 --- a/src/Ai/Base/Value/MaintenanceValues.h +++ b/src/Ai/Base/Value/MaintenanceValues.h @@ -58,6 +58,14 @@ public: bool Calculate() override; }; +class CanTrainValue : public BoolCalculatedValue +{ +public: + CanTrainValue(PlayerbotAI* botAI) : BoolCalculatedValue(botAI, "can train", 2 * 2000) {} + + bool Calculate() override; +}; + class CanFightEqualValue : public BoolCalculatedValue { public: diff --git a/src/Ai/Base/ValueContext.h b/src/Ai/Base/ValueContext.h index b67aee9b..6038db3b 100644 --- a/src/Ai/Base/ValueContext.h +++ b/src/Ai/Base/ValueContext.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_VALUECONTEXT_H #include "ActiveSpellValue.h" +#include "AggressiveTargetValue.h" #include "AlwaysLootListValue.h" #include "AoeHealValues.h" #include "AoeValues.h" @@ -51,6 +52,7 @@ #include "LineTargetValue.h" #include "LogLevelValue.h" #include "LootStrategyValue.h" +#include "LootValues.h" #include "MaintenanceValues.h" #include "ManaSaveLevelValue.h" #include "NearestAdsValue.h" @@ -143,6 +145,7 @@ public: creators["pet target"] = &ValueContext::pet_target; creators["old target"] = &ValueContext::old_target; creators["grind target"] = &ValueContext::grind_target; + creators["aggressive target"] = &ValueContext::aggressive_target; creators["rti target"] = &ValueContext::rti_target; creators["rti cc target"] = &ValueContext::rti_cc_target; creators["duel target"] = &ValueContext::duel_target; @@ -272,6 +275,7 @@ public: creators["can repair"] = &ValueContext::can_repair; creators["should sell"] = &ValueContext::should_sell; creators["can sell"] = &ValueContext::can_sell; + creators["can train"] = &ValueContext::can_train; creators["can fight equal"] = &ValueContext::can_fight_equal; creators["can fight elite"] = &ValueContext::can_fight_elite; creators["can fight boss"] = &ValueContext::can_fight_boss; @@ -457,6 +461,7 @@ private: static UntypedValue* current_cc_target(PlayerbotAI* botAI) { return new CurrentCcTargetValue(botAI); } static UntypedValue* pet_target(PlayerbotAI* botAI) { return new PetTargetValue(botAI); } static UntypedValue* grind_target(PlayerbotAI* botAI) { return new GrindTargetValue(botAI); } + static UntypedValue* aggressive_target(PlayerbotAI* botAI) { return new AggressiveTargetValue(botAI); } static UntypedValue* rti_target(PlayerbotAI* botAI) { return new RtiTargetValue(botAI); } static UntypedValue* rti_cc_target(PlayerbotAI* botAI) { return new RtiCcTargetValue(botAI); } static UntypedValue* duel_target(PlayerbotAI* botAI) { return new DuelTargetValue(botAI); } @@ -519,6 +524,7 @@ private: static UntypedValue* can_repair(PlayerbotAI* botAI) { return new CanRepairValue(botAI); } static UntypedValue* should_sell(PlayerbotAI* botAI) { return new ShouldSellValue(botAI); } static UntypedValue* can_sell(PlayerbotAI* botAI) { return new CanSellValue(botAI); } + static UntypedValue* can_train(PlayerbotAI* botAI) { return new CanTrainValue(botAI); } static UntypedValue* can_fight_equal(PlayerbotAI* botAI) { return new CanFightEqualValue(botAI); } static UntypedValue* can_fight_elite(PlayerbotAI* botAI) { return new CanFightEliteValue(botAI); } static UntypedValue* can_fight_boss(PlayerbotAI* botAI) { return new CanFightBossValue(botAI); } diff --git a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp b/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp index 577575d4..27546e79 100644 --- a/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp +++ b/src/Ai/Dungeon/PitOfSaron/Action/PitOfSaronActions.cpp @@ -201,8 +201,7 @@ bool IckAndKrickAction::ExplosiveBarrage(bool explosiveBarrage, Unit* boss) continue; // Check if position is within maximum allowed distance from boss - if (boss && sqrt(pow(potentialPos.GetPositionX() - boss->GetPositionX(), 2) + - pow(potentialPos.GetPositionY() - boss->GetPositionY(), 2)) > MAX_BOSS_DISTANCE) + if (boss && boss->GetDistance2d(potentialPos.GetPositionX(), potentialPos.GetPositionY()) > MAX_BOSS_DISTANCE) continue; // Score this position based on: @@ -215,8 +214,7 @@ bool IckAndKrickAction::ExplosiveBarrage(bool explosiveBarrage, Unit* boss) float minOrbDist = std::numeric_limits::max(); for (Unit* orb : orbs) { - float orbDist = sqrt(pow(potentialPos.GetPositionX() - orb->GetPositionX(), 2) + - pow(potentialPos.GetPositionY() - orb->GetPositionY(), 2)); + float orbDist = orb->GetDistance2d(potentialPos.GetPositionX(), potentialPos.GetPositionY()); minOrbDist = std::min(minOrbDist, orbDist); } score += minOrbDist * 2.0f; // Weight orb distance more heavily @@ -232,8 +230,7 @@ bool IckAndKrickAction::ExplosiveBarrage(bool explosiveBarrage, Unit* boss) // Factor in proximity to boss (closer is better, as long as we're safe from orbs) if (boss) { - float bossDist = sqrt(pow(potentialPos.GetPositionX() - boss->GetPositionX(), 2) + - pow(potentialPos.GetPositionY() - boss->GetPositionY(), 2)); + float bossDist = boss->GetDistance2d(potentialPos.GetPositionX(), potentialPos.GetPositionY()); // Add points for being closer to boss (inverse relationship) // but only if we're safely away from orbs if (minOrbDist > SAFE_DISTANCE) diff --git a/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp b/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp index 7ed82f19..b29549b8 100644 --- a/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp +++ b/src/Ai/Raid/GruulsLair/Util/RaidGruulsLairHelpers.cpp @@ -128,7 +128,7 @@ namespace GruulsLairHelpers Unit* krosh = botAI->GetAiObjectContext()->GetValue("find target", "krosh firehand")->Get(); if (krosh && krosh->IsAlive()) { - float dist = sqrt(pow(pos.GetPositionX() - krosh->GetPositionX(), 2) + pow(pos.GetPositionY() - krosh->GetPositionY(), 2)); + float dist = krosh->GetDistance2d(pos.GetPositionX(), pos.GetPositionY()); if (dist < KROSH_SAFE_DISTANCE) isSafe = false; } @@ -136,7 +136,7 @@ namespace GruulsLairHelpers Unit* maulgar = botAI->GetAiObjectContext()->GetValue("find target", "high king maulgar")->Get(); if (botAI->IsRanged(bot) && maulgar && maulgar->IsAlive()) { - float dist = sqrt(pow(pos.GetPositionX() - maulgar->GetPositionX(), 2) + pow(pos.GetPositionY() - maulgar->GetPositionY(), 2)); + float dist = maulgar->GetDistance2d(pos.GetPositionX(), pos.GetPositionY()); if (dist < MAULGAR_SAFE_DISTANCE) isSafe = false; } @@ -182,7 +182,7 @@ namespace GruulsLairHelpers if (IsPositionSafe(botAI, bot, candidatePos)) { - float movementDistance = sqrt(pow(destX - bot->GetPositionX(), 2) + pow(destY - bot->GetPositionY(), 2)); + float movementDistance = bot->GetDistance2d(destX, destY); if (movementDistance < bestScore) { bestScore = movementDistance; diff --git a/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp b/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp index 95f5bc3b..762f0287 100644 --- a/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp +++ b/src/Ai/Raid/Icecrown/Action/RaidIccActions.cpp @@ -1800,8 +1800,7 @@ bool IccRotfaceTankPositionAction::HandleBigOozePositioning(Unit*) Unit* puddle = botAI->GetUnit(puddleGuid); if (puddle && botAI->GetAura("Ooze Flood", puddle)) { - float puddleDistance = std::sqrt(std::pow(newX - puddle->GetPositionX(), 2) + - std::pow(newY - puddle->GetPositionY(), 2)); + float puddleDistance = puddle->GetDistance2d(newX, newY); if (puddleDistance < puddleSafeDistance) { isSafeFromPuddles = false; @@ -1921,8 +1920,7 @@ bool IccRotfaceGroupPositionAction::MoveAwayFromPuddle(Unit* boss, Unit* puddle, float moveZ = bot->GetPositionZ(); // Check distances and line of sight - float newPuddleDistance = - sqrt(pow(moveX - puddle->GetPositionX(), 2) + pow(moveY - puddle->GetPositionY(), 2)); + float newPuddleDistance = puddle->GetDistance2d(moveX, moveY); float newCenterDistance = sqrt(pow(moveX - ICC_ROTFACE_CENTER_POSITION.GetPositionX(), 2) + pow(moveY - ICC_ROTFACE_CENTER_POSITION.GetPositionY(), 2)); @@ -2125,8 +2123,7 @@ bool IccRotfaceGroupPositionAction::FindAndMoveFromClosestMember(Unit* boss, Uni // Ensure the target position is at least 30 yards away from the puddle if (puddle) { - float puddleDistance = std::sqrt(std::pow(targetX - puddle->GetPositionX(), 2) + - std::pow(targetY - puddle->GetPositionY(), 2)); + float puddleDistance = puddle->GetDistance2d(targetX, targetY); if (puddleDistance < puddleSafeDistance) { // Adjust the target position to move further away from the puddle @@ -2203,8 +2200,7 @@ bool IccRotfaceMoveAwayFromExplosionAction::MoveToRandomSafeLocation() if (!puddle || !botAI->HasAura("Ooze Flood", puddle)) continue; - float puddleDistance = - std::sqrt(std::pow(moveX - puddle->GetPositionX(), 2) + std::pow(moveY - puddle->GetPositionY(), 2)); + float puddleDistance = puddle->GetDistance2d(moveX, moveY); if (puddleDistance < 30.0f) { // Adjust the position to move further away from the puddle @@ -2407,7 +2403,7 @@ bool IccPutricideGrowingOozePuddleAction::IsPositionTooCloseToOtherPuddles(float if (Aura* grow = unit->GetAura(SPELL_GROW_AURA)) safeDistance += (grow->GetStackAmount() * STACK_MULTIPLIER); - float dist = sqrt(pow(x - unit->GetPositionX(), 2) + pow(y - unit->GetPositionY(), 2)); + float dist = unit->GetDistance2d(x, y); if (dist < safeDistance) return true; } @@ -2650,7 +2646,7 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) float minGasBombDist = FLT_MAX; for (Unit* bomb : gasBombs) { - float bombDist = sqrt(pow(testX - bomb->GetPositionX(), 2) + pow(testY - bomb->GetPositionY(), 2)); + float bombDist = bomb->GetDistance2d(testX, testY); if (bombDist < minGasBombDist) minGasBombDist = bombDist; } @@ -2717,8 +2713,7 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) float minEmergencyGasBombDist = FLT_MAX; for (Unit* bomb : gasBombs) { - float bombDist = sqrt(pow(emergencyPos.GetPositionX() - bomb->GetPositionX(), 2) + - pow(emergencyPos.GetPositionY() - bomb->GetPositionY(), 2)); + float bombDist = bomb->GetDistance2d(emergencyPos.GetPositionX(), emergencyPos.GetPositionY()); if (bombDist < minEmergencyGasBombDist) minEmergencyGasBombDist = bombDist; } @@ -4487,8 +4482,7 @@ bool IccBqlGroupPositionAction::HandleGroupPosition(Unit* boss, Aura* frenzyAura // Maintain minimum distance from center position (if too close to center, move out) float centerX = ICC_BQL_CENTER_POSITION.GetPositionX(); float centerY = ICC_BQL_CENTER_POSITION.GetPositionY(); - float centerDist = - std::sqrt(std::pow(bot->GetPositionX() - centerX, 2) + std::pow(bot->GetPositionY() - centerY, 2)); + float centerDist = bot->GetDistance2d(centerX, centerY); if (centerDist < MIN_CENTER_DISTANCE && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f)) { float dx = bot->GetPositionX() - centerX; @@ -6904,7 +6898,7 @@ bool IccLichKingShadowTrapAction::Execute(Event /*event*/) Unit* trap = botAI->GetUnit(trapGuid); if (!trap) continue; - float distToTrap = sqrt(pow(testX - trap->GetPositionX(), 2) + pow(testY - trap->GetPositionY(), 2)); + float distToTrap = trap->GetDistance2d(testX, testY); if (distToTrap < SAFE_DISTANCE) { isSafe = false; diff --git a/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp b/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp index a56d4b85..344dda5b 100644 --- a/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp +++ b/src/Ai/Raid/Magtheridon/Util/RaidMagtheridonHelpers.cpp @@ -132,7 +132,7 @@ namespace MagtheridonHelpers } for (Unit* hazard : debrisHazards) { - float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2)); + float dist = hazard->GetDistance2d(x, y); if (dist < 9.0f) return false; } @@ -145,7 +145,7 @@ namespace MagtheridonHelpers if (!go || go->GetEntry() != GO_BLAZE) continue; - float dist = std::sqrt(std::pow(x - go->GetPositionX(), 2) + std::pow(y - go->GetPositionY(), 2)); + float dist = go->GetDistance2d(x, y); if (dist < 5.0f) return false; } diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index bd27bece..06645cd5 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -262,10 +262,10 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver) // that removes the distance check and keeps all other checks switch (questGiver->GetTypeId()) { - case TYPEID_UNIT: + case TYPEID_UNIT: // Player::GetNPCIfCanInteractWith { ObjectGuid guid = questGiver->GetGUID(); - uint32 npcflagmask = UNIT_NPC_FLAG_QUESTGIVER; + // unit checks if (!guid) return false; @@ -292,7 +292,7 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver) return false; // appropriate npc type - if (npcflagmask && !creature->HasNpcFlag(NPCFlags(npcflagmask))) + if (!creature->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER)) return false; // not allow interaction under control, but allow with own pets @@ -303,35 +303,24 @@ bool NewRpgBaseAction::CanInteractWithQuestGiver(Object* questGiver) if (creature->GetReactionTo(bot) <= REP_UNFRIENDLY) return false; - Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry()); - - // pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train - // dual wield to a shaman :/ too many to change in sql and watch in the future pussywizard: this function is - // not used when talking, but when already taking action (buy spell, reset talents, show spell list) - if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && - trainer->GetTrainerType() == Trainer::Type::Class && - !trainer->IsTrainerValidForPlayer(bot)) - return false; - return true; } - case TYPEID_GAMEOBJECT: + case TYPEID_GAMEOBJECT: // Player::GetGameObjectIfCanInteractWith { ObjectGuid guid = questGiver->GetGUID(); - GameobjectTypes type = GAMEOBJECT_TYPE_QUESTGIVER; + if (GameObject* go = bot->GetMap()->GetGameObject(guid)) { - if (go->GetGoType() == type) + if (go->GetGoType() == GAMEOBJECT_TYPE_QUESTGIVER) { // Players cannot interact with gameobjects that use the "Point" icon if (go->GetGOInfo()->IconName == "Point") - { return false; - } return true; } } + return false; } // unused for now diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index f5026057..279f749b 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -4739,38 +4739,13 @@ void PlayerbotFactory::InitAttunementQuests() uint32 currentXP = bot->GetUInt32Value(PLAYER_XP); - // List of attunement quest IDs - std::list attunementQuestsTBC = { - // Caverns of Time - Part 1 - 10279, // To The Master's Lair - 10277, // The Caverns of Time - - // Caverns of Time - Part 2 (Escape from Durnholde Keep) - 10282, // Old Hillsbrad - 10283, // Taretha's Diversion - 10284, // Escape from Durnholde - 10285, // Return to Andormu - - // Caverns of Time - Part 2 (The Black Morass) - 10296, // The Black Morass - 10297, // The Opening of the Dark Portal - 10298, // Hero of the Brood - - // Magister's Terrace Attunement - 11481, // Crisis at the Sunwell - 11482, // Duty Calls - 11488, // Magisters' Terrace - 11490, // The Scryer's Scryer - 11492 // Hard to Kill - }; - // Complete all level-appropriate attunement quests for the bot if (level >= 60) { std::list questsToComplete; // Check each quest status before adding to the completion list - for (uint32 questId : attunementQuestsTBC) + for (uint32 questId : sPlayerbotAIConfig.attunementQuests) { QuestStatus questStatus = bot->GetQuestStatus(questId); diff --git a/src/Mgr/Item/StatsCollector.cpp b/src/Mgr/Item/StatsCollector.cpp index 7818dc73..dd669785 100644 --- a/src/Mgr/Item/StatsCollector.cpp +++ b/src/Mgr/Item/StatsCollector.cpp @@ -45,18 +45,18 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) switch (proto->Spells[j].SpellTrigger) { case ITEM_SPELLTRIGGER_ON_USE: - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown); + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, Milliseconds(proto->Spells[j].SpellCooldown)); break; case ITEM_SPELLTRIGGER_ON_EQUIP: - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0); + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, Milliseconds(0)); break; case ITEM_SPELLTRIGGER_CHANCE_ON_HIT: if (type_ & CollectorType::MELEE) { if (proto->Spells[j].SpellPPMRate > 0.01f) - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate); + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, Milliseconds(static_cast(60000 / proto->Spells[j].SpellPPMRate))); else - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8 + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, Milliseconds(static_cast(60000 / 1.8f))); // Default PPM = 1.8 } break; default: @@ -71,7 +71,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } } -void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 spellCooldown) +void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, Milliseconds spellCooldown) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); @@ -81,21 +81,21 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s if (SpecialSpellFilter(spellId)) return; - const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + const SpellProcEntry* eventEntry = sSpellMgr->GetSpellProcEntry(spellInfo->Id); - uint32 triggerCooldown = eventEntry ? eventEntry->cooldown : 0; + Milliseconds triggerCooldown = eventEntry ? eventEntry->Cooldown : 0ms; bool canNextTrigger = true; uint32 procFlags; uint32 procChance; - if (eventEntry && eventEntry->procFlags) - procFlags = eventEntry->procFlags; + if (eventEntry && eventEntry->ProcFlags) + procFlags = eventEntry->ProcFlags; else procFlags = spellInfo->ProcFlags; - if (eventEntry && eventEntry->customChance) - procChance = eventEntry->customChance; + if (eventEntry && eventEntry->Chance) + procChance = eventEntry->Chance; else procChance = spellInfo->ProcChance; bool lowChance = procChance <= 5; @@ -142,11 +142,11 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s break; float coverage; - if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1) + if (spellCooldown.count() <= 2000 || spellInfo->GetDuration() == -1) coverage = 1.0f; else coverage = - std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown)); + std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown.count())); multiplier *= coverage; HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown); @@ -155,9 +155,9 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s case SPELL_EFFECT_HEAL: { /// @todo Handle spell without cooldown - if (!spellCooldown) + if (!spellCooldown.count()) break; - float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); + float normalizedCd = std::max((float)spellCooldown.count() / 1000, 5.0f); int32 val = AverageValue(effectInfo); float transfer_multiplier = 1; stats[STATS_TYPE_HEAL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; @@ -166,11 +166,11 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s case SPELL_EFFECT_ENERGIZE: { /// @todo Handle spell without cooldown - if (!spellCooldown) + if (!spellCooldown.count()) break; if (effectInfo.MiscValue != POWER_MANA) break; - float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); + float normalizedCd = std::max((float)spellCooldown.count() / 1000, 5.0f); int32 val = AverageValue(effectInfo); float transfer_multiplier = 0.2; stats[STATS_TYPE_MANA_REGENERATION] += (float)val / normalizedCd * multiplier * transfer_multiplier; @@ -179,9 +179,9 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s case SPELL_EFFECT_SCHOOL_DAMAGE: { /// @todo Handle spell without cooldown - if (!spellCooldown) + if (!spellCooldown.count()) break; - float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); + float normalizedCd = std::max((float)spellCooldown.count() / 1000, 5.0f); int32 val = AverageValue(effectInfo); if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) { @@ -352,12 +352,12 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId) bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict) { - const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + const SpellProcEntry* eventEntry = sSpellMgr->GetSpellProcEntry(spellInfo->Id); uint32 spellFamilyName = 0; if (eventEntry) { - spellFamilyName = eventEntry->spellFamilyName; - flag96 spellFamilyMask = eventEntry->spellFamilyMask; + spellFamilyName = eventEntry->SpellFamilyName; + flag96 spellFamilyMask = eventEntry->SpellFamilyMask; if (spellFamilyName != 0) { if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict)) @@ -548,7 +548,7 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) } void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, - uint32 triggerCooldown) + Milliseconds triggerCooldown) { if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) return; diff --git a/src/Mgr/Item/StatsCollector.h b/src/Mgr/Item/StatsCollector.h index 1e54546f..a6ffa903 100644 --- a/src/Mgr/Item/StatsCollector.h +++ b/src/Mgr/Item/StatsCollector.h @@ -65,7 +65,7 @@ public: StatsCollector(StatsCollector& stats) = default; void Reset(); void CollectItemStats(ItemTemplate const* proto); - void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1); + void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, Milliseconds spellCooldown = -1ms); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant, uint32 default_enchant_amount = 0); bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict = true); bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true); @@ -79,7 +79,7 @@ private: bool SpecialEnchantFilter(uint32 enchantSpellId); void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, - uint32 triggerCooldown); + Milliseconds triggerCooldown); float AverageValue(const SpellEffectInfo& effectInfo); private: diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 2909df5e..be17e28b 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -228,13 +228,13 @@ WorldPosition WorldPosition::offset(WorldPosition* center) float WorldPosition::size() { - return sqrt(pow(GetPositionX(), 2.0) + pow(GetPositionY(), 2.0) + pow(GetPositionZ(), 2.0)); + return GetExactDist(0.0f, 0.0f, 0.0f); } float WorldPosition::distance(WorldPosition* center) { if (GetMapId() == center->GetMapId()) - return relPoint(center).size(); + return GetExactDist(center->GetPositionX(), center->GetPositionY(), center->GetPositionZ()); // this -> mapTransfer | mapTransfer -> center return TravelMgr::instance().mapTransDistance(*this, *center); @@ -243,7 +243,7 @@ float WorldPosition::distance(WorldPosition* center) float WorldPosition::fDist(WorldPosition* center) { if (GetMapId() == center->GetMapId()) - return sqrt(sqDistance2d(center)); + return GetExactDist2d(center->GetPositionX(), center->GetPositionY()); // this -> mapTransfer | mapTransfer -> center return TravelMgr::instance().fastMapTransDistance(*this, *center); diff --git a/src/Mgr/Travel/TravelMgr.h b/src/Mgr/Travel/TravelMgr.h index 7e2a40b1..1f5f848c 100644 --- a/src/Mgr/Travel/TravelMgr.h +++ b/src/Mgr/Travel/TravelMgr.h @@ -179,8 +179,7 @@ public: // Quick square distance in 2d plane. float sqDistance2d(WorldPosition center) { - return (GetPositionX() - center.GetPositionX()) * (GetPositionX() - center.GetPositionX()) + - (GetPositionY() - center.GetPositionY()) * (GetPositionY() - center.GetPositionY()); + return GetExactDist2dSq(center.GetPositionX(), center.GetPositionY()); } // Quick square distance calculation without map check. Used for getting the minimum distant points. @@ -193,8 +192,7 @@ public: float sqDistance2d(WorldPosition* center) { - return (GetPositionX() - center->GetPositionX()) * (GetPositionX() - center->GetPositionX()) + - (GetPositionY() - center->GetPositionY()) * (GetPositionY() - center->GetPositionY()); + return GetExactDist2dSq(center->GetPositionX(), center->GetPositionY()); } float sqDistance(WorldPosition* center) diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index b5113694..c3aee910 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -179,6 +179,9 @@ bool PlayerbotAIConfig::Initialize() "179490,141596,160836,160845,179516,176224,181085,176112,128308,128403," "165739,165738,175245,175970,176325,176327,123329,2560"), disallowedGameObjects); + LoadSet>( + sConfigMgr->GetOption("AiPlayerbot.AttunementQuests", "10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901"), + attunementQuests); botAutologin = sConfigMgr->GetOption("AiPlayerbot.BotAutologin", false); randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 500); @@ -620,7 +623,7 @@ bool PlayerbotAIConfig::Initialize() syncQuestWithPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestWithPlayer", true); syncQuestForPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestForPlayer", false); dropObsoleteQuests = sConfigMgr->GetOption("AiPlayerbot.DropObsoleteQuests", true); - autoTrainSpells = sConfigMgr->GetOption("AiPlayerbot.AutoTrainSpells", "yes"); + allowLearnTrainerSpells = sConfigMgr->GetOption("AiPlayerbot.AllowLearnTrainerSpells", true); autoPickTalents = sConfigMgr->GetOption("AiPlayerbot.AutoPickTalents", true); autoUpgradeEquip = sConfigMgr->GetOption("AiPlayerbot.AutoUpgradeEquip", false); hunterWolfPet = sConfigMgr->GetOption("AiPlayerbot.HunterWolfPet", 0); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 27177565..970bbd50 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -98,6 +98,7 @@ public: std::set aoeAvoidSpellWhitelist; bool tellWhenAvoidAoe; std::set disallowedGameObjects; + std::set attunementQuests; uint32 openGoSpell; bool randomBotAutologin; @@ -352,7 +353,7 @@ public: bool syncQuestWithPlayer; bool syncQuestForPlayer; bool dropObsoleteQuests; - std::string autoTrainSpells; + bool allowLearnTrainerSpells; bool autoPickTalents; bool autoUpgradeEquip; int32 hunterWolfPet;