Kelsidavis-WoWee/include/game/quest_handler.hpp
Paul b2710258dc refactor(game): split GameHandler into domain handlers
Extract domain-specific logic from the monolithic GameHandler into
dedicated handler classes, each owning its own opcode registration,
state, and packet parsing:

- CombatHandler: combat, XP, kill, PvP, loot roll (~26 methods)
- SpellHandler: spells, auras, pet stable, talent (~3+ methods)
- SocialHandler: friends, guild, groups, BG, RAF, PvP AFK (~14+ methods)
- ChatHandler: chat messages, channels, GM tickets, server messages,
               defense/area-trigger messages (~7+ methods)
- InventoryHandler: items, trade, loot, mail, vendor, equipment sets,
                    read item (~3+ methods)
- QuestHandler: gossip, quests, completed quest response (~5+ methods)
- MovementHandler: movement, follow, transport (~2 methods)
- WardenHandler: Warden anti-cheat module

Each handler registers its own dispatch table entries via
registerOpcodes(DispatchTable&), called from
GameHandler::registerOpcodeHandlers(). GameHandler retains core
orchestration: auth/session handshake, update-object parsing,
opcode routing, and cross-handler coordination.

game_handler.cpp reduced from ~10,188 to ~9,432 lines.

Also add a POST_BUILD CMake step to symlink Data/ next to the
executable so expansion profiles and opcode tables are found at
runtime when running from build/bin/.
2026-03-28 09:42:37 +03:00

199 lines
8 KiB
C++

#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <array>
#include <chrono>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
enum class QuestGiverStatus : uint8_t;
class QuestHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit QuestHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API (delegated from GameHandler) ---
// NPC Gossip
void selectGossipOption(uint32_t optionId);
void selectGossipQuest(uint32_t questId);
void acceptQuest();
void declineQuest();
void closeGossip();
void offerQuestFromItem(uint64_t itemGuid, uint32_t questId);
bool isGossipWindowOpen() const { return gossipWindowOpen_; }
const GossipMessageData& getCurrentGossip() const { return currentGossip_; }
// Quest details
bool isQuestDetailsOpen() {
if (questDetailsOpen_) return true;
if (questDetailsOpenTime_ != std::chrono::steady_clock::time_point{}) {
if (std::chrono::steady_clock::now() >= questDetailsOpenTime_) {
questDetailsOpen_ = true;
questDetailsOpenTime_ = std::chrono::steady_clock::time_point{};
return true;
}
}
return false;
}
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails_; }
// Gossip / quest map POI markers (aliased from handler_types.hpp)
using GossipPoi = game::GossipPoi;
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
void clearGossipPois() { gossipPois_.clear(); }
// Quest turn-in
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
void completeQuest();
void closeQuestRequestItems();
bool isQuestOfferRewardOpen() const { return questOfferRewardOpen_; }
const QuestOfferRewardData& getQuestOfferReward() const { return currentQuestOfferReward_; }
void chooseQuestReward(uint32_t rewardIndex);
void closeQuestOfferReward();
// Quest log
struct QuestLogEntry {
uint32_t questId = 0;
std::string title;
std::string objectives;
bool complete = false;
std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> killCounts;
std::unordered_map<uint32_t, uint32_t> itemCounts;
std::unordered_map<uint32_t, uint32_t> requiredItemCounts;
struct KillObjective {
int32_t npcOrGoId = 0;
uint32_t required = 0;
};
std::array<KillObjective, 4> killObjectives{};
struct ItemObjective {
uint32_t itemId = 0;
uint32_t required = 0;
};
std::array<ItemObjective, 6> itemObjectives{};
int32_t rewardMoney = 0;
std::array<QuestRewardItem, 4> rewardItems{};
std::array<QuestRewardItem, 6> rewardChoiceItems{};
};
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
int getSelectedQuestLogIndex() const { return selectedQuestLogIndex_; }
void setSelectedQuestLogIndex(int idx) { selectedQuestLogIndex_ = idx; }
void abandonQuest(uint32_t questId);
void shareQuestWithParty(uint32_t questId);
bool requestQuestQuery(uint32_t questId, bool force = false);
bool isQuestTracked(uint32_t questId) const { return trackedQuestIds_.count(questId) > 0; }
void setQuestTracked(uint32_t questId, bool tracked);
const std::unordered_set<uint32_t>& getTrackedQuestIds() const { return trackedQuestIds_; }
bool isQuestQueryPending(uint32_t questId) const {
return pendingQuestQueryIds_.count(questId) > 0;
}
void clearQuestQueryPending(uint32_t questId) { pendingQuestQueryIds_.erase(questId); }
// Quest giver status (! and ? markers)
QuestGiverStatus getQuestGiverStatus(uint64_t guid) const;
const std::unordered_map<uint64_t, QuestGiverStatus>& getNpcQuestStatuses() const { return npcQuestStatus_; }
// Shared quest
bool hasPendingSharedQuest() const { return pendingSharedQuest_; }
uint32_t getSharedQuestId() const { return sharedQuestId_; }
const std::string& getSharedQuestTitle() const { return sharedQuestTitle_; }
const std::string& getSharedQuestSharerName() const { return sharedQuestSharerName_; }
void acceptSharedQuest();
void declineSharedQuest();
// --- Internal helpers called from GameHandler ---
bool hasQuestInLog(uint32_t questId) const;
int findQuestLogSlotIndexFromServer(uint32_t questId) const;
void addQuestToLocalLogIfMissing(uint32_t questId, const std::string& title, const std::string& objectives);
bool resyncQuestLogFromServerSlots(bool forceQueryMetadata);
void applyQuestStateFromFields(const std::map<uint16_t, uint32_t>& fields);
void applyPackedKillCountsFromFields(QuestLogEntry& quest);
void clearPendingQuestAccept(uint32_t questId);
void triggerQuestAcceptResync(uint32_t questId, uint64_t npcGuid, const char* reason);
// Pending quest accept timeout state (used by GameHandler::update)
std::unordered_map<uint32_t, float>& pendingQuestAcceptTimeoutsRef() { return pendingQuestAcceptTimeouts_; }
std::unordered_map<uint32_t, uint64_t>& pendingQuestAcceptNpcGuidsRef() { return pendingQuestAcceptNpcGuids_; }
bool& pendingLoginQuestResyncRef() { return pendingLoginQuestResync_; }
float& pendingLoginQuestResyncTimeoutRef() { return pendingLoginQuestResyncTimeout_; }
// Direct state access for vendor/gossip interaction in GameHandler
bool& gossipWindowOpenRef() { return gossipWindowOpen_; }
GossipMessageData& currentGossipRef() { return currentGossip_; }
std::unordered_map<uint64_t, QuestGiverStatus>& npcQuestStatusRef() { return npcQuestStatus_; }
private:
// --- Packet handlers ---
void handleGossipMessage(network::Packet& packet);
void handleQuestgiverQuestList(network::Packet& packet);
void handleGossipComplete(network::Packet& packet);
void handleQuestPoiQueryResponse(network::Packet& packet);
void handleQuestDetails(network::Packet& packet);
void handleQuestRequestItems(network::Packet& packet);
void handleQuestOfferReward(network::Packet& packet);
void handleQuestConfirmAccept(network::Packet& packet);
GameHandler& owner_;
// --- State ---
// Gossip
bool gossipWindowOpen_ = false;
GossipMessageData currentGossip_;
std::vector<GossipPoi> gossipPois_;
// Quest details
bool questDetailsOpen_ = false;
std::chrono::steady_clock::time_point questDetailsOpenTime_{};
QuestDetailsData currentQuestDetails_;
// Quest turn-in
bool questRequestItemsOpen_ = false;
QuestRequestItemsData currentQuestRequestItems_;
uint32_t pendingTurnInQuestId_ = 0;
uint64_t pendingTurnInNpcGuid_ = 0;
bool pendingTurnInRewardRequest_ = false;
std::unordered_map<uint32_t, float> pendingQuestAcceptTimeouts_;
std::unordered_map<uint32_t, uint64_t> pendingQuestAcceptNpcGuids_;
bool questOfferRewardOpen_ = false;
QuestOfferRewardData currentQuestOfferReward_;
// Quest log
std::vector<QuestLogEntry> questLog_;
int selectedQuestLogIndex_ = 0;
std::unordered_set<uint32_t> pendingQuestQueryIds_;
std::unordered_set<uint32_t> trackedQuestIds_;
bool pendingLoginQuestResync_ = false;
float pendingLoginQuestResyncTimeout_ = 0.0f;
// Quest giver status per NPC
std::unordered_map<uint64_t, QuestGiverStatus> npcQuestStatus_;
// Shared quest state
bool pendingSharedQuest_ = false;
uint32_t sharedQuestId_ = 0;
std::string sharedQuestTitle_;
std::string sharedQuestSharerName_;
uint64_t sharedQuestSharerGuid_ = 0;
};
} // namespace game
} // namespace wowee