mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Implement complete talent system with dual spec support
Network Protocol: - Add SMSG_TALENTS_INFO (0x4C0) packet parsing for talent data - Add CMSG_LEARN_TALENT (0x251) to request learning talents - Add MSG_TALENT_WIPE_CONFIRM (0x2AB) opcode for spec switching - Parse talent spec, unspent points, and learned talent ranks DBC Parsing: - Load Talent.dbc: talent grid positions, ranks, prerequisites, spell IDs - Load TalentTab.dbc: talent tree definitions with correct field indices - Fix localized string field handling (17 fields per string) - Load Spell.dbc and SpellIcon.dbc for talent icons and tooltips - Class mask filtering using bitwise operations (1 << (class - 1)) UI Implementation: - Complete talent tree UI with tabbed interface for specs - Display talent icons from spell data with proper tinting/borders - Enhanced tooltips: spell name, rank, current/next descriptions, prereqs - Visual states: green (maxed), yellow (partial), white (available), gray (locked) - Tier unlock system (5 points per tier requirement) - Rank overlay on icons with shadow text - Click to learn talents with validation Dual Spec Support: - Store unspent points and learned talents per spec (0 and 1) - Track active spec and display its talents - Spec switching UI with buttons for Spec 1/Spec 2 - Handle both SMSG_TALENTS_INFO packets from server at login - Display unspent points for both specs in header - Independent talent trees for each specialization
This commit is contained in:
parent
bf03044a63
commit
e7556605d7
8 changed files with 860 additions and 29 deletions
|
|
@ -78,6 +78,26 @@ using WorldConnectFailureCallback = std::function<void(const std::string& reason
|
|||
*/
|
||||
class GameHandler {
|
||||
public:
|
||||
// Talent data structures (must be public for use in templates)
|
||||
struct TalentEntry {
|
||||
uint32_t talentId = 0;
|
||||
uint32_t tabId = 0; // Which talent tree
|
||||
uint8_t row = 0; // Tier (0-10)
|
||||
uint8_t column = 0; // Column (0-3)
|
||||
uint32_t rankSpells[5] = {}; // Spell IDs for ranks 1-5
|
||||
uint32_t prereqTalent[3] = {}; // Required talents
|
||||
uint8_t prereqRank[3] = {}; // Required ranks
|
||||
uint8_t maxRank = 0; // Number of ranks (1-5)
|
||||
};
|
||||
|
||||
struct TalentTabEntry {
|
||||
uint32_t tabId = 0;
|
||||
std::string name;
|
||||
uint32_t classMask = 0; // Which classes can use this tab
|
||||
uint8_t orderIndex = 0; // Display order (0-2)
|
||||
std::string backgroundFile; // Texture path
|
||||
};
|
||||
|
||||
GameHandler();
|
||||
~GameHandler();
|
||||
|
||||
|
|
@ -327,6 +347,35 @@ public:
|
|||
float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; }
|
||||
float getCastTimeRemaining() const { return castTimeRemaining; }
|
||||
|
||||
// Talents
|
||||
uint8_t getActiveTalentSpec() const { return activeTalentSpec_; }
|
||||
uint8_t getUnspentTalentPoints() const { return unspentTalentPoints_[activeTalentSpec_]; }
|
||||
uint8_t getUnspentTalentPoints(uint8_t spec) const { return spec < 2 ? unspentTalentPoints_[spec] : 0; }
|
||||
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents() const { return learnedTalents_[activeTalentSpec_]; }
|
||||
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents(uint8_t spec) const {
|
||||
static std::unordered_map<uint32_t, uint8_t> empty;
|
||||
return spec < 2 ? learnedTalents_[spec] : empty;
|
||||
}
|
||||
uint8_t getTalentRank(uint32_t talentId) const {
|
||||
auto it = learnedTalents_[activeTalentSpec_].find(talentId);
|
||||
return (it != learnedTalents_[activeTalentSpec_].end()) ? it->second : 0;
|
||||
}
|
||||
void learnTalent(uint32_t talentId, uint32_t requestedRank);
|
||||
void switchTalentSpec(uint8_t newSpec);
|
||||
|
||||
// Talent DBC access
|
||||
const TalentEntry* getTalentEntry(uint32_t talentId) const {
|
||||
auto it = talentCache_.find(talentId);
|
||||
return (it != talentCache_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
const TalentTabEntry* getTalentTabEntry(uint32_t tabId) const {
|
||||
auto it = talentTabCache_.find(tabId);
|
||||
return (it != talentTabCache_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
const std::unordered_map<uint32_t, TalentEntry>& getAllTalents() const { return talentCache_; }
|
||||
const std::unordered_map<uint32_t, TalentTabEntry>& getAllTalentTabs() const { return talentTabCache_; }
|
||||
void loadTalentDbc();
|
||||
|
||||
// Action bar
|
||||
static constexpr int ACTION_BAR_SLOTS = 12;
|
||||
std::array<ActionBarSlot, ACTION_BAR_SLOTS>& getActionBar() { return actionBar; }
|
||||
|
|
@ -436,6 +485,10 @@ public:
|
|||
|
||||
// Player GUID
|
||||
uint64_t getPlayerGuid() const { return playerGuid; }
|
||||
uint8_t getPlayerClass() const {
|
||||
const Character* ch = getActiveCharacter();
|
||||
return ch ? static_cast<uint8_t>(ch->characterClass) : 0;
|
||||
}
|
||||
void setPlayerGuid(uint64_t guid) { playerGuid = guid; }
|
||||
|
||||
// Player death state
|
||||
|
|
@ -703,6 +756,9 @@ private:
|
|||
void handleRemovedSpell(network::Packet& packet);
|
||||
void handleUnlearnSpells(network::Packet& packet);
|
||||
|
||||
// ---- Talent handlers ----
|
||||
void handleTalentsInfo(network::Packet& packet);
|
||||
|
||||
// ---- Phase 4 handlers ----
|
||||
void handleGroupInvite(network::Packet& packet);
|
||||
void handleGroupDecline(network::Packet& packet);
|
||||
|
|
@ -918,6 +974,14 @@ private:
|
|||
bool casting = false;
|
||||
uint32_t currentCastSpellId = 0;
|
||||
float castTimeRemaining = 0.0f;
|
||||
|
||||
// Talents (dual-spec support)
|
||||
uint8_t activeTalentSpec_ = 0; // Currently active spec (0 or 1)
|
||||
uint8_t unspentTalentPoints_[2] = {0, 0}; // Unspent points per spec
|
||||
std::unordered_map<uint32_t, uint8_t> learnedTalents_[2]; // Learned talents per spec
|
||||
std::unordered_map<uint32_t, TalentEntry> talentCache_; // talentId -> entry
|
||||
std::unordered_map<uint32_t, TalentTabEntry> talentTabCache_; // tabId -> entry
|
||||
bool talentDbcLoaded_ = false;
|
||||
float castTimeTotal = 0.0f;
|
||||
std::array<ActionBarSlot, 12> actionBar{};
|
||||
std::vector<AuraSlot> playerAuras;
|
||||
|
|
|
|||
|
|
@ -168,6 +168,11 @@ enum class Opcode : uint16_t {
|
|||
SMSG_SET_FLAT_SPELL_MODIFIER = 0x266,
|
||||
SMSG_SET_PCT_SPELL_MODIFIER = 0x267,
|
||||
|
||||
// ---- Talents ----
|
||||
SMSG_TALENTS_INFO = 0x4C0,
|
||||
CMSG_LEARN_TALENT = 0x251,
|
||||
MSG_TALENT_WIPE_CONFIRM = 0x2AB,
|
||||
|
||||
// ---- Phase 4: Group/Party ----
|
||||
CMSG_GROUP_INVITE = 0x06E,
|
||||
SMSG_GROUP_INVITE = 0x06F,
|
||||
|
|
|
|||
|
|
@ -1753,6 +1753,43 @@ public:
|
|||
static network::Packet build(uint64_t trainerGuid, uint32_t spellId);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Talents
|
||||
// ============================================================
|
||||
|
||||
/** Talent info for a single talent */
|
||||
struct TalentInfo {
|
||||
uint32_t talentId = 0; // Talent.dbc ID
|
||||
uint8_t currentRank = 0; // 0-5 (0 = not learned)
|
||||
};
|
||||
|
||||
/** SMSG_TALENTS_INFO data */
|
||||
struct TalentsInfoData {
|
||||
uint8_t talentSpec = 0; // Active spec (0 or 1 for dual-spec)
|
||||
uint8_t unspentPoints = 0; // Talent points available
|
||||
std::vector<TalentInfo> talents; // Learned talents
|
||||
|
||||
bool isValid() const { return true; }
|
||||
};
|
||||
|
||||
/** SMSG_TALENTS_INFO parser */
|
||||
class TalentsInfoParser {
|
||||
public:
|
||||
static bool parse(network::Packet& packet, TalentsInfoData& data);
|
||||
};
|
||||
|
||||
/** CMSG_LEARN_TALENT packet builder */
|
||||
class LearnTalentPacket {
|
||||
public:
|
||||
static network::Packet build(uint32_t talentId, uint32_t requestedRank);
|
||||
};
|
||||
|
||||
/** MSG_TALENT_WIPE_CONFIRM packet builder */
|
||||
class TalentWipeConfirmPacket {
|
||||
public:
|
||||
static network::Packet build(bool accept);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Taxi / Flight Paths
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ private:
|
|||
|
||||
// ---- New UI renders ----
|
||||
void renderActionBar(game::GameHandler& gameHandler);
|
||||
void renderBagBar(game::GameHandler& gameHandler);
|
||||
void renderXpBar(game::GameHandler& gameHandler);
|
||||
void renderCastBar(game::GameHandler& gameHandler);
|
||||
void renderCombatText(game::GameHandler& gameHandler);
|
||||
|
|
@ -195,6 +196,10 @@ private:
|
|||
int actionBarDragSlot_ = -1;
|
||||
GLuint actionBarDragIcon_ = 0;
|
||||
|
||||
// Bag bar textures
|
||||
GLuint backpackIconTexture_ = 0;
|
||||
GLuint emptyBagSlotTexture_ = 0;
|
||||
|
||||
static std::string getSettingsPath();
|
||||
|
||||
// Gender placeholder replacement
|
||||
|
|
|
|||
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
#include "game/game_handler.hpp"
|
||||
#include <imgui.h>
|
||||
#include <GL/glew.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline { class AssetManager; }
|
||||
|
||||
namespace ui {
|
||||
|
||||
class TalentScreen {
|
||||
|
|
@ -14,8 +19,24 @@ public:
|
|||
void setOpen(bool o) { open = o; }
|
||||
|
||||
private:
|
||||
void renderTalentTrees(game::GameHandler& gameHandler);
|
||||
void renderTalentTree(game::GameHandler& gameHandler, uint32_t tabId);
|
||||
void renderTalent(game::GameHandler& gameHandler, const game::GameHandler::TalentEntry& talent);
|
||||
|
||||
void loadSpellDBC(pipeline::AssetManager* assetManager);
|
||||
void loadSpellIconDBC(pipeline::AssetManager* assetManager);
|
||||
GLuint getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager);
|
||||
|
||||
bool open = false;
|
||||
bool nKeyWasDown = false;
|
||||
|
||||
// DBC caches
|
||||
bool spellDbcLoaded = false;
|
||||
bool iconDbcLoaded = false;
|
||||
std::unordered_map<uint32_t, uint32_t> spellIconIds; // spellId -> iconId
|
||||
std::unordered_map<uint32_t, std::string> spellIconPaths; // iconId -> path
|
||||
std::unordered_map<uint32_t, GLuint> spellIconCache; // iconId -> texture
|
||||
std::unordered_map<uint32_t, std::string> spellTooltips; // spellId -> description
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue