mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Implement basic trade request/accept/decline flow
- Parse SMSG_TRADE_STATUS for all 20+ status codes: incoming request, open/cancel/complete/accept notifications, error conditions (too far, wrong faction, stunned, dead, trial account, etc.) - SMSG_TRADE_STATUS_EXTENDED consumed via shared handler (no full item window yet; state tracking sufficient for accept/decline flow) - Add acceptTradeRequest() (CMSG_BEGIN_TRADE), declineTradeRequest(), acceptTrade() (CMSG_ACCEPT_TRADE), cancelTrade() (CMSG_CANCEL_TRADE) - Add BeginTradePacket, CancelTradePacket, AcceptTradePacket builders - Add renderTradeRequestPopup(): shows "X wants to trade" with Accept/Decline buttons when tradeStatus_ == PendingIncoming - TradeStatus enum tracks None/PendingIncoming/Open/Accepted/Complete
This commit is contained in:
parent
b4f6ca2ca7
commit
f369fe9c6e
6 changed files with 178 additions and 0 deletions
|
|
@ -707,6 +707,18 @@ public:
|
|||
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
|
||||
const std::string& getPendingInviterName() const { return pendingInviterName; }
|
||||
|
||||
// ---- Trade ----
|
||||
enum class TradeStatus : uint8_t {
|
||||
None = 0, PendingIncoming, Open, Accepted, Complete
|
||||
};
|
||||
TradeStatus getTradeStatus() const { return tradeStatus_; }
|
||||
bool hasPendingTradeRequest() const { return tradeStatus_ == TradeStatus::PendingIncoming; }
|
||||
const std::string& getTradePeerName() const { return tradePeerName_; }
|
||||
void acceptTradeRequest(); // respond to incoming SMSG_TRADE_STATUS(1) with CMSG_BEGIN_TRADE
|
||||
void declineTradeRequest(); // respond with CMSG_CANCEL_TRADE
|
||||
void acceptTrade(); // lock in offer: CMSG_ACCEPT_TRADE
|
||||
void cancelTrade(); // CMSG_CANCEL_TRADE
|
||||
|
||||
// ---- Duel ----
|
||||
bool hasPendingDuelRequest() const { return pendingDuelRequest_; }
|
||||
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
|
||||
|
|
@ -1268,6 +1280,7 @@ private:
|
|||
|
||||
// ---- Instance lockout handler ----
|
||||
void handleRaidInstanceInfo(network::Packet& packet);
|
||||
void handleTradeStatus(network::Packet& packet);
|
||||
void handleDuelRequested(network::Packet& packet);
|
||||
void handleDuelComplete(network::Packet& packet);
|
||||
void handleDuelWinner(network::Packet& packet);
|
||||
|
|
@ -1625,6 +1638,11 @@ private:
|
|||
bool pendingGroupInvite = false;
|
||||
std::string pendingInviterName;
|
||||
|
||||
// Trade state
|
||||
TradeStatus tradeStatus_ = TradeStatus::None;
|
||||
uint64_t tradePeerGuid_= 0;
|
||||
std::string tradePeerName_;
|
||||
|
||||
// Duel state
|
||||
bool pendingDuelRequest_ = false;
|
||||
uint64_t duelChallengerGuid_= 0;
|
||||
|
|
|
|||
|
|
@ -1326,6 +1326,24 @@ public:
|
|||
static network::Packet build(uint64_t targetGuid);
|
||||
};
|
||||
|
||||
/** CMSG_BEGIN_TRADE packet builder (no payload — accepts incoming trade request) */
|
||||
class BeginTradePacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_CANCEL_TRADE packet builder (no payload) */
|
||||
class CancelTradePacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_ACCEPT_TRADE packet builder (no payload — lock in current offer) */
|
||||
class AcceptTradePacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_ATTACKSWING packet builder */
|
||||
class AttackSwingPacket {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ private:
|
|||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||
void renderDuelRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderLootRollPopup(game::GameHandler& gameHandler);
|
||||
void renderTradeRequestPopup(game::GameHandler& gameHandler);
|
||||
void renderBuffBar(game::GameHandler& gameHandler);
|
||||
void renderLootWindow(game::GameHandler& gameHandler);
|
||||
void renderGossipWindow(game::GameHandler& gameHandler);
|
||||
|
|
|
|||
|
|
@ -1953,6 +1953,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
case Opcode::SMSG_LOOT_REMOVED:
|
||||
handleLootRemoved(packet);
|
||||
break;
|
||||
case Opcode::SMSG_TRADE_STATUS:
|
||||
case Opcode::SMSG_TRADE_STATUS_EXTENDED:
|
||||
handleTradeStatus(packet);
|
||||
break;
|
||||
case Opcode::SMSG_LOOT_ROLL:
|
||||
handleLootRoll(packet);
|
||||
break;
|
||||
|
|
@ -14991,6 +14995,100 @@ void GameHandler::handleAuctionCommandResult(network::Packet& packet) {
|
|||
" error=", result.errorCode);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trade (SMSG_TRADE_STATUS / SMSG_TRADE_STATUS_EXTENDED)
|
||||
// WotLK 3.3.5a status values:
|
||||
// 0=busy, 1=begin_trade(+guid), 2=open_window, 3=cancelled, 4=accepted,
|
||||
// 5=busy2, 6=no_target, 7=back_to_trade, 8=complete, 9=rejected,
|
||||
// 10=too_far, 11=wrong_faction, 12=close_window, 13=ignore,
|
||||
// 14-19=stun/dead/logout, 20=trial, 21=conjured_only
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void GameHandler::handleTradeStatus(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 4) return;
|
||||
uint32_t status = packet.readUInt32();
|
||||
|
||||
switch (status) {
|
||||
case 1: { // BEGIN_TRADE — incoming request; read initiator GUID
|
||||
if (packet.getSize() - packet.getReadPos() >= 8) {
|
||||
tradePeerGuid_ = packet.readUInt64();
|
||||
}
|
||||
// Resolve name from entity list
|
||||
tradePeerName_.clear();
|
||||
auto entity = entityManager.getEntity(tradePeerGuid_);
|
||||
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
|
||||
tradePeerName_ = unit->getName();
|
||||
}
|
||||
if (tradePeerName_.empty()) {
|
||||
char tmp[32];
|
||||
std::snprintf(tmp, sizeof(tmp), "0x%llX",
|
||||
static_cast<unsigned long long>(tradePeerGuid_));
|
||||
tradePeerName_ = tmp;
|
||||
}
|
||||
tradeStatus_ = TradeStatus::PendingIncoming;
|
||||
addSystemChatMessage(tradePeerName_ + " wants to trade with you.");
|
||||
break;
|
||||
}
|
||||
case 2: // OPEN_WINDOW
|
||||
tradeStatus_ = TradeStatus::Open;
|
||||
addSystemChatMessage("Trade window opened.");
|
||||
break;
|
||||
case 3: // CANCELLED
|
||||
case 9: // REJECTED
|
||||
case 12: // CLOSE_WINDOW
|
||||
tradeStatus_ = TradeStatus::None;
|
||||
addSystemChatMessage("Trade cancelled.");
|
||||
break;
|
||||
case 4: // ACCEPTED (partner accepted)
|
||||
tradeStatus_ = TradeStatus::Accepted;
|
||||
addSystemChatMessage("Trade accepted. Awaiting other player...");
|
||||
break;
|
||||
case 8: // COMPLETE
|
||||
tradeStatus_ = TradeStatus::Complete;
|
||||
addSystemChatMessage("Trade complete!");
|
||||
tradeStatus_ = TradeStatus::None; // reset after notification
|
||||
break;
|
||||
case 7: // BACK_TO_TRADE (unaccepted after a change)
|
||||
tradeStatus_ = TradeStatus::Open;
|
||||
addSystemChatMessage("Trade offer changed.");
|
||||
break;
|
||||
case 10: addSystemChatMessage("Trade target is too far away."); break;
|
||||
case 11: addSystemChatMessage("Trade failed: wrong faction."); break;
|
||||
case 13: addSystemChatMessage("Trade failed: player ignores you."); break;
|
||||
case 14: addSystemChatMessage("Trade failed: you are stunned."); break;
|
||||
case 15: addSystemChatMessage("Trade failed: target is stunned."); break;
|
||||
case 16: addSystemChatMessage("Trade failed: you are dead."); break;
|
||||
case 17: addSystemChatMessage("Trade failed: target is dead."); break;
|
||||
case 20: addSystemChatMessage("Trial accounts cannot trade."); break;
|
||||
default: break;
|
||||
}
|
||||
LOG_DEBUG("SMSG_TRADE_STATUS: status=", status);
|
||||
}
|
||||
|
||||
void GameHandler::acceptTradeRequest() {
|
||||
if (tradeStatus_ != TradeStatus::PendingIncoming || !socket) return;
|
||||
tradeStatus_ = TradeStatus::Open;
|
||||
socket->send(BeginTradePacket::build());
|
||||
}
|
||||
|
||||
void GameHandler::declineTradeRequest() {
|
||||
if (!socket) return;
|
||||
tradeStatus_ = TradeStatus::None;
|
||||
socket->send(CancelTradePacket::build());
|
||||
}
|
||||
|
||||
void GameHandler::acceptTrade() {
|
||||
if (tradeStatus_ != TradeStatus::Open || !socket) return;
|
||||
tradeStatus_ = TradeStatus::Accepted;
|
||||
socket->send(AcceptTradePacket::build());
|
||||
}
|
||||
|
||||
void GameHandler::cancelTrade() {
|
||||
if (!socket) return;
|
||||
tradeStatus_ = TradeStatus::None;
|
||||
socket->send(CancelTradePacket::build());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Group loot roll (SMSG_LOOT_ROLL / SMSG_LOOT_ROLL_WON / CMSG_LOOT_ROLL)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -2139,6 +2139,24 @@ network::Packet DuelProposedPacket::build(uint64_t targetGuid) {
|
|||
return packet;
|
||||
}
|
||||
|
||||
network::Packet BeginTradePacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_BEGIN_TRADE));
|
||||
LOG_DEBUG("Built CMSG_BEGIN_TRADE");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet CancelTradePacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_TRADE));
|
||||
LOG_DEBUG("Built CMSG_CANCEL_TRADE");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet AcceptTradePacket::build() {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ACCEPT_TRADE));
|
||||
LOG_DEBUG("Built CMSG_ACCEPT_TRADE");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet InitiateTradePacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_INITIATE_TRADE));
|
||||
packet.writeUInt64(targetGuid);
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderGroupInvitePopup(gameHandler);
|
||||
renderDuelRequestPopup(gameHandler);
|
||||
renderLootRollPopup(gameHandler);
|
||||
renderTradeRequestPopup(gameHandler);
|
||||
renderGuildInvitePopup(gameHandler);
|
||||
renderGuildRoster(gameHandler);
|
||||
renderBuffBar(gameHandler);
|
||||
|
|
@ -4402,6 +4403,30 @@ void GameScreen::renderDuelRequestPopup(game::GameHandler& gameHandler) {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
void GameScreen::renderTradeRequestPopup(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.hasPendingTradeRequest()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 370), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always);
|
||||
|
||||
if (ImGui::Begin("Trade Request", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
|
||||
ImGui::Text("%s wants to trade with you.", gameHandler.getTradePeerName().c_str());
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Accept", ImVec2(130, 30))) {
|
||||
gameHandler.acceptTradeRequest();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Decline", ImVec2(130, 30))) {
|
||||
gameHandler.declineTradeRequest();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void GameScreen::renderLootRollPopup(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.hasPendingLootRoll()) return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue