diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 9c1015a7..47f1e2b3 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1594,6 +1594,11 @@ public: return it != taxiNpcHasRoutes_.end() && it->second; } + // Vehicle (WotLK) + bool isInVehicle() const { return vehicleId_ != 0; } + uint32_t getVehicleId() const { return vehicleId_; } + void sendRequestVehicleExit(); + // Vendor void openVendor(uint64_t npcGuid); void closeVendor(); @@ -2536,6 +2541,9 @@ private: return it != factionHostileMap_.end() ? it->second : true; // default hostile if unknown } + // Vehicle (WotLK): non-zero when player is seated in a vehicle + uint32_t vehicleId_ = 0; + // Taxi / Flight Paths std::unordered_map taxiNpcHasRoutes_; // guid -> has new/available routes std::unordered_map taxiNodes_; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f70a3c39..d7bf2db0 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -5211,10 +5211,19 @@ void GameHandler::handlePacket(network::Packet& packet) { // GM ticket status (new/updated); no ticket UI yet packet.setReadPos(packet.getSize()); break; - case Opcode::SMSG_PLAYER_VEHICLE_DATA: - // Vehicle data update for player in vehicle; no vehicle UI yet - packet.setReadPos(packet.getSize()); + case Opcode::SMSG_PLAYER_VEHICLE_DATA: { + // PackedGuid (player guid) + uint32 vehicleId + // vehicleId == 0 means the player left the vehicle + if (packet.getSize() - packet.getReadPos() >= 1) { + (void)UpdateObjectParser::readPackedGuid(packet); // player guid (unused) + } + if (packet.getSize() - packet.getReadPos() >= 4) { + vehicleId_ = packet.readUInt32(); + } else { + vehicleId_ = 0; + } break; + } case Opcode::SMSG_SET_EXTRA_AURA_INFO_NEED_UPDATE: packet.setReadPos(packet.getSize()); break; @@ -6868,6 +6877,7 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { taxiStartGrace_ = 0.0f; currentMountDisplayId_ = 0; taxiMountDisplayId_ = 0; + vehicleId_ = 0; if (mountCallback_) { mountCallback_(0); } @@ -7891,6 +7901,14 @@ void GameHandler::sendPing() { socket->send(packet); } +void GameHandler::sendRequestVehicleExit() { + if (state != WorldState::IN_WORLD || vehicleId_ == 0) return; + // CMSG_REQUEST_VEHICLE_EXIT has no payload — opcode only + network::Packet pkt(wireOpcode(Opcode::CMSG_REQUEST_VEHICLE_EXIT)); + socket->send(pkt); + vehicleId_ = 0; // Optimistically clear; server will confirm via SMSG_PLAYER_VEHICLE_DATA(0) +} + void GameHandler::sendMinimapPing(float wowX, float wowY) { if (state != WorldState::IN_WORLD) return; @@ -8202,6 +8220,7 @@ void GameHandler::forceClearTaxiAndMovementState() { taxiMountActive_ = false; taxiMountDisplayId_ = 0; currentMountDisplayId_ = 0; + vehicleId_ = 0; resurrectPending_ = false; resurrectRequestPending_ = false; playerDead_ = false; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 5b880d51..8be6697a 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6968,6 +6968,38 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { ImGui::PopStyleVar(4); } + // Vehicle exit button (WotLK): floating button above action bar when player is in a vehicle + if (gameHandler.isInVehicle()) { + const float btnW = 120.0f; + const float btnH = 32.0f; + const float btnX = (screenW - btnW) / 2.0f; + const float btnY = barY - btnH - 6.0f; + + ImGui::SetNextWindowPos(ImVec2(btnX, btnY), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(btnW, btnH), ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f, 4.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGuiWindowFlags vFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground; + if (ImGui::Begin("##VehicleExit", nullptr, vFlags)) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.1f, 0.1f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.4f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); + if (ImGui::Button("Leave Vehicle", ImVec2(btnW - 8.0f, btnH - 8.0f))) { + gameHandler.sendRequestVehicleExit(); + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + } + ImGui::End(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(3); + } + // Handle action bar drag: render icon at cursor and detect drop outside if (actionBarDragSlot_ >= 0) { ImVec2 mousePos = ImGui::GetMousePos();