Add trainer dialog system with spell list UI and buy support

This commit is contained in:
Kelsi 2026-02-08 14:33:39 -08:00
parent 046d4615ea
commit 9a01261401
7 changed files with 340 additions and 1 deletions

View file

@ -536,6 +536,14 @@ public:
void useItemById(uint32_t itemId);
bool isVendorWindowOpen() const { return vendorWindowOpen; }
const ListInventoryData& getVendorItems() const { return currentVendorItems; }
// Trainer
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
void trainSpell(uint32_t spellId);
void closeTrainer();
const std::string& getSpellName(uint32_t spellId) const;
const std::string& getSpellRank(uint32_t spellId) const;
const ItemQueryResponseData* getItemInfo(uint32_t itemId) const {
auto it = itemInfoCache_.find(itemId);
return (it != itemInfoCache_.end()) ? &it->second : nullptr;
@ -948,6 +956,15 @@ private:
bool vendorWindowOpen = false;
ListInventoryData currentVendorItems;
// Trainer
bool trainerWindowOpen_ = false;
TrainerListData currentTrainerList_;
struct SpellNameEntry { std::string name; std::string rank; };
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
bool spellNameCacheLoaded_ = false;
void handleTrainerList(network::Packet& packet);
void loadSpellNameCache();
// Callbacks
WorldConnectSuccessCallback onSuccess;
WorldConnectFailureCallback onFailure;

View file

@ -126,7 +126,7 @@ enum class Opcode : uint16_t {
CMSG_GAMEOBJECT_QUERY = 0x05E,
SMSG_GAMEOBJECT_QUERY_RESPONSE = 0x05F,
CMSG_SET_ACTIVE_MOVER = 0x26A,
CMSG_BINDER_ACTIVATE = 0x1B2,
CMSG_BINDER_ACTIVATE = 0x1B5,
// ---- XP ----
SMSG_LOG_XPGAIN = 0x1D0,
@ -231,6 +231,10 @@ enum class Opcode : uint16_t {
CMSG_BUY_ITEM = 0x1A2,
SMSG_BUY_FAILED = 0x1A5,
// ---- Trainer ----
SMSG_TRAINER_LIST = 0x01B1,
CMSG_TRAINER_BUY_SPELL = 0x01B2,
// ---- Phase 5: Item/Equip ----
CMSG_ITEM_QUERY_SINGLE = 0x056,
SMSG_ITEM_QUERY_SINGLE_RESPONSE = 0x058,

View file

@ -1716,6 +1716,42 @@ public:
static bool parse(network::Packet& packet, ListInventoryData& data);
};
// ============================================================
// Trainer
// ============================================================
struct TrainerSpell {
uint32_t spellId = 0;
uint8_t state = 0; // 0=known(green), 1=available, 2=unavailable(red)
uint32_t spellCost = 0; // copper
uint32_t profDialog = 0;
uint32_t profButton = 0;
uint8_t reqLevel = 0;
uint32_t reqSkill = 0;
uint32_t reqSkillValue = 0;
uint32_t chainNode1 = 0;
uint32_t chainNode2 = 0;
uint32_t chainNode3 = 0;
};
struct TrainerListData {
uint64_t trainerGuid = 0;
uint32_t trainerType = 0; // 0=class, 1=mounts, 2=tradeskills, 3=pets
std::vector<TrainerSpell> spells;
std::string greeting;
bool isValid() const { return true; }
};
class TrainerListParser {
public:
static bool parse(network::Packet& packet, TrainerListData& data);
};
class TrainerBuySpellPacket {
public:
static network::Packet build(uint64_t trainerGuid, uint32_t spellId);
};
// ============================================================
// Taxi / Flight Paths
// ============================================================