Files
mod-playerbots/src/Ai/Base/Actions/ChangeTalentsAction.cpp
Keleborn 441f9f7552 Warnings PR 1: Event warnings and headers (#2106)
# Pull Request

This is the first in a series of PRs intended to eliminate warnings in
the module. The design intent is to eliminate the calling event when not
needed in the body of the function. Based off of SmashingQuasars work.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)

---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-14 20:55:10 +01:00

388 lines
12 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ChangeTalentsAction.h"
#include "AiFactory.h"
#include "ChatHelper.h"
#include "Event.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h"
#include "AiObjectContext.h"
#include "Log.h"
#include "RandomPlayerbotMgr.h"
bool ChangeTalentsAction::Execute(Event event)
{
auto* flag = botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs"); // Added for custom Glyphs
if (flag->Get()) // Added for custom Glyphs
{
flag->Set(false);
LOG_INFO("playerbots", "Custom Glyph Flag set to OFF");
}
std::string param = event.getParam();
std::ostringstream out;
if (!param.empty())
{
if (param.find("help") != std::string::npos)
{
out << TalentsHelp();
}
else if (param.find("switch") != std::string::npos)
{
if (param.find("switch 1") != std::string::npos)
{
bot->ActivateSpec(0);
out << "Active first talent";
botAI->ResetStrategies();
}
else if (param.find("switch 2") != std::string::npos)
{
if (bot->GetSpecsCount() == 1 && bot->GetLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))
{
bot->CastSpell(bot, 63680, true, nullptr, nullptr, bot->GetGUID());
bot->CastSpell(bot, 63624, true, nullptr, nullptr, bot->GetGUID());
}
bot->ActivateSpec(1);
out << "Active second talent";
botAI->ResetStrategies();
}
}
else if (param.find("autopick") != std::string::npos)
{
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitTalentsTree(true);
out << "Auto pick talents";
botAI->ResetStrategies();
}
else if (param.find("spec list") != std::string::npos)
{
out << SpecList();
}
else if (param.find("spec ") != std::string::npos)
{
param = param.substr(5);
out << SpecPick(param);
botAI->ResetStrategies();
}
else if (param.find("apply ") != std::string::npos)
{
param = param.substr(6);
out << SpecApply(param);
botAI->ResetStrategies();
}
else
{
out << "Unknown command.";
}
}
else
{
uint32 tab = AiFactory::GetPlayerSpecTab(bot);
out << "My current talent spec is: "
<< "|h|cffffffff";
out << chat->FormatClass(bot, tab) << "\n";
out << TalentsHelp();
}
botAI->TellMaster(out);
return true;
}
std::string ChangeTalentsAction::TalentsHelp()
{
std::ostringstream out;
out << "Talents usage: talents switch <1/2>, talents autopick, talents spec list, "
"talents spec <specName>, talents apply <link>.";
return out.str();
}
std::string ChangeTalentsAction::SpecList()
{
int cls = bot->getClass();
int specFound = 0;
std::ostringstream out;
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
{
if (sPlayerbotAIConfig.premadeSpecName[cls][specNo].size() == 0)
{
break;
}
specFound++;
std::ostringstream out;
std::vector<std::vector<uint32>> parsed = sPlayerbotAIConfig.parsedSpecLinkOrder[cls][specNo][80];
std::unordered_map<int, int> tabCount;
tabCount[0] = tabCount[1] = tabCount[2] = 0;
for (auto& item : parsed)
{
tabCount[item[0]] += item[3];
}
out << specFound << ". " << sPlayerbotAIConfig.premadeSpecName[cls][specNo] << " (";
out << tabCount[0] << "-" << tabCount[1] << "-" << tabCount[2] << ")";
botAI->TellMasterNoFacing(out.str());
}
out << "Total " << specFound << " specs found";
return out.str();
}
std::string ChangeTalentsAction::SpecPick(std::string param)
{
int cls = bot->getClass();
// int specFound = 0; //not used, line marked for removal.
for (int specNo = 0; specNo < MAX_SPECNO; ++specNo)
{
if (sPlayerbotAIConfig.premadeSpecName[cls][specNo].size() == 0)
{
break;
}
if (sPlayerbotAIConfig.premadeSpecName[cls][specNo] == param)
{
PlayerbotFactory::InitTalentsBySpecNo(bot, specNo, true);
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitGlyphs(false);
std::ostringstream out;
out << "Picking " << sPlayerbotAIConfig.premadeSpecName[cls][specNo];
return out.str();
}
}
std::ostringstream out;
out << "Spec " << param << " not found";
return out.str();
}
std::string ChangeTalentsAction::SpecApply(std::string param)
{
int cls = bot->getClass();
std::ostringstream out;
std::vector<std::vector<uint32>> parsedSpecLink = PlayerbotAIConfig::ParseTempTalentsOrder(cls, param);
if (parsedSpecLink.size() == 0)
{
out << "Invalid link " << param;
return out.str();
}
PlayerbotFactory::InitTalentsByParsedSpecLink(bot, parsedSpecLink, true);
out << "Applying " << param;
return out.str();
}
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(std::string const findName)
// {
// std::vector<TalentPath*> ret;
// // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // {
// // if (findName.empty() || path.name.find(findName) != std::string::npos)
// // {
// // ret.push_back(&path);
// // }
// // }
// return ret;
// }
// std::vector<TalentPath*> ChangeTalentsAction::getPremadePaths(TalentSpec* oldSpec)
// {
// std::vector<TalentPath*> ret;
// // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // {
// // TalentSpec newSpec = *GetBestPremadeSpec(path.id);
// // newSpec.CropTalents(bot->GetLevel());
// // if (oldSpec->isEarlierVersionOf(newSpec))
// // {
// // ret.push_back(&path);
// // }
// // }
// return ret;
// }
// TalentPath* ChangeTalentsAction::getPremadePath(uint32 id)
// {
// // for (auto& path : sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath)
// // {
// // if (id == path.id)
// // {
// // return &path;
// // }
// // }
// // return &sPlayerbotAIConfig.classSpecs[bot->getClass()].talentPath[0];
// return nullptr;
// }
// void ChangeTalentsAction::listPremadePaths(std::vector<TalentPath*> paths, std::ostringstream* out)
// {
// if (paths.size() == 0)
// {
// *out << "No predefined talents found..";
// }
// *out << "|h|cffffffff";
// for (auto path : paths)
// {
// *out << path->name << " (" << path->talentSpec.back().FormatSpec(bot) << "), ";
// }
// out->seekp(-2, out->cur);
// *out << ".";
// }
// TalentPath* ChangeTalentsAction::PickPremadePath(std::vector<TalentPath*> paths, bool useProbability)
// {
// uint32 totProbability = 0;
// uint32 curProbability = 0;
// if (paths.size() == 1)
// return paths[0];
// for (auto path : paths)
// {
// totProbability += useProbability ? path->probability : 1;
// }
// totProbability = urand(0, totProbability);
// for (auto path : paths)
// {
// curProbability += (useProbability ? path->probability : 1);
// if (curProbability >= totProbability)
// return path;
// }
// return paths[0];
// }
// bool ChangeTalentsAction::AutoSelectTalents(std::ostringstream* out)
// {
// // Does the bot have talentpoints?
// if (bot->GetLevel() < 10)
// {
// *out << "No free talent points.";
// return false;
// }
// uint32 specNo = sRandomPlayerbotMgr.GetValue(bot->GetGUID().GetCounter(), "specNo");
// uint32 specId = specNo - 1;
// std::string specLink = sRandomPlayerbotMgr.GetData(bot->GetGUID().GetCounter(), "specLink");
// //Continue the current spec
// if (specNo > 0)
// {
// TalentSpec newSpec = *GetBestPremadeSpec(specId);
// newSpec.CropTalents(bot->GetLevel());
// newSpec.ApplyTalents(bot, out);
// if (newSpec.GetTalentPoints() > 0)
// {
// *out << "Upgrading spec " << "|h|cffffffff" << getPremadePath(specId)->name << "" <<
// newSpec.FormatSpec(bot);
// }
// }
// else if (!specLink.empty())
// {
// TalentSpec newSpec(bot, specLink);
// newSpec.CropTalents(bot->GetLevel());
// newSpec.ApplyTalents(bot, out);
// if (newSpec.GetTalentPoints() > 0)
// {
// *out << "Upgrading saved spec "
// << "|h|cffffffff" << chat->FormatClass(bot, newSpec.highestTree()) << " (" <<
// newSpec.FormatSpec(bot) << ")";
// }
// }
// //Spec was not found or not sufficient
// if (bot->GetFreeTalentPoints() > 0 || (!specNo && specLink.empty()))
// {
// TalentSpec oldSpec(bot);
// std::vector<TalentPath*> paths = getPremadePaths(&oldSpec);
// if (paths.size() == 0) //No spec like the old one found. Pick any.
// {
// if (bot->CalculateTalentsPoints() > 0)
// *out << "No specs like the current spec found. ";
// paths = getPremadePaths("");
// }
// if (paths.size() == 0)
// {
// *out << "No predefined talents found for this class.";
// specId = -1;
// // specLink = "";
// }
// else if (paths.size() > 1 && false/*!sPlayerbotAIConfig.autoPickTalents*/ &&
// !sRandomPlayerbotMgr.IsRandomBot(bot))
// {
// *out << "Found multiple specs: ";
// listPremadePaths(paths, out);
// }
// else
// {
// specId = PickPremadePath(paths, sRandomPlayerbotMgr.IsRandomBot(bot))->id;
// TalentSpec newSpec = *GetBestPremadeSpec(specId);
// specLink = newSpec.GetTalentLink();
// newSpec.CropTalents(bot->GetLevel());
// newSpec.ApplyTalents(bot, out);
// if (paths.size() > 1)
// *out << "Found " << paths.size() << " possible specs to choose from. ";
// *out << "Apply spec " << "|h|cffffffff" << getPremadePath(specId)->name << " " <<
// newSpec.FormatSpec(bot);
// }
// }
// sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specNo", specId + 1);
// if (!specLink.empty() && specId == -1)
// sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specLink", 1, specLink);
// else
// sRandomPlayerbotMgr.SetValue(bot->GetGUID().GetCounter(), "specLink", 0);
// return (specNo == 0) ? false : true;
// }
// //Returns a pre-made talentspec that best suits the bots current talents.
// TalentSpec* ChangeTalentsAction::GetBestPremadeSpec(uint32 specId)
// {
// TalentPath* path = getPremadePath(specId);
// for (auto& spec : path->talentSpec)
// {
// if (spec.points >= bot->CalculateTalentsPoints())
// return &spec;
// }
// if (path->talentSpec.size())
// return &path->talentSpec.back();
// // return &sPlayerbotAIConfig.classSpecs[bot->getClassMask()].baseSpec;
// return nullptr;
// }
bool AutoSetTalentsAction::Execute(Event /*event*/)
{
std::ostringstream out;
if (!PlayerbotAIConfig::instance().autoPickTalents || !RandomPlayerbotMgr::instance().IsRandomBot(bot))
return false;
if (bot->GetFreeTalentPoints() <= 0)
return false;
PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitTalentsTree(true, true, true);
factory.InitPetTalents();
botAI->TellMaster(out);
return true;
}