diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 38f95050..cf8d775c 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1749,7 +1749,7 @@ public: class TrainerBuySpellPacket { public: - static network::Packet build(uint64_t trainerGuid, uint32_t spellId); + static network::Packet build(uint64_t trainerGuid, uint32_t trainerId, uint32_t spellId); }; // ============================================================ diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index d1ad873c..032e09d9 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4582,7 +4582,10 @@ void GameHandler::handleTrainerList(network::Packet& packet) { void GameHandler::trainSpell(uint32_t spellId) { if (state != WorldState::IN_WORLD || !socket) return; - auto packet = TrainerBuySpellPacket::build(currentTrainerList_.trainerGuid, spellId); + auto packet = TrainerBuySpellPacket::build( + currentTrainerList_.trainerGuid, + currentTrainerList_.trainerType, + spellId); socket->send(packet); } diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 2fd74d08..438f054a 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2682,9 +2682,10 @@ bool TrainerListParser::parse(network::Packet& packet, TrainerListData& data) { return true; } -network::Packet TrainerBuySpellPacket::build(uint64_t trainerGuid, uint32_t spellId) { +network::Packet TrainerBuySpellPacket::build(uint64_t trainerGuid, uint32_t trainerId, uint32_t spellId) { network::Packet packet(static_cast(Opcode::CMSG_TRAINER_BUY_SPELL)); packet.writeUInt64(trainerGuid); + packet.writeUInt32(trainerId); packet.writeUInt32(spellId); return packet; } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 72af7f84..41f6cf57 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -3566,8 +3566,10 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { // Known spells for checking const auto& knownSpells = gameHandler.getKnownSpells(); auto isKnown = [&](uint32_t id) { + if (id == 0) return true; return std::find(knownSpells.begin(), knownSpells.end(), id) != knownSpells.end(); }; + uint32_t playerLevel = gameHandler.getPlayerLevel(); // Renders spell rows into the current table auto renderSpellRows = [&](const std::vector& spells) { @@ -3575,12 +3577,19 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { ImGui::TableNextRow(); ImGui::PushID(static_cast(spell->spellId)); + // Check prerequisites client-side + bool prereqsMet = isKnown(spell->chainNode1) + && isKnown(spell->chainNode2) + && isKnown(spell->chainNode3); + bool levelMet = (spell->reqLevel == 0 || playerLevel >= spell->reqLevel); + bool alreadyKnown = (spell->state == 0) || isKnown(spell->spellId); + ImVec4 color; const char* statusLabel; - if (spell->state == 0 || isKnown(spell->spellId)) { + if (alreadyKnown) { color = ImVec4(0.3f, 0.9f, 0.3f, 1.0f); statusLabel = "Known"; - } else if (spell->state == 1) { + } else if (spell->state == 1 && prereqsMet && levelMet) { color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); statusLabel = "Available"; } else { @@ -3608,13 +3617,24 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { if (!rank.empty()) ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", rank.c_str()); } ImGui::Text("Status: %s", statusLabel); - if (spell->reqLevel > 0) ImGui::Text("Required Level: %u", spell->reqLevel); - if (spell->reqSkill > 0) ImGui::Text("Required Skill: %u (value %u)", spell->reqSkill, spell->reqSkillValue); - if (spell->chainNode1 > 0) { - const std::string& prereq = gameHandler.getSpellName(spell->chainNode1); - if (!prereq.empty()) ImGui::Text("Requires: %s", prereq.c_str()); - else ImGui::Text("Requires: Spell #%u", spell->chainNode1); + if (spell->reqLevel > 0) { + ImVec4 lvlColor = levelMet ? ImVec4(0.7f, 0.7f, 0.7f, 1.0f) : ImVec4(1.0f, 0.3f, 0.3f, 1.0f); + ImGui::TextColored(lvlColor, "Required Level: %u", spell->reqLevel); } + if (spell->reqSkill > 0) ImGui::Text("Required Skill: %u (value %u)", spell->reqSkill, spell->reqSkillValue); + auto showPrereq = [&](uint32_t node) { + if (node == 0) return; + bool met = isKnown(node); + const std::string& pname = gameHandler.getSpellName(node); + ImVec4 pcolor = met ? ImVec4(0.3f, 0.9f, 0.3f, 1.0f) : ImVec4(1.0f, 0.3f, 0.3f, 1.0f); + if (!pname.empty()) + ImGui::TextColored(pcolor, "Requires: %s%s", pname.c_str(), met ? " (known)" : ""); + else + ImGui::TextColored(pcolor, "Requires: Spell #%u%s", node, met ? " (known)" : ""); + }; + showPrereq(spell->chainNode1); + showPrereq(spell->chainNode2); + showPrereq(spell->chainNode3); ImGui::EndTooltip(); } @@ -3635,9 +3655,11 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) { ImGui::TextColored(color, "Free"); } - // Train button + // Train button - only enabled if available, affordable, prereqs met ImGui::TableSetColumnIndex(3); - bool canTrain = (spell->state == 1) && (money >= spell->spellCost); + bool canTrain = !alreadyKnown && spell->state == 1 + && prereqsMet && levelMet + && (money >= spell->spellCost); if (!canTrain) ImGui::BeginDisabled(); if (ImGui::SmallButton("Train")) { gameHandler.trainSpell(spell->spellId);