mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 15:20:15 +00:00
Implement duel request/accept/decline UI and packet handling
- Parse SMSG_DUEL_REQUESTED: store challenger guid/name, set pendingDuelRequest_ flag, show chat notification - Parse SMSG_DUEL_COMPLETE: clear pending flag, notify on cancel - Parse SMSG_DUEL_WINNER: show "X defeated Y in a duel!" chat message - Handle SMSG_DUEL_OUTOFBOUNDS with warning message - Add acceptDuel() method sending CMSG_DUEL_ACCEPTED (new builder) - Wire forfeitDuel() to clear pendingDuelRequest_ on decline - Add renderDuelRequestPopup() ImGui window (Accept/Decline buttons) positioned near group invite popup; shown when challenge is pending - Add DuelAcceptPacket builder to world_packets.hpp/cpp
This commit is contained in:
parent
e4f53ce0c3
commit
2d124e7e54
7 changed files with 129 additions and 3 deletions
1
.claude/scheduled_tasks.lock
Normal file
1
.claude/scheduled_tasks.lock
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"sessionId":"55a28c7e-8043-44c2-9829-702f303c84ba","pid":3880168,"acquiredAt":1773085726967}
|
||||
|
|
@ -707,6 +707,12 @@ public:
|
|||
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
||||
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
||||
|
||||
// ---- Duel ----
|
||||
bool hasPendingDuelRequest() const { return pendingDuelRequest_; }
|
||||
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
||||
void acceptDuel();
|
||||
// forfeitDuel() already declared at line ~399
|
||||
|
||||
// ---- Instance lockouts ----
|
||||
struct InstanceLockout {
|
||||
uint32_t mapId = 0;
|
||||
|
|
@ -1249,6 +1255,9 @@ private:
|
|||
|
||||
// ---- Instance lockout handler ----
|
||||
void handleRaidInstanceInfo(network::Packet& packet);
|
||||
void handleDuelRequested(network::Packet& packet);
|
||||
void handleDuelComplete(network::Packet& packet);
|
||||
void handleDuelWinner(network::Packet& packet);
|
||||
|
||||
// ---- LFG / Dungeon Finder handlers ----
|
||||
void handleLfgJoinResult(network::Packet& packet);
|
||||
|
|
@ -1601,6 +1610,12 @@ private:
|
|||
bool pendingGroupInvite = false;
|
||||
std::string pendingInviterName;
|
||||
|
||||
// Duel state
|
||||
bool pendingDuelRequest_ = false;
|
||||
uint64_t duelChallengerGuid_= 0;
|
||||
uint64_t duelFlagGuid_ = 0;
|
||||
std::string duelChallengerName_;
|
||||
|
||||
// ---- Guild state ----
|
||||
std::string guildName_;
|
||||
std::vector<std::string> guildRankNames_;
|
||||
|
|
|
|||
|
|
@ -1265,6 +1265,12 @@ public:
|
|||
// Duel
|
||||
// ============================================================
|
||||
|
||||
/** CMSG_DUEL_ACCEPTED packet builder (no payload) */
|
||||
class DuelAcceptPacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_DUEL_CANCELLED packet builder */
|
||||
class DuelCancelPacket {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ private:
|
|||
void renderCombatText(game::GameHandler& gameHandler);
|
||||
void renderPartyFrames(game::GameHandler& gameHandler);
|
||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderBuffBar(game::GameHandler& gameHandler);
|
||||
void renderLootWindow(game::GameHandler& gameHandler);
|
||||
void renderGossipWindow(game::GameHandler& gameHandler);
|
||||
|
|
|
|||
|
|
@ -1891,8 +1891,22 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
handleRaidInstanceInfo(packet);
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_REQUESTED:
|
||||
// Duel request UI flow not implemented yet.
|
||||
packet.setReadPos(packet.getSize());
|
||||
handleDuelRequested(packet);
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_COMPLETE:
|
||||
handleDuelComplete(packet);
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_WINNER:
|
||||
handleDuelWinner(packet);
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_OUTOFBOUNDS:
|
||||
addSystemChatMessage("You are out of the duel area!");
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_INBOUNDS:
|
||||
// Re-entered the duel area; no special action needed.
|
||||
break;
|
||||
case Opcode::SMSG_DUEL_COUNTDOWN:
|
||||
// Countdown timer — no action needed; server also sends UNIT_FIELD_FLAGS update.
|
||||
break;
|
||||
case Opcode::SMSG_PARTYKILLLOG:
|
||||
// Classic-era packet: killer GUID + victim GUID.
|
||||
|
|
@ -7023,18 +7037,76 @@ void GameHandler::respondToReadyCheck(bool ready) {
|
|||
LOG_INFO("Responded to ready check: ", ready ? "ready" : "not ready");
|
||||
}
|
||||
|
||||
void GameHandler::acceptDuel() {
|
||||
if (!pendingDuelRequest_ || state != WorldState::IN_WORLD || !socket) return;
|
||||
pendingDuelRequest_ = false;
|
||||
auto pkt = DuelAcceptPacket::build();
|
||||
socket->send(pkt);
|
||||
addSystemChatMessage("You accept the duel.");
|
||||
LOG_INFO("Accepted duel from guid=0x", std::hex, duelChallengerGuid_, std::dec);
|
||||
}
|
||||
|
||||
void GameHandler::forfeitDuel() {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot forfeit duel: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
pendingDuelRequest_ = false; // cancel request if still pending
|
||||
auto packet = DuelCancelPacket::build();
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("You have forfeited the duel.");
|
||||
LOG_INFO("Forfeited duel");
|
||||
}
|
||||
|
||||
void GameHandler::handleDuelRequested(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 16) {
|
||||
packet.setReadPos(packet.getSize());
|
||||
return;
|
||||
}
|
||||
duelChallengerGuid_ = packet.readUInt64();
|
||||
duelFlagGuid_ = packet.readUInt64();
|
||||
|
||||
// Resolve challenger name from entity list
|
||||
duelChallengerName_.clear();
|
||||
auto entity = entityManager.getEntity(duelChallengerGuid_);
|
||||
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
|
||||
duelChallengerName_ = unit->getName();
|
||||
}
|
||||
if (duelChallengerName_.empty()) {
|
||||
char tmp[32];
|
||||
std::snprintf(tmp, sizeof(tmp), "0x%llX",
|
||||
static_cast<unsigned long long>(duelChallengerGuid_));
|
||||
duelChallengerName_ = tmp;
|
||||
}
|
||||
pendingDuelRequest_ = true;
|
||||
|
||||
addSystemChatMessage(duelChallengerName_ + " challenges you to a duel!");
|
||||
LOG_INFO("SMSG_DUEL_REQUESTED: challenger=0x", std::hex, duelChallengerGuid_,
|
||||
" flag=0x", duelFlagGuid_, std::dec, " name=", duelChallengerName_);
|
||||
}
|
||||
|
||||
void GameHandler::handleDuelComplete(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 1) return;
|
||||
uint8_t started = packet.readUInt8();
|
||||
// started=1: duel began, started=0: duel was cancelled before starting
|
||||
pendingDuelRequest_ = false;
|
||||
if (!started) {
|
||||
addSystemChatMessage("The duel was cancelled.");
|
||||
}
|
||||
LOG_INFO("SMSG_DUEL_COMPLETE: started=", static_cast<int>(started));
|
||||
}
|
||||
|
||||
void GameHandler::handleDuelWinner(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 3) return;
|
||||
/*uint8_t type =*/ packet.readUInt8(); // 0=normal, 1=flee
|
||||
std::string winner = packet.readString();
|
||||
std::string loser = packet.readString();
|
||||
|
||||
std::string msg = winner + " has defeated " + loser + " in a duel!";
|
||||
addSystemChatMessage(msg);
|
||||
LOG_INFO("SMSG_DUEL_WINNER: winner=", winner, " loser=", loser);
|
||||
}
|
||||
|
||||
void GameHandler::toggleAfk(const std::string& message) {
|
||||
afkStatus_ = !afkStatus_;
|
||||
afkMessage_ = message;
|
||||
|
|
|
|||
|
|
@ -2083,6 +2083,12 @@ network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
|||
// Duel
|
||||
// ============================================================
|
||||
|
||||
network::Packet DuelAcceptPacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_ACCEPTED));
|
||||
LOG_DEBUG("Built CMSG_DUEL_ACCEPTED");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet DuelCancelPacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
||||
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
||||
|
|
|
|||
|
|
@ -396,6 +396,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderCombatText(gameHandler);
|
||||
renderPartyFrames(gameHandler);
|
||||
renderGroupInvitePopup(gameHandler);
|
||||
renderDuelRequestPopup(gameHandler);
|
||||
renderGuildInvitePopup(gameHandler);
|
||||
renderGuildRoster(gameHandler);
|
||||
renderBuffBar(gameHandler);
|
||||
|
|
@ -4376,6 +4377,30 @@ void GameScreen::renderGroupInvitePopup(game::GameHandler& gameHandler) {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
void GameScreen::renderDuelRequestPopup(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.hasPendingDuelRequest()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 250), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
||||
|
||||
if (ImGui::Begin("Duel Request", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
|
||||
ImGui::Text("%s challenges you to a duel!", gameHandler.getDuelChallengerName().c_str());
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
||||
gameHandler.acceptDuel();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
||||
gameHandler.forfeitDuel();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void GameScreen::renderGuildInvitePopup(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.hasPendingGuildInvite()) return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue