mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +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; }
|
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
||||||
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
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 ----
|
// ---- Instance lockouts ----
|
||||||
struct InstanceLockout {
|
struct InstanceLockout {
|
||||||
uint32_t mapId = 0;
|
uint32_t mapId = 0;
|
||||||
|
|
@ -1249,6 +1255,9 @@ private:
|
||||||
|
|
||||||
// ---- Instance lockout handler ----
|
// ---- Instance lockout handler ----
|
||||||
void handleRaidInstanceInfo(network::Packet& packet);
|
void handleRaidInstanceInfo(network::Packet& packet);
|
||||||
|
void handleDuelRequested(network::Packet& packet);
|
||||||
|
void handleDuelComplete(network::Packet& packet);
|
||||||
|
void handleDuelWinner(network::Packet& packet);
|
||||||
|
|
||||||
// ---- LFG / Dungeon Finder handlers ----
|
// ---- LFG / Dungeon Finder handlers ----
|
||||||
void handleLfgJoinResult(network::Packet& packet);
|
void handleLfgJoinResult(network::Packet& packet);
|
||||||
|
|
@ -1601,6 +1610,12 @@ private:
|
||||||
bool pendingGroupInvite = false;
|
bool pendingGroupInvite = false;
|
||||||
std::string pendingInviterName;
|
std::string pendingInviterName;
|
||||||
|
|
||||||
|
// Duel state
|
||||||
|
bool pendingDuelRequest_ = false;
|
||||||
|
uint64_t duelChallengerGuid_= 0;
|
||||||
|
uint64_t duelFlagGuid_ = 0;
|
||||||
|
std::string duelChallengerName_;
|
||||||
|
|
||||||
// ---- Guild state ----
|
// ---- Guild state ----
|
||||||
std::string guildName_;
|
std::string guildName_;
|
||||||
std::vector<std::string> guildRankNames_;
|
std::vector<std::string> guildRankNames_;
|
||||||
|
|
|
||||||
|
|
@ -1265,6 +1265,12 @@ public:
|
||||||
// Duel
|
// Duel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
/** CMSG_DUEL_ACCEPTED packet builder (no payload) */
|
||||||
|
class DuelAcceptPacket {
|
||||||
|
public:
|
||||||
|
static network::Packet build();
|
||||||
|
};
|
||||||
|
|
||||||
/** CMSG_DUEL_CANCELLED packet builder */
|
/** CMSG_DUEL_CANCELLED packet builder */
|
||||||
class DuelCancelPacket {
|
class DuelCancelPacket {
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ private:
|
||||||
void renderCombatText(game::GameHandler& gameHandler);
|
void renderCombatText(game::GameHandler& gameHandler);
|
||||||
void renderPartyFrames(game::GameHandler& gameHandler);
|
void renderPartyFrames(game::GameHandler& gameHandler);
|
||||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||||
|
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||||
void renderBuffBar(game::GameHandler& gameHandler);
|
void renderBuffBar(game::GameHandler& gameHandler);
|
||||||
void renderLootWindow(game::GameHandler& gameHandler);
|
void renderLootWindow(game::GameHandler& gameHandler);
|
||||||
void renderGossipWindow(game::GameHandler& gameHandler);
|
void renderGossipWindow(game::GameHandler& gameHandler);
|
||||||
|
|
|
||||||
|
|
@ -1891,8 +1891,22 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
handleRaidInstanceInfo(packet);
|
handleRaidInstanceInfo(packet);
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_DUEL_REQUESTED:
|
case Opcode::SMSG_DUEL_REQUESTED:
|
||||||
// Duel request UI flow not implemented yet.
|
handleDuelRequested(packet);
|
||||||
packet.setReadPos(packet.getSize());
|
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;
|
break;
|
||||||
case Opcode::SMSG_PARTYKILLLOG:
|
case Opcode::SMSG_PARTYKILLLOG:
|
||||||
// Classic-era packet: killer GUID + victim GUID.
|
// 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");
|
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() {
|
void GameHandler::forfeitDuel() {
|
||||||
if (state != WorldState::IN_WORLD || !socket) {
|
if (state != WorldState::IN_WORLD || !socket) {
|
||||||
LOG_WARNING("Cannot forfeit duel: not in world or not connected");
|
LOG_WARNING("Cannot forfeit duel: not in world or not connected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
pendingDuelRequest_ = false; // cancel request if still pending
|
||||||
auto packet = DuelCancelPacket::build();
|
auto packet = DuelCancelPacket::build();
|
||||||
socket->send(packet);
|
socket->send(packet);
|
||||||
addSystemChatMessage("You have forfeited the duel.");
|
addSystemChatMessage("You have forfeited the duel.");
|
||||||
LOG_INFO("Forfeited 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) {
|
void GameHandler::toggleAfk(const std::string& message) {
|
||||||
afkStatus_ = !afkStatus_;
|
afkStatus_ = !afkStatus_;
|
||||||
afkMessage_ = message;
|
afkMessage_ = message;
|
||||||
|
|
|
||||||
|
|
@ -2083,6 +2083,12 @@ network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
||||||
// Duel
|
// 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 DuelCancelPacket::build() {
|
||||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
||||||
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderCombatText(gameHandler);
|
renderCombatText(gameHandler);
|
||||||
renderPartyFrames(gameHandler);
|
renderPartyFrames(gameHandler);
|
||||||
renderGroupInvitePopup(gameHandler);
|
renderGroupInvitePopup(gameHandler);
|
||||||
|
renderDuelRequestPopup(gameHandler);
|
||||||
renderGuildInvitePopup(gameHandler);
|
renderGuildInvitePopup(gameHandler);
|
||||||
renderGuildRoster(gameHandler);
|
renderGuildRoster(gameHandler);
|
||||||
renderBuffBar(gameHandler);
|
renderBuffBar(gameHandler);
|
||||||
|
|
@ -4376,6 +4377,30 @@ void GameScreen::renderGroupInvitePopup(game::GameHandler& gameHandler) {
|
||||||
ImGui::End();
|
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) {
|
void GameScreen::renderGuildInvitePopup(game::GameHandler& gameHandler) {
|
||||||
if (!gameHandler.hasPendingGuildInvite()) return;
|
if (!gameHandler.hasPendingGuildInvite()) return;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue