Add Companions tab to spellbook for vanity pets (SkillLine 778)
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run

- Companion pet spells (SkillLine 778) get their own "Companions" tab
- Mounts (SkillLine 762) and Companions are now split out from secondary
  skills instead of being lumped into profession tabs
- Refactored tab grouping into addGroupedTabs helper to reduce duplication
- Tab order: Specs > General > Professions > Mounts > Companions
- Double-click to summon companions/mounts works via existing castSpell
This commit is contained in:
Kelsi 2026-02-25 15:04:17 -08:00
parent 0d87a86516
commit 7982815a67

View file

@ -159,15 +159,17 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
// SkillLine.dbc category IDs // SkillLine.dbc category IDs
static constexpr uint32_t CAT_CLASS = 7; // Class abilities (spec trees) static constexpr uint32_t CAT_CLASS = 7; // Class abilities (spec trees)
static constexpr uint32_t CAT_PROFESSION = 11; // Primary professions static constexpr uint32_t CAT_PROFESSION = 11; // Primary professions
static constexpr uint32_t CAT_SECONDARY = 9; // Secondary professions (Cooking, First Aid, Fishing, Riding) static constexpr uint32_t CAT_SECONDARY = 9; // Secondary skills (Cooking, First Aid, Fishing, Riding, Companions)
// Riding skill line ID — mount spells are linked here via SkillLineAbility // Special skill line IDs within category 9 that get their own tabs
static constexpr uint32_t SKILLLINE_RIDING = 762; static constexpr uint32_t SKILLLINE_RIDING = 762; // Mounts
static constexpr uint32_t SKILLLINE_COMPANIONS = 778; // Vanity/companion pets
// Buckets // Buckets
std::map<uint32_t, std::vector<const SpellInfo*>> specSpells; // skillLineId -> spells (class specs) std::map<uint32_t, std::vector<const SpellInfo*>> specSpells; // class spec trees
std::map<uint32_t, std::vector<const SpellInfo*>> profSpells; // skillLineId -> spells (professions) std::map<uint32_t, std::vector<const SpellInfo*>> profSpells; // professions + secondary
std::vector<const SpellInfo*> mountSpells; std::vector<const SpellInfo*> mountSpells;
std::vector<const SpellInfo*> companionSpells;
std::vector<const SpellInfo*> generalSpells; std::vector<const SpellInfo*> generalSpells;
for (uint32_t spellId : knownSpells) { for (uint32_t spellId : knownSpells) {
@ -180,12 +182,18 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
if (slIt != spellToSkillLine.end()) { if (slIt != spellToSkillLine.end()) {
uint32_t skillLineId = slIt->second; uint32_t skillLineId = slIt->second;
// Mounts: spells linked to Riding skill line (762) // Mounts: Riding skill line (762)
if (skillLineId == SKILLLINE_RIDING) { if (skillLineId == SKILLLINE_RIDING) {
mountSpells.push_back(info); mountSpells.push_back(info);
continue; continue;
} }
// Companions: vanity pets skill line (778)
if (skillLineId == SKILLLINE_COMPANIONS) {
companionSpells.push_back(info);
continue;
}
auto catIt = skillLineCategories.find(skillLineId); auto catIt = skillLineCategories.find(skillLineId);
if (catIt != skillLineCategories.end()) { if (catIt != skillLineCategories.end()) {
uint32_t cat = catIt->second; uint32_t cat = catIt->second;
@ -196,13 +204,13 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
continue; continue;
} }
// Primary professions (Alchemy, Blacksmithing, etc.) // Primary professions
if (cat == CAT_PROFESSION) { if (cat == CAT_PROFESSION) {
profSpells[skillLineId].push_back(info); profSpells[skillLineId].push_back(info);
continue; continue;
} }
// Secondary professions (Cooking, First Aid, Fishing — but NOT Riding, already handled) // Secondary skills (Cooking, First Aid, Fishing)
if (cat == CAT_SECONDARY) { if (cat == CAT_SECONDARY) {
profSpells[skillLineId].push_back(info); profSpells[skillLineId].push_back(info);
continue; continue;
@ -215,12 +223,13 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
auto byName = [](const SpellInfo* a, const SpellInfo* b) { return a->name < b->name; }; auto byName = [](const SpellInfo* a, const SpellInfo* b) { return a->name < b->name; };
// 1. Class spec tabs (sorted alphabetically by spec name) // Helper: add sorted skill-line-grouped tabs
{ auto addGroupedTabs = [&](std::map<uint32_t, std::vector<const SpellInfo*>>& groups,
const char* fallbackName) {
std::vector<std::pair<std::string, std::vector<const SpellInfo*>>> named; std::vector<std::pair<std::string, std::vector<const SpellInfo*>>> named;
for (auto& [skillLineId, spells] : specSpells) { for (auto& [skillLineId, spells] : groups) {
auto nameIt = skillLineNames.find(skillLineId); auto nameIt = skillLineNames.find(skillLineId);
std::string tabName = (nameIt != skillLineNames.end()) ? nameIt->second : "Spec"; std::string tabName = (nameIt != skillLineNames.end()) ? nameIt->second : fallbackName;
std::sort(spells.begin(), spells.end(), byName); std::sort(spells.begin(), spells.end(), byName);
named.push_back({std::move(tabName), std::move(spells)}); named.push_back({std::move(tabName), std::move(spells)});
} }
@ -229,29 +238,19 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
for (auto& [name, spells] : named) { for (auto& [name, spells] : named) {
spellTabs.push_back({std::move(name), std::move(spells)}); spellTabs.push_back({std::move(name), std::move(spells)});
} }
} };
// 2. General tab (everything not in a specific category) // 1. Class spec tabs
addGroupedTabs(specSpells, "Spec");
// 2. General tab
if (!generalSpells.empty()) { if (!generalSpells.empty()) {
std::sort(generalSpells.begin(), generalSpells.end(), byName); std::sort(generalSpells.begin(), generalSpells.end(), byName);
spellTabs.push_back({"General", std::move(generalSpells)}); spellTabs.push_back({"General", std::move(generalSpells)});
} }
// 3. Professions tabs (primary + secondary, each skill line gets its own tab) // 3. Professions tabs
{ addGroupedTabs(profSpells, "Profession");
std::vector<std::pair<std::string, std::vector<const SpellInfo*>>> named;
for (auto& [skillLineId, spells] : profSpells) {
auto nameIt = skillLineNames.find(skillLineId);
std::string tabName = (nameIt != skillLineNames.end()) ? nameIt->second : "Profession";
std::sort(spells.begin(), spells.end(), byName);
named.push_back({std::move(tabName), std::move(spells)});
}
std::sort(named.begin(), named.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
for (auto& [name, spells] : named) {
spellTabs.push_back({std::move(name), std::move(spells)});
}
}
// 4. Mounts tab // 4. Mounts tab
if (!mountSpells.empty()) { if (!mountSpells.empty()) {
@ -259,6 +258,12 @@ void SpellbookScreen::categorizeSpells(const std::unordered_set<uint32_t>& known
spellTabs.push_back({"Mounts", std::move(mountSpells)}); spellTabs.push_back({"Mounts", std::move(mountSpells)});
} }
// 5. Companions tab
if (!companionSpells.empty()) {
std::sort(companionSpells.begin(), companionSpells.end(), byName);
spellTabs.push_back({"Companions", std::move(companionSpells)});
}
lastKnownSpellCount = knownSpells.size(); lastKnownSpellCount = knownSpells.size();
} }