mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Add trainer dialog system with spell list UI and buy support
This commit is contained in:
parent
046d4615ea
commit
9a01261401
7 changed files with 340 additions and 1 deletions
|
|
@ -536,6 +536,14 @@ public:
|
||||||
void useItemById(uint32_t itemId);
|
void useItemById(uint32_t itemId);
|
||||||
bool isVendorWindowOpen() const { return vendorWindowOpen; }
|
bool isVendorWindowOpen() const { return vendorWindowOpen; }
|
||||||
const ListInventoryData& getVendorItems() const { return currentVendorItems; }
|
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 {
|
const ItemQueryResponseData* getItemInfo(uint32_t itemId) const {
|
||||||
auto it = itemInfoCache_.find(itemId);
|
auto it = itemInfoCache_.find(itemId);
|
||||||
return (it != itemInfoCache_.end()) ? &it->second : nullptr;
|
return (it != itemInfoCache_.end()) ? &it->second : nullptr;
|
||||||
|
|
@ -948,6 +956,15 @@ private:
|
||||||
bool vendorWindowOpen = false;
|
bool vendorWindowOpen = false;
|
||||||
ListInventoryData currentVendorItems;
|
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
|
// Callbacks
|
||||||
WorldConnectSuccessCallback onSuccess;
|
WorldConnectSuccessCallback onSuccess;
|
||||||
WorldConnectFailureCallback onFailure;
|
WorldConnectFailureCallback onFailure;
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ enum class Opcode : uint16_t {
|
||||||
CMSG_GAMEOBJECT_QUERY = 0x05E,
|
CMSG_GAMEOBJECT_QUERY = 0x05E,
|
||||||
SMSG_GAMEOBJECT_QUERY_RESPONSE = 0x05F,
|
SMSG_GAMEOBJECT_QUERY_RESPONSE = 0x05F,
|
||||||
CMSG_SET_ACTIVE_MOVER = 0x26A,
|
CMSG_SET_ACTIVE_MOVER = 0x26A,
|
||||||
CMSG_BINDER_ACTIVATE = 0x1B2,
|
CMSG_BINDER_ACTIVATE = 0x1B5,
|
||||||
|
|
||||||
// ---- XP ----
|
// ---- XP ----
|
||||||
SMSG_LOG_XPGAIN = 0x1D0,
|
SMSG_LOG_XPGAIN = 0x1D0,
|
||||||
|
|
@ -231,6 +231,10 @@ enum class Opcode : uint16_t {
|
||||||
CMSG_BUY_ITEM = 0x1A2,
|
CMSG_BUY_ITEM = 0x1A2,
|
||||||
SMSG_BUY_FAILED = 0x1A5,
|
SMSG_BUY_FAILED = 0x1A5,
|
||||||
|
|
||||||
|
// ---- Trainer ----
|
||||||
|
SMSG_TRAINER_LIST = 0x01B1,
|
||||||
|
CMSG_TRAINER_BUY_SPELL = 0x01B2,
|
||||||
|
|
||||||
// ---- Phase 5: Item/Equip ----
|
// ---- Phase 5: Item/Equip ----
|
||||||
CMSG_ITEM_QUERY_SINGLE = 0x056,
|
CMSG_ITEM_QUERY_SINGLE = 0x056,
|
||||||
SMSG_ITEM_QUERY_SINGLE_RESPONSE = 0x058,
|
SMSG_ITEM_QUERY_SINGLE_RESPONSE = 0x058,
|
||||||
|
|
|
||||||
|
|
@ -1716,6 +1716,42 @@ public:
|
||||||
static bool parse(network::Packet& packet, ListInventoryData& data);
|
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
|
// Taxi / Flight Paths
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ private:
|
||||||
void renderQuestRequestItemsWindow(game::GameHandler& gameHandler);
|
void renderQuestRequestItemsWindow(game::GameHandler& gameHandler);
|
||||||
void renderQuestOfferRewardWindow(game::GameHandler& gameHandler);
|
void renderQuestOfferRewardWindow(game::GameHandler& gameHandler);
|
||||||
void renderVendorWindow(game::GameHandler& gameHandler);
|
void renderVendorWindow(game::GameHandler& gameHandler);
|
||||||
|
void renderTrainerWindow(game::GameHandler& gameHandler);
|
||||||
void renderTaxiWindow(game::GameHandler& gameHandler);
|
void renderTaxiWindow(game::GameHandler& gameHandler);
|
||||||
void renderDeathScreen(game::GameHandler& gameHandler);
|
void renderDeathScreen(game::GameHandler& gameHandler);
|
||||||
void renderResurrectDialog(game::GameHandler& gameHandler);
|
void renderResurrectDialog(game::GameHandler& gameHandler);
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,18 @@ void GameHandler::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (trainerWindowOpen_ && currentTrainerList_.trainerGuid != 0) {
|
||||||
|
auto npc = entityManager.getEntity(currentTrainerList_.trainerGuid);
|
||||||
|
if (npc) {
|
||||||
|
float dx = movementInfo.x - npc->getX();
|
||||||
|
float dy = movementInfo.y - npc->getY();
|
||||||
|
float dist = std::sqrt(dx * dx + dy * dy);
|
||||||
|
if (dist > 15.0f) {
|
||||||
|
closeTrainer();
|
||||||
|
LOG_INFO("Trainer closed: walked too far from NPC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update entity movement interpolation (keeps targeting in sync with visuals)
|
// Update entity movement interpolation (keeps targeting in sync with visuals)
|
||||||
for (auto& [guid, entity] : entityManager.getEntities()) {
|
for (auto& [guid, entity] : entityManager.getEntities()) {
|
||||||
|
|
@ -639,6 +651,9 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_LIST_INVENTORY:
|
case Opcode::SMSG_LIST_INVENTORY:
|
||||||
handleListInventory(packet);
|
handleListInventory(packet);
|
||||||
break;
|
break;
|
||||||
|
case Opcode::SMSG_TRAINER_LIST:
|
||||||
|
handleTrainerList(packet);
|
||||||
|
break;
|
||||||
|
|
||||||
// Silently ignore common packets we don't handle yet
|
// Silently ignore common packets we don't handle yet
|
||||||
case Opcode::SMSG_FEATURE_SYSTEM_STATUS:
|
case Opcode::SMSG_FEATURE_SYSTEM_STATUS:
|
||||||
|
|
@ -4538,6 +4553,74 @@ void GameHandler::handleListInventory(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Trainer
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameHandler::handleTrainerList(network::Packet& packet) {
|
||||||
|
if (!TrainerListParser::parse(packet, currentTrainerList_)) return;
|
||||||
|
trainerWindowOpen_ = true;
|
||||||
|
gossipWindowOpen = false;
|
||||||
|
|
||||||
|
// Ensure spell name cache is populated
|
||||||
|
loadSpellNameCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameHandler::trainSpell(uint32_t spellId) {
|
||||||
|
if (state != WorldState::IN_WORLD || !socket) return;
|
||||||
|
auto packet = TrainerBuySpellPacket::build(currentTrainerList_.trainerGuid, spellId);
|
||||||
|
socket->send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameHandler::closeTrainer() {
|
||||||
|
trainerWindowOpen_ = false;
|
||||||
|
currentTrainerList_ = TrainerListData{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameHandler::loadSpellNameCache() {
|
||||||
|
if (spellNameCacheLoaded_) return;
|
||||||
|
spellNameCacheLoaded_ = true;
|
||||||
|
|
||||||
|
auto* am = core::Application::getInstance().getAssetManager();
|
||||||
|
if (!am || !am->isInitialized()) return;
|
||||||
|
|
||||||
|
auto dbc = am->loadDBC("Spell.dbc");
|
||||||
|
if (!dbc || !dbc->isLoaded()) {
|
||||||
|
LOG_WARNING("Trainer: Could not load Spell.dbc for spell names");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbc->getFieldCount() < 154) {
|
||||||
|
LOG_WARNING("Trainer: Spell.dbc has too few fields");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields: 0=SpellID, 136=SpellName_enUS, 153=RankText_enUS
|
||||||
|
uint32_t count = dbc->getRecordCount();
|
||||||
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
|
uint32_t id = dbc->getUInt32(i, 0);
|
||||||
|
if (id == 0) continue;
|
||||||
|
std::string name = dbc->getString(i, 136);
|
||||||
|
std::string rank = dbc->getString(i, 153);
|
||||||
|
if (!name.empty()) {
|
||||||
|
spellNameCache_[id] = {std::move(name), std::move(rank)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO("Trainer: Loaded ", spellNameCache_.size(), " spell names from Spell.dbc");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string EMPTY_STRING;
|
||||||
|
|
||||||
|
const std::string& GameHandler::getSpellName(uint32_t spellId) const {
|
||||||
|
auto it = spellNameCache_.find(spellId);
|
||||||
|
return (it != spellNameCache_.end()) ? it->second.name : EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& GameHandler::getSpellRank(uint32_t spellId) const {
|
||||||
|
auto it = spellNameCache_.find(spellId);
|
||||||
|
return (it != spellNameCache_.end()) ? it->second.rank : EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Single-player local combat
|
// Single-player local combat
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -2643,6 +2643,52 @@ bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Trainer
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool TrainerListParser::parse(network::Packet& packet, TrainerListData& data) {
|
||||||
|
data = TrainerListData{};
|
||||||
|
data.trainerGuid = packet.readUInt64();
|
||||||
|
data.trainerType = packet.readUInt32();
|
||||||
|
uint32_t spellCount = packet.readUInt32();
|
||||||
|
|
||||||
|
if (spellCount > 1000) {
|
||||||
|
LOG_ERROR("TrainerListParser: unreasonable spell count ", spellCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.spells.reserve(spellCount);
|
||||||
|
for (uint32_t i = 0; i < spellCount; ++i) {
|
||||||
|
TrainerSpell spell;
|
||||||
|
spell.spellId = packet.readUInt32();
|
||||||
|
spell.state = packet.readUInt8();
|
||||||
|
spell.spellCost = packet.readUInt32();
|
||||||
|
spell.profDialog = packet.readUInt32();
|
||||||
|
spell.profButton = packet.readUInt32();
|
||||||
|
spell.reqLevel = packet.readUInt8();
|
||||||
|
spell.reqSkill = packet.readUInt32();
|
||||||
|
spell.reqSkillValue = packet.readUInt32();
|
||||||
|
spell.chainNode1 = packet.readUInt32();
|
||||||
|
spell.chainNode2 = packet.readUInt32();
|
||||||
|
spell.chainNode3 = packet.readUInt32();
|
||||||
|
data.spells.push_back(spell);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.greeting = packet.readString();
|
||||||
|
|
||||||
|
LOG_INFO("Trainer list: ", spellCount, " spells, type=", data.trainerType,
|
||||||
|
", greeting=\"", data.greeting, "\"");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
network::Packet TrainerBuySpellPacket::build(uint64_t trainerGuid, uint32_t spellId) {
|
||||||
|
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_TRAINER_BUY_SPELL));
|
||||||
|
packet.writeUInt64(trainerGuid);
|
||||||
|
packet.writeUInt32(spellId);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Death/Respawn
|
// Death/Respawn
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderQuestRequestItemsWindow(gameHandler);
|
renderQuestRequestItemsWindow(gameHandler);
|
||||||
renderQuestOfferRewardWindow(gameHandler);
|
renderQuestOfferRewardWindow(gameHandler);
|
||||||
renderVendorWindow(gameHandler);
|
renderVendorWindow(gameHandler);
|
||||||
|
renderTrainerWindow(gameHandler);
|
||||||
renderTaxiWindow(gameHandler);
|
renderTaxiWindow(gameHandler);
|
||||||
renderQuestMarkers(gameHandler);
|
renderQuestMarkers(gameHandler);
|
||||||
renderMinimapMarkers(gameHandler);
|
renderMinimapMarkers(gameHandler);
|
||||||
|
|
@ -3519,6 +3520,157 @@ void GameScreen::renderVendorWindow(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Trainer
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) {
|
||||||
|
if (!gameHandler.isTrainerWindowOpen()) return;
|
||||||
|
|
||||||
|
auto* window = core::Application::getInstance().getWindow();
|
||||||
|
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 225, 100), ImGuiCond_Appearing);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_Appearing);
|
||||||
|
|
||||||
|
bool open = true;
|
||||||
|
if (ImGui::Begin("Trainer", &open)) {
|
||||||
|
const auto& trainer = gameHandler.getTrainerSpells();
|
||||||
|
|
||||||
|
// NPC name
|
||||||
|
auto npcEntity = gameHandler.getEntityManager().getEntity(trainer.trainerGuid);
|
||||||
|
if (npcEntity && npcEntity->getType() == game::ObjectType::UNIT) {
|
||||||
|
auto unit = std::static_pointer_cast<game::Unit>(npcEntity);
|
||||||
|
if (!unit->getName().empty()) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", unit->getName().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greeting
|
||||||
|
if (!trainer.greeting.empty()) {
|
||||||
|
ImGui::TextWrapped("%s", trainer.greeting.c_str());
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Player money
|
||||||
|
uint64_t money = gameHandler.getMoneyCopper();
|
||||||
|
uint32_t mg = static_cast<uint32_t>(money / 10000);
|
||||||
|
uint32_t ms = static_cast<uint32_t>((money / 100) % 100);
|
||||||
|
uint32_t mc = static_cast<uint32_t>(money % 100);
|
||||||
|
ImGui::Text("Your money: %ug %us %uc", mg, ms, mc);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (trainer.spells.empty()) {
|
||||||
|
ImGui::TextDisabled("This trainer has nothing to teach you.");
|
||||||
|
} else {
|
||||||
|
// Known spells for checking
|
||||||
|
const auto& knownSpells = gameHandler.getKnownSpells();
|
||||||
|
auto isKnown = [&](uint32_t id) {
|
||||||
|
return std::find(knownSpells.begin(), knownSpells.end(), id) != knownSpells.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("TrainerTable", 4,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) {
|
||||||
|
ImGui::TableSetupColumn("Spell", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 40.0f);
|
||||||
|
ImGui::TableSetupColumn("Cost", ImGuiTableColumnFlags_WidthFixed, 120.0f);
|
||||||
|
ImGui::TableSetupColumn("##action", ImGuiTableColumnFlags_WidthFixed, 55.0f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& spell : trainer.spells) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::PushID(static_cast<int>(spell.spellId));
|
||||||
|
|
||||||
|
// State color: 0=known(green), 1=available(white), 2=unavailable(gray)
|
||||||
|
ImVec4 color;
|
||||||
|
const char* statusLabel;
|
||||||
|
if (spell.state == 0 || isKnown(spell.spellId)) {
|
||||||
|
color = ImVec4(0.3f, 0.9f, 0.3f, 1.0f);
|
||||||
|
statusLabel = "Known";
|
||||||
|
} else if (spell.state == 1) {
|
||||||
|
color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
statusLabel = "Available";
|
||||||
|
} else {
|
||||||
|
color = ImVec4(0.6f, 0.3f, 0.3f, 1.0f);
|
||||||
|
statusLabel = "Unavailable";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spell name
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
const std::string& name = gameHandler.getSpellName(spell.spellId);
|
||||||
|
const std::string& rank = gameHandler.getSpellRank(spell.spellId);
|
||||||
|
if (!name.empty()) {
|
||||||
|
if (!rank.empty()) {
|
||||||
|
ImGui::TextColored(color, "%s (%s)", name.c_str(), rank.c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(color, "%s", name.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(color, "Spell #%u", spell.spellId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
if (!name.empty()) {
|
||||||
|
ImGui::Text("%s", name.c_str());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Level
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::TextColored(color, "%u", spell.reqLevel);
|
||||||
|
|
||||||
|
// Cost
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
if (spell.spellCost > 0) {
|
||||||
|
uint32_t g = spell.spellCost / 10000;
|
||||||
|
uint32_t s = (spell.spellCost / 100) % 100;
|
||||||
|
uint32_t c = spell.spellCost % 100;
|
||||||
|
bool canAfford = money >= spell.spellCost;
|
||||||
|
ImVec4 costColor = canAfford ? color : ImVec4(1.0f, 0.3f, 0.3f, 1.0f);
|
||||||
|
ImGui::TextColored(costColor, "%ug %us %uc", g, s, c);
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(color, "Free");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Train button
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
bool canTrain = (spell.state == 1) && (money >= spell.spellCost);
|
||||||
|
if (!canTrain) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
}
|
||||||
|
if (ImGui::SmallButton("Train")) {
|
||||||
|
gameHandler.trainSpell(spell.spellId);
|
||||||
|
}
|
||||||
|
if (!canTrain) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!open) {
|
||||||
|
gameHandler.closeTrainer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Teleporter Panel
|
// Teleporter Panel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue