diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index dd9afc8c..2cf88bd1 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -227,6 +227,14 @@ public: // Stand state void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged + // Display toggles + void toggleHelm(); + void toggleCloak(); + + // Follow/Assist + void followTarget(); + void assistTarget(); + // ---- Phase 1: Name queries ---- void queryPlayerName(uint64_t guid); void queryCreatureInfo(uint32_t entry, uint64_t guid); @@ -626,6 +634,13 @@ private: // ---- Logout state ---- bool loggingOut_ = false; + // ---- Display state ---- + bool helmVisible_ = true; + bool cloakVisible_ = true; + + // ---- Follow state ---- + uint64_t followTargetGuid_ = 0; + // ---- Online item tracking ---- struct OnlineItemInfo { uint32_t entry = 0; diff --git a/include/game/opcodes.hpp b/include/game/opcodes.hpp index 26a623c0..4b001794 100644 --- a/include/game/opcodes.hpp +++ b/include/game/opcodes.hpp @@ -81,6 +81,10 @@ enum class Opcode : uint16_t { // ---- Stand State ---- CMSG_STAND_STATE_CHANGE = 0x101, + // ---- Display Toggles ---- + CMSG_SHOWING_HELM = 0x2B9, + CMSG_SHOWING_CLOAK = 0x2BA, + // ---- Random Roll ---- MSG_RANDOM_ROLL = 0x1FB, diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 347e0d3e..a9660740 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -786,6 +786,22 @@ public: static network::Packet build(uint8_t state); }; +// ============================================================ +// Display Toggles +// ============================================================ + +/** CMSG_SHOWING_HELM packet builder */ +class ShowingHelmPacket { +public: + static network::Packet build(bool show); +}; + +/** CMSG_SHOWING_CLOAK packet builder */ +class ShowingCloakPacket { +public: + static network::Packet build(bool show); +}; + // ============================================================ // Random Roll // ============================================================ diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 501a7bb2..15088025 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1776,6 +1776,123 @@ void GameHandler::setStandState(uint8_t standState) { LOG_INFO("Changed stand state to: ", (int)standState); } +void GameHandler::toggleHelm() { + if (state != WorldState::IN_WORLD || !socket) { + LOG_WARNING("Cannot toggle helm: not in world or not connected"); + return; + } + + helmVisible_ = !helmVisible_; + auto packet = ShowingHelmPacket::build(helmVisible_); + socket->send(packet); + addSystemChatMessage(helmVisible_ ? "Helm is now visible." : "Helm is now hidden."); + LOG_INFO("Helm visibility toggled: ", helmVisible_); +} + +void GameHandler::toggleCloak() { + if (state != WorldState::IN_WORLD || !socket) { + LOG_WARNING("Cannot toggle cloak: not in world or not connected"); + return; + } + + cloakVisible_ = !cloakVisible_; + auto packet = ShowingCloakPacket::build(cloakVisible_); + socket->send(packet); + addSystemChatMessage(cloakVisible_ ? "Cloak is now visible." : "Cloak is now hidden."); + LOG_INFO("Cloak visibility toggled: ", cloakVisible_); +} + +void GameHandler::followTarget() { + if (state != WorldState::IN_WORLD) { + LOG_WARNING("Cannot follow: not in world"); + return; + } + + if (targetGuid == 0) { + addSystemChatMessage("You must target someone to follow."); + return; + } + + auto target = getTarget(); + if (!target) { + addSystemChatMessage("Invalid target."); + return; + } + + // Set follow target + followTargetGuid_ = targetGuid; + + // Get target name + std::string targetName = "Target"; + if (target->getType() == ObjectType::PLAYER) { + auto player = std::static_pointer_cast(target); + if (!player->getName().empty()) { + targetName = player->getName(); + } + } else if (target->getType() == ObjectType::UNIT) { + auto unit = std::static_pointer_cast(target); + targetName = unit->getName(); + } + + addSystemChatMessage("Now following " + targetName + "."); + LOG_INFO("Following target: ", targetName, " (GUID: 0x", std::hex, targetGuid, std::dec, ")"); +} + +void GameHandler::assistTarget() { + if (state != WorldState::IN_WORLD) { + LOG_WARNING("Cannot assist: not in world"); + return; + } + + if (targetGuid == 0) { + addSystemChatMessage("You must target someone to assist."); + return; + } + + auto target = getTarget(); + if (!target) { + addSystemChatMessage("Invalid target."); + return; + } + + // Get target name + std::string targetName = "Target"; + if (target->getType() == ObjectType::PLAYER) { + auto player = std::static_pointer_cast(target); + if (!player->getName().empty()) { + targetName = player->getName(); + } + } else if (target->getType() == ObjectType::UNIT) { + auto unit = std::static_pointer_cast(target); + targetName = unit->getName(); + } + + // Try to read target GUID from update fields (UNIT_FIELD_TARGET) + // Field offset 6 is typically UNIT_FIELD_TARGET in 3.3.5a + uint64_t assistTargetGuid = 0; + const auto& fields = target->getFields(); + auto it = fields.find(6); + if (it != fields.end()) { + // Low 32 bits + assistTargetGuid = it->second; + // Try to get high 32 bits from next field + auto it2 = fields.find(7); + if (it2 != fields.end()) { + assistTargetGuid |= (static_cast(it2->second) << 32); + } + } + + if (assistTargetGuid == 0) { + addSystemChatMessage(targetName + " has no target."); + LOG_INFO("Assist: ", targetName, " has no target"); + return; + } + + // Set our target to their target + setTarget(assistTargetGuid); + LOG_INFO("Assisting ", targetName, ", now targeting GUID: 0x", std::hex, assistTargetGuid, std::dec); +} + void GameHandler::releaseSpirit() { if (!playerDead_) return; if (socket && state == WorldState::IN_WORLD) { diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 6f3982f3..50c677fa 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -1316,6 +1316,24 @@ network::Packet StandStateChangePacket::build(uint8_t state) { return packet; } +// ============================================================ +// Display Toggles +// ============================================================ + +network::Packet ShowingHelmPacket::build(bool show) { + network::Packet packet(static_cast(Opcode::CMSG_SHOWING_HELM)); + packet.writeUInt8(show ? 1 : 0); + LOG_DEBUG("Built CMSG_SHOWING_HELM: show=", show); + return packet; +} + +network::Packet ShowingCloakPacket::build(bool show) { + network::Packet packet(static_cast(Opcode::CMSG_SHOWING_CLOAK)); + packet.writeUInt8(show ? 1 : 0); + LOG_DEBUG("Built CMSG_SHOWING_CLOAK: show=", show); + return packet; +} + // ============================================================ // Random Roll // ============================================================ diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index e67eafe2..9ff46442 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1150,6 +1150,34 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) { return; } + // /helm command + if (cmdLower == "helm" || cmdLower == "helmet" || cmdLower == "showhelm") { + gameHandler.toggleHelm(); + chatInputBuffer[0] = '\0'; + return; + } + + // /cloak command + if (cmdLower == "cloak" || cmdLower == "showcloak") { + gameHandler.toggleCloak(); + chatInputBuffer[0] = '\0'; + return; + } + + // /follow command + if (cmdLower == "follow" || cmdLower == "f") { + gameHandler.followTarget(); + chatInputBuffer[0] = '\0'; + return; + } + + // /assist command + if (cmdLower == "assist") { + gameHandler.assistTarget(); + chatInputBuffer[0] = '\0'; + return; + } + // Chat channel slash commands bool isChannelCommand = false; if (cmdLower == "s" || cmdLower == "say") {