mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-02-20 08:14:55 -05:00
Classic: synchronized Data/expansions/classic/opcodes.json to /home/k/Desktop/classicopcodes.h with exact symbol/value parity (0 missing, 0 mismatches). WotLK: synchronized Data/expansions/wotlk/opcodes.json to /home/k/Desktop/azerothcoreOpcodes.h and aligned symbol names to AzerothCore naming. Logical opcode layer: expanded include/game/opcode_table.hpp and src/game/opcode_table.cpp to include missing canonical opcode symbols required by synced tables, and removed legacy alias fallback block so canonical names are used directly. Gameplay/handler updates included from ongoing fixes: duel/taxi stale opcode cleanup, level-up/sound handling adjustments, and related parser/packet references updated to match canonical opcode identifiers. Validated by successful full build: cmake --build build -j32.
2506 lines
67 KiB
C++
2506 lines
67 KiB
C++
#pragma once
|
|
|
|
#include "network/packet.hpp"
|
|
#include "game/opcodes.hpp"
|
|
#include "game/character.hpp"
|
|
#include "game/entity.hpp"
|
|
#include "game/spell_defines.hpp"
|
|
#include "game/group_defines.hpp"
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <chrono>
|
|
|
|
namespace wowee {
|
|
namespace game {
|
|
|
|
/**
|
|
* SMSG_AUTH_CHALLENGE data (from server)
|
|
*
|
|
* Sent by world server immediately after TCP connect
|
|
* Contains server seed/salt for authentication hash
|
|
*/
|
|
struct AuthChallengeData {
|
|
uint32_t unknown1; // Always seems to be 0x00000001
|
|
uint32_t serverSeed; // Random seed from server
|
|
// Note: 3.3.5a has additional data after this
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/**
|
|
* CMSG_AUTH_SESSION packet builder
|
|
*
|
|
* Client authentication to world server using session key from auth server
|
|
*/
|
|
class AuthSessionPacket {
|
|
public:
|
|
/**
|
|
* Build CMSG_AUTH_SESSION packet
|
|
*
|
|
* @param build Client build number (12340 for 3.3.5a)
|
|
* @param accountName Account name (uppercase)
|
|
* @param clientSeed Random 4-byte seed generated by client
|
|
* @param sessionKey 40-byte session key from auth server
|
|
* @param serverSeed 4-byte seed from SMSG_AUTH_CHALLENGE
|
|
* @return Packet ready to send
|
|
*/
|
|
static network::Packet build(uint32_t build,
|
|
const std::string& accountName,
|
|
uint32_t clientSeed,
|
|
const std::vector<uint8_t>& sessionKey,
|
|
uint32_t serverSeed,
|
|
uint32_t realmId = 1);
|
|
|
|
private:
|
|
/**
|
|
* Compute authentication hash
|
|
*
|
|
* SHA1(account + [0,0,0,0] + clientSeed + serverSeed + sessionKey)
|
|
*
|
|
* @param accountName Account name
|
|
* @param clientSeed Client seed
|
|
* @param serverSeed Server seed
|
|
* @param sessionKey 40-byte session key
|
|
* @return 20-byte SHA1 hash
|
|
*/
|
|
static std::vector<uint8_t> computeAuthHash(
|
|
const std::string& accountName,
|
|
uint32_t clientSeed,
|
|
uint32_t serverSeed,
|
|
const std::vector<uint8_t>& sessionKey);
|
|
};
|
|
|
|
/**
|
|
* SMSG_AUTH_CHALLENGE response parser
|
|
*/
|
|
class AuthChallengeParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuthChallengeData& data);
|
|
};
|
|
|
|
/**
|
|
* SMSG_AUTH_RESPONSE result codes
|
|
*/
|
|
enum class AuthResult : uint8_t {
|
|
// TrinityCore/AzerothCore auth response codes (3.3.5a)
|
|
OK = 0x0C, // Success, proceed to character screen
|
|
FAILED = 0x0D, // Generic failure
|
|
REJECT = 0x0E, // Reject
|
|
BAD_SERVER_PROOF = 0x0F, // Bad server proof
|
|
UNAVAILABLE = 0x10, // Unavailable
|
|
SYSTEM_ERROR = 0x11, // System error
|
|
BILLING_ERROR = 0x12, // Billing error
|
|
BILLING_EXPIRED = 0x13, // Billing expired
|
|
VERSION_MISMATCH = 0x14, // Version mismatch
|
|
UNKNOWN_ACCOUNT = 0x15, // Unknown account
|
|
INCORRECT_PASSWORD = 0x16, // Incorrect password
|
|
SESSION_EXPIRED = 0x17, // Session expired
|
|
SERVER_SHUTTING_DOWN = 0x18, // Server shutting down
|
|
ALREADY_LOGGING_IN = 0x19, // Already logging in
|
|
LOGIN_SERVER_NOT_FOUND = 0x1A, // Login server not found
|
|
WAIT_QUEUE = 0x1B, // Wait queue
|
|
BANNED = 0x1C, // Banned
|
|
ALREADY_ONLINE = 0x1D, // Already online
|
|
NO_TIME = 0x1E, // No game time
|
|
DB_BUSY = 0x1F, // DB busy
|
|
SUSPENDED = 0x20, // Suspended
|
|
PARENTAL_CONTROL = 0x21, // Parental control
|
|
LOCKED_ENFORCED = 0x22 // Account locked
|
|
};
|
|
|
|
/**
|
|
* SMSG_AUTH_RESPONSE data (from server)
|
|
*/
|
|
struct AuthResponseData {
|
|
AuthResult result;
|
|
|
|
bool isSuccess() const { return result == AuthResult::OK; }
|
|
};
|
|
|
|
/**
|
|
* SMSG_AUTH_RESPONSE parser
|
|
*/
|
|
class AuthResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuthResponseData& data);
|
|
};
|
|
|
|
/**
|
|
* Get human-readable string for auth result
|
|
*/
|
|
const char* getAuthResultString(AuthResult result);
|
|
|
|
// Forward declare Character
|
|
struct Character;
|
|
|
|
/**
|
|
* CMSG_CHAR_ENUM packet builder
|
|
*
|
|
* Request list of characters on account
|
|
*/
|
|
class CharEnumPacket {
|
|
public:
|
|
/**
|
|
* Build CMSG_CHAR_ENUM packet
|
|
*
|
|
* This packet has no body - just the opcode
|
|
*/
|
|
static network::Packet build();
|
|
};
|
|
|
|
/**
|
|
* SMSG_CHAR_ENUM response data (from server)
|
|
*
|
|
* Contains list of all characters on the account
|
|
*/
|
|
struct CharEnumResponse {
|
|
std::vector<Character> characters;
|
|
|
|
bool isEmpty() const { return characters.empty(); }
|
|
size_t count() const { return characters.size(); }
|
|
};
|
|
|
|
/**
|
|
* SMSG_CHAR_ENUM response parser
|
|
*/
|
|
class CharEnumParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, CharEnumResponse& response);
|
|
};
|
|
|
|
// ============================================================
|
|
// Character Creation
|
|
// ============================================================
|
|
|
|
// WoW 3.3.5a ResponseCodes for character creation (from ResponseCodes enum)
|
|
// Windows wingdi.h defines ERROR as 0; undefine it for this enum scope.
|
|
#ifdef _WIN32
|
|
#pragma push_macro("ERROR")
|
|
#undef ERROR
|
|
#endif
|
|
enum class CharCreateResult : uint8_t {
|
|
// Success codes
|
|
SUCCESS = 0x2F, // CHAR_CREATE_SUCCESS
|
|
|
|
// CHAR_CREATE error codes
|
|
IN_PROGRESS = 0x2E, // CHAR_CREATE_IN_PROGRESS
|
|
CHAR_ERROR = 0x30, // CHAR_CREATE_ERROR
|
|
FAILED = 0x31, // CHAR_CREATE_FAILED
|
|
NAME_IN_USE = 0x32, // CHAR_CREATE_NAME_IN_USE
|
|
DISABLED = 0x33, // CHAR_CREATE_DISABLED
|
|
PVP_TEAMS_VIOLATION = 0x34, // CHAR_CREATE_PVP_TEAMS_VIOLATION
|
|
SERVER_LIMIT = 0x35, // CHAR_CREATE_SERVER_LIMIT
|
|
ACCOUNT_LIMIT = 0x36, // CHAR_CREATE_ACCOUNT_LIMIT
|
|
SERVER_QUEUE = 0x37, // CHAR_CREATE_SERVER_QUEUE
|
|
ONLY_EXISTING = 0x38, // CHAR_CREATE_ONLY_EXISTING
|
|
EXPANSION = 0x39, // CHAR_CREATE_EXPANSION
|
|
EXPANSION_CLASS = 0x3A, // CHAR_CREATE_EXPANSION_CLASS
|
|
LEVEL_REQUIREMENT = 0x3B, // CHAR_CREATE_LEVEL_REQUIREMENT
|
|
UNIQUE_CLASS_LIMIT = 0x3C, // CHAR_CREATE_UNIQUE_CLASS_LIMIT
|
|
CHARACTER_IN_GUILD = 0x3D, // CHAR_CREATE_CHARACTER_IN_GUILD
|
|
RESTRICTED_RACECLASS = 0x3E, // CHAR_CREATE_RESTRICTED_RACECLASS
|
|
CHARACTER_CHOOSE_RACE= 0x3F, // CHAR_CREATE_CHARACTER_CHOOSE_RACE
|
|
CHARACTER_ARENA_LEADER=0x40, // CHAR_CREATE_CHARACTER_ARENA_LEADER
|
|
CHARACTER_DELETE_MAIL= 0x41, // CHAR_CREATE_CHARACTER_DELETE_MAIL
|
|
CHARACTER_SWAP_FACTION=0x42, // CHAR_CREATE_CHARACTER_SWAP_FACTION
|
|
CHARACTER_RACE_ONLY = 0x43, // CHAR_CREATE_CHARACTER_RACE_ONLY
|
|
CHARACTER_GOLD_LIMIT = 0x44, // CHAR_CREATE_CHARACTER_GOLD_LIMIT
|
|
FORCE_LOGIN = 0x45, // CHAR_CREATE_FORCE_LOGIN
|
|
|
|
// CHAR_NAME error codes (name validation failures)
|
|
NAME_SUCCESS = 0x57, // CHAR_NAME_SUCCESS
|
|
NAME_FAILURE = 0x58, // CHAR_NAME_FAILURE
|
|
NAME_NO_NAME = 0x59, // CHAR_NAME_NO_NAME
|
|
NAME_TOO_SHORT = 0x5A, // CHAR_NAME_TOO_SHORT
|
|
NAME_TOO_LONG = 0x5B, // CHAR_NAME_TOO_LONG
|
|
NAME_INVALID_CHARACTER = 0x5C, // CHAR_NAME_INVALID_CHARACTER
|
|
NAME_MIXED_LANGUAGES = 0x5D, // CHAR_NAME_MIXED_LANGUAGES
|
|
NAME_PROFANE = 0x5E, // CHAR_NAME_PROFANE
|
|
NAME_RESERVED = 0x5F, // CHAR_NAME_RESERVED
|
|
NAME_INVALID_APOSTROPHE = 0x60, // CHAR_NAME_INVALID_APOSTROPHE
|
|
NAME_MULTIPLE_APOSTROPHES = 0x61, // CHAR_NAME_MULTIPLE_APOSTROPHES
|
|
NAME_THREE_CONSECUTIVE = 0x62, // CHAR_NAME_THREE_CONSECUTIVE (98 decimal)
|
|
NAME_INVALID_SPACE = 0x63, // CHAR_NAME_INVALID_SPACE
|
|
NAME_CONSECUTIVE_SPACES = 0x64, // CHAR_NAME_CONSECUTIVE_SPACES
|
|
NAME_RUSSIAN_CONSECUTIVE_SILENT = 0x65, // CHAR_NAME_RUSSIAN_CONSECUTIVE_SILENT_CHARACTERS
|
|
NAME_RUSSIAN_SILENT_AT_BEGIN_OR_END = 0x66, // CHAR_NAME_RUSSIAN_SILENT_CHARACTER_AT_BEGINNING_OR_END
|
|
NAME_DECLENSION_DOESNT_MATCH = 0x67, // CHAR_NAME_DECLENSION_DOESNT_MATCH_BASE_NAME
|
|
};
|
|
#ifdef _WIN32
|
|
#pragma pop_macro("ERROR")
|
|
#endif
|
|
|
|
struct CharCreateData {
|
|
std::string name;
|
|
Race race;
|
|
Class characterClass;
|
|
Gender gender;
|
|
uint8_t skin = 0;
|
|
uint8_t face = 0;
|
|
uint8_t hairStyle = 0;
|
|
uint8_t hairColor = 0;
|
|
uint8_t facialHair = 0;
|
|
bool useFemaleModel = false; // For nonbinary: choose body type
|
|
};
|
|
|
|
class CharCreatePacket {
|
|
public:
|
|
static network::Packet build(const CharCreateData& data);
|
|
};
|
|
|
|
struct CharCreateResponseData {
|
|
CharCreateResult result;
|
|
};
|
|
|
|
class CharCreateResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, CharCreateResponseData& data);
|
|
};
|
|
|
|
/**
|
|
* CMSG_PLAYER_LOGIN packet builder
|
|
*
|
|
* Select character and enter world
|
|
*/
|
|
class PlayerLoginPacket {
|
|
public:
|
|
/**
|
|
* Build CMSG_PLAYER_LOGIN packet
|
|
*
|
|
* @param characterGuid GUID of character to log in with
|
|
*/
|
|
static network::Packet build(uint64_t characterGuid);
|
|
};
|
|
|
|
/**
|
|
* SMSG_LOGIN_VERIFY_WORLD data (from server)
|
|
*
|
|
* Confirms successful world entry with initial position and map info
|
|
*/
|
|
struct LoginVerifyWorldData {
|
|
uint32_t mapId; // Map ID where character spawned
|
|
float x, y, z; // Initial position coordinates
|
|
float orientation; // Initial orientation (facing direction)
|
|
|
|
bool isValid() const { return mapId != 0xFFFFFFFF; }
|
|
};
|
|
|
|
/**
|
|
* SMSG_LOGIN_VERIFY_WORLD parser
|
|
*/
|
|
class LoginVerifyWorldParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, LoginVerifyWorldData& data);
|
|
};
|
|
|
|
/**
|
|
* SMSG_ACCOUNT_DATA_TIMES data (from server)
|
|
*
|
|
* Contains timestamps for account data (macros, keybindings, etc.)
|
|
*/
|
|
struct AccountDataTimesData {
|
|
uint32_t serverTime; // Current server time (Unix timestamp)
|
|
uint8_t unknown; // Unknown (always 1?)
|
|
uint32_t accountDataTimes[8]; // Timestamps for 8 account data slots
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/**
|
|
* SMSG_ACCOUNT_DATA_TIMES parser
|
|
*/
|
|
class AccountDataTimesParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AccountDataTimesData& data);
|
|
};
|
|
|
|
/**
|
|
* SMSG_MOTD data (from server)
|
|
*
|
|
* Message of the Day from server
|
|
*/
|
|
struct MotdData {
|
|
std::vector<std::string> lines; // MOTD text lines
|
|
|
|
bool isEmpty() const { return lines.empty(); }
|
|
size_t lineCount() const { return lines.size(); }
|
|
};
|
|
|
|
/**
|
|
* SMSG_MOTD parser
|
|
*/
|
|
class MotdParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, MotdData& data);
|
|
};
|
|
|
|
/**
|
|
* CMSG_PING packet builder
|
|
*
|
|
* Heartbeat packet sent periodically to keep connection alive
|
|
*/
|
|
class PingPacket {
|
|
public:
|
|
/**
|
|
* Build CMSG_PING packet
|
|
*
|
|
* @param sequence Sequence number (increments with each ping)
|
|
* @param latency Client-side latency estimate in milliseconds
|
|
* @return Packet ready to send
|
|
*/
|
|
static network::Packet build(uint32_t sequence, uint32_t latency);
|
|
};
|
|
|
|
/**
|
|
* SMSG_PONG data (from server)
|
|
*
|
|
* Response to CMSG_PING, echoes back the sequence number
|
|
*/
|
|
struct PongData {
|
|
uint32_t sequence; // Sequence number from CMSG_PING
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/**
|
|
* SMSG_PONG parser
|
|
*/
|
|
class PongParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, PongData& data);
|
|
};
|
|
|
|
/**
|
|
* Movement flags for player movement
|
|
*/
|
|
enum class MovementFlags : uint32_t {
|
|
NONE = 0x00000000,
|
|
FORWARD = 0x00000001,
|
|
BACKWARD = 0x00000002,
|
|
STRAFE_LEFT = 0x00000004,
|
|
STRAFE_RIGHT = 0x00000008,
|
|
TURN_LEFT = 0x00000010,
|
|
TURN_RIGHT = 0x00000020,
|
|
PITCH_UP = 0x00000040,
|
|
PITCH_DOWN = 0x00000080,
|
|
WALKING = 0x00000100,
|
|
ONTRANSPORT = 0x00000200,
|
|
LEVITATING = 0x00000400,
|
|
ROOT = 0x00000800,
|
|
FALLING = 0x00001000,
|
|
FALLINGFAR = 0x00002000,
|
|
SWIMMING = 0x00200000,
|
|
ASCENDING = 0x00400000,
|
|
CAN_FLY = 0x00800000,
|
|
FLYING = 0x01000000,
|
|
};
|
|
|
|
/**
|
|
* Movement info structure
|
|
*
|
|
* Contains all movement-related data sent in movement packets
|
|
*/
|
|
struct MovementInfo {
|
|
uint32_t flags = 0; // Movement flags
|
|
uint16_t flags2 = 0; // Extra movement flags
|
|
uint32_t time = 0; // Movement timestamp (milliseconds)
|
|
float x = 0.0f; // Position X
|
|
float y = 0.0f; // Position Y
|
|
float z = 0.0f; // Position Z
|
|
float orientation = 0.0f; // Facing direction (radians)
|
|
|
|
// Optional fields (based on flags)
|
|
float pitch = 0.0f; // Pitch angle (swimming/flying)
|
|
uint32_t fallTime = 0; // Time falling (milliseconds)
|
|
float jumpVelocity = 0.0f; // Jump vertical velocity
|
|
float jumpSinAngle = 0.0f; // Jump horizontal sin
|
|
float jumpCosAngle = 0.0f; // Jump horizontal cos
|
|
float jumpXYSpeed = 0.0f; // Jump horizontal speed
|
|
|
|
// Transport fields (when ONTRANSPORT flag is set)
|
|
uint64_t transportGuid = 0; // GUID of transport (boat/zeppelin/etc)
|
|
float transportX = 0.0f; // Local position on transport
|
|
float transportY = 0.0f;
|
|
float transportZ = 0.0f;
|
|
float transportO = 0.0f; // Local orientation on transport
|
|
uint32_t transportTime = 0; // Transport movement timestamp
|
|
int8_t transportSeat = -1; // Transport seat (-1 when unknown/not seated)
|
|
uint32_t transportTime2 = 0; // Secondary transport time (when interpolated movement flag is set)
|
|
|
|
bool hasFlag(MovementFlags flag) const {
|
|
return (flags & static_cast<uint32_t>(flag)) != 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Movement packet builder
|
|
*
|
|
* Builds CMSG_MOVE_* packets with movement info
|
|
*/
|
|
class MovementPacket {
|
|
public:
|
|
static void writePackedGuid(network::Packet& packet, uint64_t guid);
|
|
static void writeMovementPayload(network::Packet& packet, const MovementInfo& info);
|
|
|
|
/**
|
|
* Build a movement packet
|
|
*
|
|
* @param opcode Movement opcode (MSG_MOVE_START_FORWARD, etc.)
|
|
* @param info Movement info
|
|
* @return Packet ready to send
|
|
*/
|
|
static network::Packet build(Opcode opcode, const MovementInfo& info, uint64_t playerGuid = 0);
|
|
};
|
|
|
|
// Forward declare Entity types
|
|
class Entity;
|
|
class EntityManager;
|
|
enum class ObjectType : uint8_t;
|
|
enum class UpdateType : uint8_t;
|
|
|
|
/**
|
|
* Update block for a single object in SMSG_UPDATE_OBJECT
|
|
*/
|
|
struct UpdateBlock {
|
|
UpdateType updateType;
|
|
uint64_t guid = 0;
|
|
ObjectType objectType;
|
|
|
|
// Movement data (for MOVEMENT updates)
|
|
bool hasMovement = false;
|
|
float x = 0.0f, y = 0.0f, z = 0.0f, orientation = 0.0f;
|
|
float runSpeed = 0.0f;
|
|
|
|
// Update flags from movement block (for detecting transports, etc.)
|
|
uint16_t updateFlags = 0;
|
|
|
|
// Transport data from LIVING movement block (MOVEMENTFLAG_ONTRANSPORT)
|
|
bool onTransport = false;
|
|
uint64_t transportGuid = 0;
|
|
float transportX = 0.0f, transportY = 0.0f, transportZ = 0.0f, transportO = 0.0f;
|
|
|
|
// Field data (for VALUES and CREATE updates)
|
|
std::map<uint16_t, uint32_t> fields;
|
|
};
|
|
|
|
/**
|
|
* SMSG_UPDATE_OBJECT data
|
|
*
|
|
* Contains all update blocks in the packet
|
|
*/
|
|
struct UpdateObjectData {
|
|
uint32_t blockCount = 0;
|
|
std::vector<UpdateBlock> blocks;
|
|
|
|
// Out-of-range GUIDs (for OUT_OF_RANGE_OBJECTS)
|
|
std::vector<uint64_t> outOfRangeGuids;
|
|
};
|
|
|
|
/**
|
|
* SMSG_UPDATE_OBJECT parser
|
|
*
|
|
* Parses object updates from server
|
|
*/
|
|
class UpdateObjectParser {
|
|
public:
|
|
/**
|
|
* Parse SMSG_UPDATE_OBJECT packet
|
|
*
|
|
* @param packet Packet to parse
|
|
* @param data Output data
|
|
* @return true if successful
|
|
*/
|
|
static bool parse(network::Packet& packet, UpdateObjectData& data);
|
|
|
|
/**
|
|
* Read packed GUID from packet
|
|
*
|
|
* @param packet Packet to read from
|
|
* @return GUID value
|
|
*/
|
|
static uint64_t readPackedGuid(network::Packet& packet);
|
|
|
|
/**
|
|
* Parse a single update block
|
|
*
|
|
* @param packet Packet to read from
|
|
* @param block Output block
|
|
* @return true if successful
|
|
*/
|
|
static bool parseUpdateBlock(network::Packet& packet, UpdateBlock& block);
|
|
|
|
/**
|
|
* Parse movement block
|
|
*
|
|
* @param packet Packet to read from
|
|
* @param block Output block
|
|
* @return true if successful
|
|
*/
|
|
static bool parseMovementBlock(network::Packet& packet, UpdateBlock& block);
|
|
|
|
/**
|
|
* Parse update fields (mask + values)
|
|
*
|
|
* @param packet Packet to read from
|
|
* @param block Output block
|
|
* @return true if successful
|
|
*/
|
|
static bool parseUpdateFields(network::Packet& packet, UpdateBlock& block);
|
|
};
|
|
|
|
/**
|
|
* SMSG_DESTROY_OBJECT data
|
|
*/
|
|
struct DestroyObjectData {
|
|
uint64_t guid = 0;
|
|
bool isDeath = false; // true if unit died, false if despawned
|
|
};
|
|
|
|
/**
|
|
* SMSG_DESTROY_OBJECT parser
|
|
*/
|
|
class DestroyObjectParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, DestroyObjectData& data);
|
|
};
|
|
|
|
/**
|
|
* Chat message types
|
|
*/
|
|
enum class ChatType : uint8_t {
|
|
SAY = 0,
|
|
PARTY = 1,
|
|
RAID = 2,
|
|
GUILD = 3,
|
|
OFFICER = 4,
|
|
YELL = 5,
|
|
WHISPER = 6,
|
|
WHISPER_INFORM = 7,
|
|
EMOTE = 8,
|
|
TEXT_EMOTE = 9,
|
|
SYSTEM = 10,
|
|
MONSTER_SAY = 11,
|
|
MONSTER_YELL = 12,
|
|
MONSTER_EMOTE = 13,
|
|
CHANNEL = 14,
|
|
CHANNEL_JOIN = 15,
|
|
CHANNEL_LEAVE = 16,
|
|
CHANNEL_LIST = 17,
|
|
CHANNEL_NOTICE = 18,
|
|
CHANNEL_NOTICE_USER = 19,
|
|
AFK = 20,
|
|
DND = 21,
|
|
IGNORED = 22,
|
|
SKILL = 23,
|
|
LOOT = 24,
|
|
BATTLEGROUND = 25,
|
|
BATTLEGROUND_LEADER = 26,
|
|
RAID_LEADER = 27,
|
|
RAID_WARNING = 28,
|
|
ACHIEVEMENT = 29,
|
|
GUILD_ACHIEVEMENT = 30
|
|
};
|
|
|
|
/**
|
|
* Chat language IDs
|
|
*/
|
|
enum class ChatLanguage : uint32_t {
|
|
UNIVERSAL = 0,
|
|
ORCISH = 1,
|
|
DARNASSIAN = 2,
|
|
TAURAHE = 3,
|
|
DWARVISH = 6,
|
|
COMMON = 7,
|
|
DEMONIC = 8,
|
|
TITAN = 9,
|
|
THALASSIAN = 10,
|
|
DRACONIC = 11,
|
|
KALIMAG = 12,
|
|
GNOMISH = 13,
|
|
TROLL = 14,
|
|
GUTTERSPEAK = 33,
|
|
DRAENEI = 35,
|
|
ZOMBIE = 36,
|
|
GNOMISH_BINARY = 37,
|
|
GOBLIN_BINARY = 38,
|
|
ADDON = 0xFFFFFFFF // Used for addon communication
|
|
};
|
|
|
|
/**
|
|
* CMSG_MESSAGECHAT packet builder
|
|
*/
|
|
class MessageChatPacket {
|
|
public:
|
|
/**
|
|
* Build CMSG_MESSAGECHAT packet
|
|
*
|
|
* @param type Chat type (SAY, YELL, etc.)
|
|
* @param language Language ID
|
|
* @param message Message text
|
|
* @param target Target name (for whispers, empty otherwise)
|
|
* @return Packet ready to send
|
|
*/
|
|
static network::Packet build(ChatType type,
|
|
ChatLanguage language,
|
|
const std::string& message,
|
|
const std::string& target = "");
|
|
};
|
|
|
|
/**
|
|
* SMSG_MESSAGECHAT data
|
|
*/
|
|
struct MessageChatData {
|
|
ChatType type;
|
|
ChatLanguage language;
|
|
uint64_t senderGuid = 0;
|
|
std::string senderName;
|
|
uint64_t receiverGuid = 0;
|
|
std::string receiverName;
|
|
std::string message;
|
|
std::string channelName; // For channel messages
|
|
uint8_t chatTag = 0; // Player flags (AFK, DND, GM, etc.)
|
|
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
|
|
|
|
bool isValid() const { return !message.empty(); }
|
|
};
|
|
|
|
/**
|
|
* SMSG_MESSAGECHAT parser
|
|
*/
|
|
class MessageChatParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, MessageChatData& data);
|
|
};
|
|
|
|
/**
|
|
* Get human-readable string for chat type
|
|
*/
|
|
const char* getChatTypeString(ChatType type);
|
|
|
|
// ============================================================
|
|
// Text Emotes
|
|
// ============================================================
|
|
|
|
/**
|
|
* CMSG_TEXT_EMOTE packet builder
|
|
*/
|
|
class TextEmotePacket {
|
|
public:
|
|
static network::Packet build(uint32_t textEmoteId, uint64_t targetGuid = 0);
|
|
};
|
|
|
|
/**
|
|
* SMSG_TEXT_EMOTE data
|
|
*/
|
|
struct TextEmoteData {
|
|
uint64_t senderGuid = 0;
|
|
uint32_t textEmoteId = 0;
|
|
uint32_t emoteNum = 0;
|
|
std::string targetName;
|
|
|
|
bool isValid() const { return senderGuid != 0; }
|
|
};
|
|
|
|
/**
|
|
* SMSG_TEXT_EMOTE parser
|
|
*/
|
|
class TextEmoteParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, TextEmoteData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Channel System
|
|
// ============================================================
|
|
|
|
/**
|
|
* CMSG_JOIN_CHANNEL packet builder
|
|
*/
|
|
class JoinChannelPacket {
|
|
public:
|
|
static network::Packet build(const std::string& channelName, const std::string& password = "");
|
|
};
|
|
|
|
/**
|
|
* CMSG_LEAVE_CHANNEL packet builder
|
|
*/
|
|
class LeaveChannelPacket {
|
|
public:
|
|
static network::Packet build(const std::string& channelName);
|
|
};
|
|
|
|
/**
|
|
* Channel notification types
|
|
*/
|
|
enum class ChannelNotifyType : uint8_t {
|
|
YOU_JOINED = 0x00,
|
|
YOU_LEFT = 0x01,
|
|
WRONG_PASSWORD = 0x02,
|
|
NOT_MEMBER = 0x03,
|
|
NOT_MODERATOR = 0x04,
|
|
PASSWORD_CHANGED = 0x05,
|
|
OWNER_CHANGED = 0x06,
|
|
PLAYER_NOT_FOUND = 0x07,
|
|
NOT_OWNER = 0x08,
|
|
CHANNEL_OWNER = 0x09,
|
|
MODE_CHANGE = 0x0A,
|
|
ANNOUNCEMENTS_ON = 0x0B,
|
|
ANNOUNCEMENTS_OFF = 0x0C,
|
|
MODERATION_ON = 0x0D,
|
|
MODERATION_OFF = 0x0E,
|
|
MUTED = 0x0F,
|
|
PLAYER_KICKED = 0x10,
|
|
BANNED = 0x11,
|
|
PLAYER_BANNED = 0x12,
|
|
PLAYER_UNBANNED = 0x13,
|
|
PLAYER_NOT_BANNED = 0x14,
|
|
PLAYER_ALREADY_MEMBER = 0x15,
|
|
INVITE = 0x16,
|
|
INVITE_WRONG_FACTION = 0x17,
|
|
WRONG_FACTION = 0x18,
|
|
INVALID_NAME = 0x19,
|
|
NOT_MODERATED = 0x1A,
|
|
PLAYER_INVITED = 0x1B,
|
|
PLAYER_INVITE_BANNED = 0x1C,
|
|
THROTTLED = 0x1D,
|
|
NOT_IN_AREA = 0x1E,
|
|
NOT_IN_LFG = 0x1F,
|
|
};
|
|
|
|
/**
|
|
* SMSG_CHANNEL_NOTIFY data
|
|
*/
|
|
struct ChannelNotifyData {
|
|
ChannelNotifyType notifyType = ChannelNotifyType::YOU_JOINED;
|
|
std::string channelName;
|
|
uint64_t senderGuid = 0;
|
|
|
|
bool isValid() const { return !channelName.empty(); }
|
|
};
|
|
|
|
/**
|
|
* SMSG_CHANNEL_NOTIFY parser
|
|
*/
|
|
class ChannelNotifyParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ChannelNotifyData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Server Info Commands
|
|
// ============================================================
|
|
|
|
/** CMSG_QUERY_TIME packet builder */
|
|
class QueryTimePacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** SMSG_QUERY_TIME_RESPONSE data */
|
|
struct QueryTimeResponseData {
|
|
uint32_t serverTime = 0; // Unix timestamp
|
|
uint32_t timeOffset = 0; // Time until next daily reset
|
|
};
|
|
|
|
/** SMSG_QUERY_TIME_RESPONSE parser */
|
|
class QueryTimeResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, QueryTimeResponseData& data);
|
|
};
|
|
|
|
/** CMSG_PLAYED_TIME packet builder */
|
|
class RequestPlayedTimePacket {
|
|
public:
|
|
static network::Packet build(bool sendToChat = true);
|
|
};
|
|
|
|
/** SMSG_PLAYED_TIME data */
|
|
struct PlayedTimeData {
|
|
uint32_t totalTimePlayed = 0; // Total seconds played
|
|
uint32_t levelTimePlayed = 0; // Seconds played at current level
|
|
bool triggerMessage = false; // Whether to show in chat
|
|
};
|
|
|
|
/** SMSG_PLAYED_TIME parser */
|
|
class PlayedTimeParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, PlayedTimeData& data);
|
|
};
|
|
|
|
/** CMSG_WHO packet builder */
|
|
class WhoPacket {
|
|
public:
|
|
static network::Packet build(uint32_t minLevel = 0, uint32_t maxLevel = 100,
|
|
const std::string& playerName = "",
|
|
const std::string& guildName = "",
|
|
uint32_t raceMask = 0xFFFFFFFF,
|
|
uint32_t classMask = 0xFFFFFFFF,
|
|
uint32_t zones = 0);
|
|
};
|
|
|
|
// ============================================================
|
|
// Social Commands
|
|
// ============================================================
|
|
|
|
/** CMSG_ADD_FRIEND packet builder */
|
|
class AddFriendPacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName, const std::string& note = "");
|
|
};
|
|
|
|
/** CMSG_DEL_FRIEND packet builder */
|
|
class DelFriendPacket {
|
|
public:
|
|
static network::Packet build(uint64_t friendGuid);
|
|
};
|
|
|
|
/** CMSG_SET_CONTACT_NOTES packet builder */
|
|
class SetContactNotesPacket {
|
|
public:
|
|
static network::Packet build(uint64_t friendGuid, const std::string& note);
|
|
};
|
|
|
|
/** SMSG_FRIEND_STATUS data */
|
|
struct FriendStatusData {
|
|
uint8_t status = 0; // 0 = offline, 1 = online, etc.
|
|
uint64_t guid = 0;
|
|
std::string note;
|
|
uint8_t chatFlag = 0;
|
|
};
|
|
|
|
/** SMSG_FRIEND_STATUS parser */
|
|
class FriendStatusParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, FriendStatusData& data);
|
|
};
|
|
|
|
/** CMSG_ADD_IGNORE packet builder */
|
|
class AddIgnorePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_DEL_IGNORE packet builder */
|
|
class DelIgnorePacket {
|
|
public:
|
|
static network::Packet build(uint64_t ignoreGuid);
|
|
};
|
|
|
|
// ============================================================
|
|
// Logout Commands
|
|
// ============================================================
|
|
|
|
/** CMSG_LOGOUT_REQUEST packet builder */
|
|
class LogoutRequestPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_LOGOUT_CANCEL packet builder */
|
|
class LogoutCancelPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** SMSG_LOGOUT_RESPONSE data */
|
|
struct LogoutResponseData {
|
|
uint32_t result = 0; // 0 = success, 1 = failure
|
|
uint8_t instant = 0; // 1 = instant logout
|
|
};
|
|
|
|
/** SMSG_LOGOUT_RESPONSE parser */
|
|
class LogoutResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, LogoutResponseData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Stand State
|
|
// ============================================================
|
|
|
|
/** CMSG_STANDSTATECHANGE packet builder */
|
|
class StandStateChangePacket {
|
|
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);
|
|
};
|
|
|
|
// ============================================================
|
|
// PvP
|
|
// ============================================================
|
|
|
|
/** CMSG_TOGGLE_PVP packet builder */
|
|
class TogglePvpPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
// ============================================================
|
|
// Guild Commands
|
|
// ============================================================
|
|
|
|
/** CMSG_GUILD_INFO packet builder */
|
|
class GuildInfoPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GUILD_ROSTER packet builder */
|
|
class GuildRosterPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GUILD_MOTD packet builder */
|
|
class GuildMotdPacket {
|
|
public:
|
|
static network::Packet build(const std::string& motd);
|
|
};
|
|
|
|
/** CMSG_GUILD_PROMOTE packet builder */
|
|
class GuildPromotePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GUILD_DEMOTE packet builder */
|
|
class GuildDemotePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GUILD_LEAVE packet builder */
|
|
class GuildLeavePacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GUILD_INVITE packet builder */
|
|
class GuildInvitePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GUILD_QUERY packet builder */
|
|
class GuildQueryPacket {
|
|
public:
|
|
static network::Packet build(uint32_t guildId);
|
|
};
|
|
|
|
/** CMSG_GUILD_REMOVE packet builder */
|
|
class GuildRemovePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GUILD_DISBAND packet builder (empty body) */
|
|
class GuildDisbandPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GUILD_LEADER packet builder */
|
|
class GuildLeaderPacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GUILD_SET_PUBLIC_NOTE packet builder */
|
|
class GuildSetPublicNotePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName, const std::string& note);
|
|
};
|
|
|
|
/** CMSG_GUILD_SET_OFFICER_NOTE packet builder */
|
|
class GuildSetOfficerNotePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName, const std::string& note);
|
|
};
|
|
|
|
/** CMSG_GUILD_ACCEPT packet builder (empty body) */
|
|
class GuildAcceptPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GUILD_DECLINE packet builder (empty body) */
|
|
class GuildDeclineInvitationPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
// Guild event type constants
|
|
namespace GuildEvent {
|
|
constexpr uint8_t PROMOTION = 0;
|
|
constexpr uint8_t DEMOTION = 1;
|
|
constexpr uint8_t MOTD = 2;
|
|
constexpr uint8_t JOINED = 3;
|
|
constexpr uint8_t LEFT = 4;
|
|
constexpr uint8_t REMOVED = 5;
|
|
constexpr uint8_t LEADER_IS = 6;
|
|
constexpr uint8_t LEADER_CHANGED = 7;
|
|
constexpr uint8_t DISBANDED = 8;
|
|
constexpr uint8_t SIGNED_ON = 14;
|
|
constexpr uint8_t SIGNED_OFF = 15;
|
|
}
|
|
|
|
/** SMSG_GUILD_QUERY_RESPONSE data */
|
|
struct GuildQueryResponseData {
|
|
uint32_t guildId = 0;
|
|
std::string guildName;
|
|
std::string rankNames[10];
|
|
uint32_t emblemStyle = 0;
|
|
uint32_t emblemColor = 0;
|
|
uint32_t borderStyle = 0;
|
|
uint32_t borderColor = 0;
|
|
uint32_t backgroundColor = 0;
|
|
uint32_t rankCount = 0;
|
|
|
|
bool isValid() const { return guildId != 0 && !guildName.empty(); }
|
|
};
|
|
|
|
/** SMSG_GUILD_QUERY_RESPONSE parser */
|
|
class GuildQueryResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildQueryResponseData& data);
|
|
};
|
|
|
|
/** SMSG_GUILD_INFO data */
|
|
struct GuildInfoData {
|
|
std::string guildName;
|
|
uint32_t creationDay = 0;
|
|
uint32_t creationMonth = 0;
|
|
uint32_t creationYear = 0;
|
|
uint32_t numMembers = 0;
|
|
uint32_t numAccounts = 0;
|
|
|
|
bool isValid() const { return !guildName.empty(); }
|
|
};
|
|
|
|
/** SMSG_GUILD_INFO parser */
|
|
class GuildInfoParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildInfoData& data);
|
|
};
|
|
|
|
/** Guild roster member entry */
|
|
struct GuildRosterMember {
|
|
uint64_t guid = 0;
|
|
bool online = false;
|
|
std::string name;
|
|
uint32_t rankIndex = 0;
|
|
uint8_t level = 0;
|
|
uint8_t classId = 0;
|
|
uint8_t gender = 0;
|
|
uint32_t zoneId = 0;
|
|
float lastOnline = 0.0f;
|
|
std::string publicNote;
|
|
std::string officerNote;
|
|
};
|
|
|
|
/** Guild rank info */
|
|
struct GuildRankInfo {
|
|
uint32_t rights = 0;
|
|
uint32_t goldLimit = 0;
|
|
};
|
|
|
|
/** SMSG_GUILD_ROSTER data */
|
|
struct GuildRosterData {
|
|
std::string motd;
|
|
std::string guildInfo;
|
|
std::vector<GuildRankInfo> ranks;
|
|
std::vector<GuildRosterMember> members;
|
|
|
|
bool isEmpty() const { return members.empty(); }
|
|
};
|
|
|
|
/** SMSG_GUILD_ROSTER parser */
|
|
class GuildRosterParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildRosterData& data);
|
|
};
|
|
|
|
/** SMSG_GUILD_EVENT data */
|
|
struct GuildEventData {
|
|
uint8_t eventType = 0;
|
|
uint8_t numStrings = 0;
|
|
std::string strings[3];
|
|
uint64_t guid = 0;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/** SMSG_GUILD_EVENT parser */
|
|
class GuildEventParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildEventData& data);
|
|
};
|
|
|
|
/** SMSG_GUILD_INVITE data */
|
|
struct GuildInviteResponseData {
|
|
std::string inviterName;
|
|
std::string guildName;
|
|
|
|
bool isValid() const { return !inviterName.empty(); }
|
|
};
|
|
|
|
/** SMSG_GUILD_INVITE parser */
|
|
class GuildInviteResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildInviteResponseData& data);
|
|
};
|
|
|
|
/** SMSG_GUILD_COMMAND_RESULT data */
|
|
struct GuildCommandResultData {
|
|
uint32_t command = 0;
|
|
std::string name;
|
|
uint32_t errorCode = 0;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/** SMSG_GUILD_COMMAND_RESULT parser */
|
|
class GuildCommandResultParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildCommandResultData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Ready Check
|
|
// ============================================================
|
|
|
|
/** MSG_RAID_READY_CHECK packet builder */
|
|
class ReadyCheckPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** MSG_RAID_READY_CHECK_CONFIRM packet builder */
|
|
class ReadyCheckConfirmPacket {
|
|
public:
|
|
static network::Packet build(bool ready);
|
|
};
|
|
|
|
// ============================================================
|
|
// Duel
|
|
// ============================================================
|
|
|
|
/** CMSG_DUEL_CANCELLED packet builder */
|
|
class DuelCancelPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
// ============================================================
|
|
// Party/Raid Management
|
|
// ============================================================
|
|
|
|
/** CMSG_GROUP_UNINVITE_GUID packet builder */
|
|
class GroupUninvitePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** CMSG_GROUP_DISBAND packet builder */
|
|
class GroupDisbandPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** MSG_RAID_TARGET_UPDATE packet builder */
|
|
class RaidTargetUpdatePacket {
|
|
public:
|
|
/**
|
|
* Build raid target marker update packet
|
|
* @param targetIndex 0-7 for raid icons, 0 = MainTank, 1 = MainAssist
|
|
* @param targetGuid GUID to mark, or 0 to clear
|
|
*/
|
|
static network::Packet build(uint8_t targetIndex, uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_REQUEST_RAID_INFO packet builder */
|
|
class RequestRaidInfoPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
// ============================================================
|
|
// Combat and Trade
|
|
// ============================================================
|
|
|
|
/** Duel request packet builder (implemented via CMSG_CAST_SPELL, spell 7266) */
|
|
class DuelProposedPacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_INITIATE_TRADE packet builder */
|
|
class InitiateTradePacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_ATTACKSWING packet builder */
|
|
class AttackSwingPacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_ATTACKSTOP packet builder */
|
|
class AttackStopPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_CANCEL_CAST packet builder */
|
|
class CancelCastPacket {
|
|
public:
|
|
static network::Packet build(uint32_t spellId);
|
|
};
|
|
|
|
// ============================================================
|
|
// Random Roll
|
|
// ============================================================
|
|
|
|
/** CMSG_RANDOM_ROLL packet builder */
|
|
class RandomRollPacket {
|
|
public:
|
|
static network::Packet build(uint32_t minRoll, uint32_t maxRoll);
|
|
};
|
|
|
|
/** SMSG_RANDOM_ROLL data */
|
|
struct RandomRollData {
|
|
uint64_t rollerGuid = 0;
|
|
uint64_t targetGuid = 0; // 0 for party roll
|
|
uint32_t minRoll = 0;
|
|
uint32_t maxRoll = 0;
|
|
uint32_t result = 0;
|
|
};
|
|
|
|
/** SMSG_RANDOM_ROLL parser */
|
|
class RandomRollParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, RandomRollData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 1: Foundation — Targeting, Name Queries
|
|
// ============================================================
|
|
|
|
/** CMSG_SET_SELECTION packet builder */
|
|
class SetSelectionPacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_SET_ACTIVE_MOVER packet builder */
|
|
class SetActiveMoverPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** CMSG_INSPECT packet builder */
|
|
class InspectPacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_NAME_QUERY packet builder */
|
|
class NameQueryPacket {
|
|
public:
|
|
static network::Packet build(uint64_t playerGuid);
|
|
};
|
|
|
|
/** SMSG_NAME_QUERY_RESPONSE data */
|
|
struct NameQueryResponseData {
|
|
uint64_t guid = 0;
|
|
uint8_t found = 1; // 0 = found, 1 = not found
|
|
std::string name;
|
|
std::string realmName;
|
|
uint8_t race = 0;
|
|
uint8_t gender = 0;
|
|
uint8_t classId = 0;
|
|
|
|
bool isValid() const { return found == 0 && !name.empty(); }
|
|
};
|
|
|
|
/** SMSG_NAME_QUERY_RESPONSE parser */
|
|
class NameQueryResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, NameQueryResponseData& data);
|
|
};
|
|
|
|
/** CMSG_CREATURE_QUERY packet builder */
|
|
class CreatureQueryPacket {
|
|
public:
|
|
static network::Packet build(uint32_t entry, uint64_t guid);
|
|
};
|
|
|
|
/** SMSG_CREATURE_QUERY_RESPONSE data */
|
|
struct CreatureQueryResponseData {
|
|
uint32_t entry = 0;
|
|
std::string name;
|
|
std::string subName;
|
|
std::string iconName;
|
|
uint32_t typeFlags = 0;
|
|
uint32_t creatureType = 0;
|
|
uint32_t family = 0;
|
|
uint32_t rank = 0; // 0=Normal, 1=Elite, 2=Rare Elite, 3=Boss, 4=Rare
|
|
|
|
bool isValid() const { return entry != 0 && !name.empty(); }
|
|
};
|
|
|
|
/** SMSG_CREATURE_QUERY_RESPONSE parser */
|
|
class CreatureQueryResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, CreatureQueryResponseData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// GameObject Query
|
|
// ============================================================
|
|
|
|
/** CMSG_GAMEOBJECT_QUERY packet builder */
|
|
class GameObjectQueryPacket {
|
|
public:
|
|
static network::Packet build(uint32_t entry, uint64_t guid);
|
|
};
|
|
|
|
/** SMSG_GAMEOBJECT_QUERY_RESPONSE data */
|
|
struct GameObjectQueryResponseData {
|
|
uint32_t entry = 0;
|
|
std::string name;
|
|
uint32_t type = 0; // GameObjectType (e.g. 3=chest, 2=questgiver, 15=MO_TRANSPORT)
|
|
uint32_t displayId = 0;
|
|
uint32_t data[24] = {}; // Type-specific data fields (e.g. data[0]=taxiPathId for MO_TRANSPORT)
|
|
bool hasData = false; // Whether data[] was parsed
|
|
|
|
bool isValid() const { return entry != 0 && !name.empty(); }
|
|
};
|
|
|
|
/** SMSG_GAMEOBJECT_QUERY_RESPONSE parser */
|
|
class GameObjectQueryResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GameObjectQueryResponseData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Item Query
|
|
// ============================================================
|
|
|
|
/** CMSG_ITEM_QUERY_SINGLE packet builder */
|
|
class ItemQueryPacket {
|
|
public:
|
|
static network::Packet build(uint32_t entry, uint64_t guid);
|
|
};
|
|
|
|
/** SMSG_ITEM_QUERY_SINGLE_RESPONSE data */
|
|
struct ItemQueryResponseData {
|
|
uint32_t entry = 0;
|
|
std::string name;
|
|
uint32_t itemClass = 0;
|
|
uint32_t subClass = 0;
|
|
uint32_t displayInfoId = 0;
|
|
uint32_t quality = 0;
|
|
uint32_t inventoryType = 0;
|
|
int32_t maxStack = 1;
|
|
uint32_t containerSlots = 0;
|
|
float damageMin = 0.0f;
|
|
float damageMax = 0.0f;
|
|
uint32_t delayMs = 0;
|
|
int32_t armor = 0;
|
|
int32_t stamina = 0;
|
|
int32_t strength = 0;
|
|
int32_t agility = 0;
|
|
int32_t intellect = 0;
|
|
int32_t spirit = 0;
|
|
uint32_t sellPrice = 0;
|
|
std::string subclassName;
|
|
bool valid = false;
|
|
};
|
|
|
|
/** SMSG_ITEM_QUERY_SINGLE_RESPONSE parser */
|
|
class ItemQueryResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ItemQueryResponseData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 2: Combat Core
|
|
// ============================================================
|
|
|
|
/** SMSG_MONSTER_MOVE data */
|
|
struct MonsterMoveData {
|
|
uint64_t guid = 0;
|
|
float x = 0, y = 0, z = 0; // Current position (server coords)
|
|
uint8_t moveType = 0; // 0=Normal, 1=Stop, 2=FacingSpot, 3=FacingTarget, 4=FacingAngle
|
|
float facingAngle = 0;
|
|
uint64_t facingTarget = 0;
|
|
uint32_t splineFlags = 0;
|
|
uint32_t duration = 0;
|
|
// Destination (final point of the spline, server coords)
|
|
float destX = 0, destY = 0, destZ = 0;
|
|
bool hasDest = false;
|
|
};
|
|
|
|
class MonsterMoveParser {
|
|
public:
|
|
// WotLK 3.3.5a format: PackedGUID + uint8 unk + float[3] + uint32 splineId + uint8 moveType + ...
|
|
static bool parse(network::Packet& packet, MonsterMoveData& data);
|
|
// Vanilla 1.12 format: PackedGUID + float[3] + uint32 timeInMs + uint8 moveType + ...
|
|
// Used for Classic/TBC/Turtle WoW servers (no splineId, timeInMs before moveType)
|
|
static bool parseVanilla(network::Packet& packet, MonsterMoveData& data);
|
|
};
|
|
|
|
/** SMSG_ATTACKSTART data */
|
|
struct AttackStartData {
|
|
uint64_t attackerGuid = 0;
|
|
uint64_t victimGuid = 0;
|
|
bool isValid() const { return attackerGuid != 0 && victimGuid != 0; }
|
|
};
|
|
|
|
class AttackStartParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AttackStartData& data);
|
|
};
|
|
|
|
/** SMSG_ATTACKSTOP data */
|
|
struct AttackStopData {
|
|
uint64_t attackerGuid = 0;
|
|
uint64_t victimGuid = 0;
|
|
uint32_t unknown = 0;
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class AttackStopParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AttackStopData& data);
|
|
};
|
|
|
|
/** Sub-damage entry for melee hits */
|
|
struct SubDamage {
|
|
uint32_t schoolMask = 0;
|
|
float damage = 0.0f;
|
|
uint32_t intDamage = 0;
|
|
uint32_t absorbed = 0;
|
|
uint32_t resisted = 0;
|
|
};
|
|
|
|
/** SMSG_ATTACKERSTATEUPDATE data */
|
|
struct AttackerStateUpdateData {
|
|
uint32_t hitInfo = 0;
|
|
uint64_t attackerGuid = 0;
|
|
uint64_t targetGuid = 0;
|
|
int32_t totalDamage = 0;
|
|
uint8_t subDamageCount = 0;
|
|
std::vector<SubDamage> subDamages;
|
|
uint32_t victimState = 0; // 0=hit, 1=dodge, 2=parry, 3=interrupt, 4=block, etc.
|
|
int32_t overkill = -1;
|
|
uint32_t blocked = 0;
|
|
|
|
bool isValid() const { return attackerGuid != 0; }
|
|
bool isCrit() const { return (hitInfo & 0x200) != 0; }
|
|
bool isMiss() const { return (hitInfo & 0x10) != 0; }
|
|
};
|
|
|
|
class AttackerStateUpdateParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AttackerStateUpdateData& data);
|
|
};
|
|
|
|
/** SMSG_SPELLNONMELEEDAMAGELOG data (simplified) */
|
|
struct SpellDamageLogData {
|
|
uint64_t targetGuid = 0;
|
|
uint64_t attackerGuid = 0;
|
|
uint32_t spellId = 0;
|
|
uint32_t damage = 0;
|
|
uint32_t overkill = 0;
|
|
uint8_t schoolMask = 0;
|
|
uint32_t absorbed = 0;
|
|
uint32_t resisted = 0;
|
|
bool isCrit = false;
|
|
|
|
bool isValid() const { return spellId != 0; }
|
|
};
|
|
|
|
class SpellDamageLogParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, SpellDamageLogData& data);
|
|
};
|
|
|
|
/** SMSG_SPELLHEALLOG data (simplified) */
|
|
struct SpellHealLogData {
|
|
uint64_t targetGuid = 0;
|
|
uint64_t casterGuid = 0;
|
|
uint32_t spellId = 0;
|
|
uint32_t heal = 0;
|
|
uint32_t overheal = 0;
|
|
uint32_t absorbed = 0;
|
|
bool isCrit = false;
|
|
|
|
bool isValid() const { return spellId != 0; }
|
|
};
|
|
|
|
class SpellHealLogParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, SpellHealLogData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// XP Gain
|
|
// ============================================================
|
|
|
|
/** SMSG_LOG_XPGAIN data */
|
|
struct XpGainData {
|
|
uint64_t victimGuid = 0; // 0 for non-kill XP (quest, exploration)
|
|
uint32_t totalXp = 0;
|
|
uint8_t type = 0; // 0 = kill, 1 = non-kill
|
|
uint32_t groupBonus = 0;
|
|
|
|
bool isValid() const { return totalXp > 0; }
|
|
};
|
|
|
|
class XpGainParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, XpGainData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 3: Spells, Action Bar, Auras
|
|
// ============================================================
|
|
|
|
/** SMSG_INITIAL_SPELLS data */
|
|
struct InitialSpellsData {
|
|
uint8_t talentSpec = 0;
|
|
std::vector<uint32_t> spellIds;
|
|
std::vector<SpellCooldownEntry> cooldowns;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class InitialSpellsParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, InitialSpellsData& data);
|
|
};
|
|
|
|
/** CMSG_CAST_SPELL packet builder */
|
|
class CastSpellPacket {
|
|
public:
|
|
static network::Packet build(uint32_t spellId, uint64_t targetGuid, uint8_t castCount);
|
|
};
|
|
|
|
/** CMSG_CANCEL_AURA packet builder */
|
|
class CancelAuraPacket {
|
|
public:
|
|
static network::Packet build(uint32_t spellId);
|
|
};
|
|
|
|
/** SMSG_CAST_FAILED data */
|
|
struct CastFailedData {
|
|
uint8_t castCount = 0;
|
|
uint32_t spellId = 0;
|
|
uint8_t result = 0;
|
|
|
|
bool isValid() const { return spellId != 0; }
|
|
};
|
|
|
|
class CastFailedParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, CastFailedData& data);
|
|
};
|
|
|
|
/** SMSG_SPELL_START data (simplified) */
|
|
struct SpellStartData {
|
|
uint64_t casterGuid = 0;
|
|
uint64_t casterUnit = 0;
|
|
uint8_t castCount = 0;
|
|
uint32_t spellId = 0;
|
|
uint32_t castFlags = 0;
|
|
uint32_t castTime = 0;
|
|
uint64_t targetGuid = 0;
|
|
|
|
bool isValid() const { return spellId != 0; }
|
|
};
|
|
|
|
class SpellStartParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, SpellStartData& data);
|
|
};
|
|
|
|
/** SMSG_SPELL_GO data (simplified) */
|
|
struct SpellGoData {
|
|
uint64_t casterGuid = 0;
|
|
uint64_t casterUnit = 0;
|
|
uint8_t castCount = 0;
|
|
uint32_t spellId = 0;
|
|
uint32_t castFlags = 0;
|
|
uint8_t hitCount = 0;
|
|
std::vector<uint64_t> hitTargets;
|
|
uint8_t missCount = 0;
|
|
|
|
bool isValid() const { return spellId != 0; }
|
|
};
|
|
|
|
class SpellGoParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, SpellGoData& data);
|
|
};
|
|
|
|
/** SMSG_AURA_UPDATE / SMSG_AURA_UPDATE_ALL data */
|
|
struct AuraUpdateData {
|
|
uint64_t guid = 0;
|
|
std::vector<std::pair<uint8_t, AuraSlot>> updates; // slot index + aura data
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class AuraUpdateParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuraUpdateData& data, bool isAll);
|
|
};
|
|
|
|
/** SMSG_SPELL_COOLDOWN data */
|
|
struct SpellCooldownData {
|
|
uint64_t guid = 0;
|
|
uint8_t flags = 0;
|
|
std::vector<std::pair<uint32_t, uint32_t>> cooldowns; // spellId, cooldownMs
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class SpellCooldownParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, SpellCooldownData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 4: Group/Party System
|
|
// ============================================================
|
|
|
|
/** CMSG_GROUP_INVITE packet builder */
|
|
class GroupInvitePacket {
|
|
public:
|
|
static network::Packet build(const std::string& playerName);
|
|
};
|
|
|
|
/** SMSG_GROUP_INVITE data */
|
|
struct GroupInviteResponseData {
|
|
uint8_t canAccept = 0;
|
|
std::string inviterName;
|
|
|
|
bool isValid() const { return !inviterName.empty(); }
|
|
};
|
|
|
|
class GroupInviteResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GroupInviteResponseData& data);
|
|
};
|
|
|
|
/** CMSG_GROUP_ACCEPT packet builder */
|
|
class GroupAcceptPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_GROUP_DECLINE packet builder */
|
|
class GroupDeclinePacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** SMSG_GROUP_LIST parser */
|
|
class GroupListParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GroupListData& data);
|
|
};
|
|
|
|
/** SMSG_PARTY_COMMAND_RESULT data */
|
|
struct PartyCommandResultData {
|
|
PartyCommand command;
|
|
std::string name;
|
|
PartyResult result;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class PartyCommandResultParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, PartyCommandResultData& data);
|
|
};
|
|
|
|
/** SMSG_GROUP_DECLINE data */
|
|
struct GroupDeclineData {
|
|
std::string playerName;
|
|
bool isValid() const { return !playerName.empty(); }
|
|
};
|
|
|
|
class GroupDeclineResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GroupDeclineData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 5: Loot System
|
|
// ============================================================
|
|
|
|
/** Loot item entry */
|
|
struct LootItem {
|
|
uint8_t slotIndex = 0;
|
|
uint32_t itemId = 0;
|
|
uint32_t count = 0;
|
|
uint32_t displayInfoId = 0;
|
|
uint32_t randomSuffix = 0;
|
|
uint32_t randomPropertyId = 0;
|
|
uint8_t lootSlotType = 0;
|
|
bool isQuestItem = false;
|
|
};
|
|
|
|
/** SMSG_LOOT_RESPONSE data */
|
|
struct LootResponseData {
|
|
uint64_t lootGuid = 0;
|
|
uint8_t lootType = 0;
|
|
uint32_t gold = 0; // In copper
|
|
std::vector<LootItem> items;
|
|
|
|
bool isValid() const { return true; }
|
|
uint32_t getGold() const { return gold / 10000; }
|
|
uint32_t getSilver() const { return (gold / 100) % 100; }
|
|
uint32_t getCopper() const { return gold % 100; }
|
|
};
|
|
|
|
/** CMSG_LOOT packet builder */
|
|
class LootPacket {
|
|
public:
|
|
static network::Packet build(uint64_t targetGuid);
|
|
};
|
|
|
|
/** CMSG_AUTOSTORE_LOOT_ITEM packet builder */
|
|
class AutostoreLootItemPacket {
|
|
public:
|
|
static network::Packet build(uint8_t slotIndex);
|
|
};
|
|
|
|
/** CMSG_USE_ITEM packet builder */
|
|
class UseItemPacket {
|
|
public:
|
|
static network::Packet build(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid);
|
|
};
|
|
|
|
/** CMSG_AUTOEQUIP_ITEM packet builder */
|
|
class AutoEquipItemPacket {
|
|
public:
|
|
static network::Packet build(uint8_t srcBag, uint8_t srcSlot);
|
|
};
|
|
|
|
/** CMSG_SWAP_ITEM packet builder */
|
|
class SwapItemPacket {
|
|
public:
|
|
// Order matches AzerothCore handler: destBag, destSlot, srcBag, srcSlot.
|
|
static network::Packet build(uint8_t dstBag, uint8_t dstSlot, uint8_t srcBag, uint8_t srcSlot);
|
|
};
|
|
|
|
/** CMSG_SWAP_INV_ITEM packet builder */
|
|
class SwapInvItemPacket {
|
|
public:
|
|
// WoW inventory: slots are in the "inventory" range (equipment 0-18, bags 19-22, backpack 23-38).
|
|
// This swaps two inventory slots directly.
|
|
static network::Packet build(uint8_t srcSlot, uint8_t dstSlot);
|
|
};
|
|
|
|
/** CMSG_LOOT_MONEY packet builder (empty body) */
|
|
class LootMoneyPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_LOOT_RELEASE packet builder */
|
|
class LootReleasePacket {
|
|
public:
|
|
static network::Packet build(uint64_t lootGuid);
|
|
};
|
|
|
|
/** SMSG_LOOT_RESPONSE parser */
|
|
class LootResponseParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, LootResponseData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 5: NPC Gossip
|
|
// ============================================================
|
|
|
|
/** Gossip menu option */
|
|
struct GossipOption {
|
|
uint32_t id = 0;
|
|
uint8_t icon = 0; // 0=chat, 1=vendor, 2=taxi, 3=trainer, etc.
|
|
bool isCoded = false;
|
|
uint32_t boxMoney = 0;
|
|
std::string text;
|
|
std::string boxText;
|
|
};
|
|
|
|
/** Gossip quest item */
|
|
struct GossipQuestItem {
|
|
uint32_t questId = 0;
|
|
uint32_t questIcon = 0;
|
|
int32_t questLevel = 0;
|
|
uint32_t questFlags = 0;
|
|
uint8_t isRepeatable = 0;
|
|
std::string title;
|
|
};
|
|
|
|
/** SMSG_GOSSIP_MESSAGE data */
|
|
struct GossipMessageData {
|
|
uint64_t npcGuid = 0;
|
|
uint32_t menuId = 0;
|
|
uint32_t titleTextId = 0;
|
|
std::vector<GossipOption> options;
|
|
std::vector<GossipQuestItem> quests;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/** CMSG_GOSSIP_HELLO packet builder */
|
|
class GossipHelloPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_HELLO packet builder */
|
|
class QuestgiverHelloPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid);
|
|
};
|
|
|
|
/** CMSG_GOSSIP_SELECT_OPTION packet builder */
|
|
class GossipSelectOptionPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t menuId, uint32_t optionId, const std::string& code = "");
|
|
};
|
|
|
|
/** SMSG_GOSSIP_MESSAGE parser */
|
|
class GossipMessageParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GossipMessageData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Bind Point (Hearthstone)
|
|
// ============================================================
|
|
|
|
struct BindPointUpdateData {
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
float z = 0.0f;
|
|
uint32_t mapId = 0;
|
|
uint32_t zoneId = 0;
|
|
};
|
|
|
|
/** CMSG_BINDER_ACTIVATE packet builder */
|
|
class BinderActivatePacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid);
|
|
};
|
|
|
|
/** SMSG_BINDPOINTUPDATE parser */
|
|
class BindPointUpdateParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, BindPointUpdateData& data);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_QUERY_QUEST packet builder */
|
|
class QuestgiverQueryQuestPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t questId);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_ACCEPT_QUEST packet builder */
|
|
class QuestgiverAcceptQuestPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t questId);
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_QUEST_DETAILS data (simplified) */
|
|
struct QuestDetailsData {
|
|
uint64_t npcGuid = 0;
|
|
uint32_t questId = 0;
|
|
std::string title;
|
|
std::string details; // Quest description text
|
|
std::string objectives; // Objectives text
|
|
uint32_t suggestedPlayers = 0;
|
|
uint32_t rewardMoney = 0;
|
|
uint32_t rewardXp = 0;
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_QUEST_DETAILS parser */
|
|
class QuestDetailsParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, QuestDetailsData& data);
|
|
};
|
|
|
|
/** Reward item entry (shared by quest detail/offer windows) */
|
|
struct QuestRewardItem {
|
|
uint32_t itemId = 0;
|
|
uint32_t count = 0;
|
|
uint32_t displayInfoId = 0;
|
|
uint32_t choiceSlot = 0; // Original reward slot index from server payload
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_REQUEST_ITEMS data (turn-in progress check) */
|
|
struct QuestRequestItemsData {
|
|
uint64_t npcGuid = 0;
|
|
uint32_t questId = 0;
|
|
std::string title;
|
|
std::string completionText;
|
|
uint32_t requiredMoney = 0;
|
|
uint32_t completableFlags = 0; // 0x03 = completable
|
|
std::vector<QuestRewardItem> requiredItems;
|
|
|
|
bool isCompletable() const { return (completableFlags & 0x03) != 0; }
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_REQUEST_ITEMS parser */
|
|
class QuestRequestItemsParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, QuestRequestItemsData& data);
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_OFFER_REWARD data (choose reward) */
|
|
struct QuestOfferRewardData {
|
|
uint64_t npcGuid = 0;
|
|
uint32_t questId = 0;
|
|
std::string title;
|
|
std::string rewardText;
|
|
uint32_t rewardMoney = 0;
|
|
uint32_t rewardXp = 0;
|
|
std::vector<QuestRewardItem> choiceRewards; // Pick one
|
|
std::vector<QuestRewardItem> fixedRewards; // Always given
|
|
};
|
|
|
|
/** SMSG_QUESTGIVER_OFFER_REWARD parser */
|
|
class QuestOfferRewardParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, QuestOfferRewardData& data);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_COMPLETE_QUEST packet builder */
|
|
class QuestgiverCompleteQuestPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t questId);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_REQUEST_REWARD packet builder */
|
|
class QuestgiverRequestRewardPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t questId);
|
|
};
|
|
|
|
/** CMSG_QUESTGIVER_CHOOSE_REWARD packet builder */
|
|
class QuestgiverChooseRewardPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t questId, uint32_t rewardIndex);
|
|
};
|
|
|
|
// ============================================================
|
|
// Phase 5: Vendor
|
|
// ============================================================
|
|
|
|
/** Vendor item entry */
|
|
struct VendorItem {
|
|
uint32_t slot = 0;
|
|
uint32_t itemId = 0;
|
|
uint32_t displayInfoId = 0;
|
|
int32_t maxCount = -1; // -1 = unlimited
|
|
uint32_t buyPrice = 0; // In copper
|
|
uint32_t durability = 0;
|
|
uint32_t stackCount = 0;
|
|
uint32_t extendedCost = 0;
|
|
};
|
|
|
|
/** SMSG_LIST_INVENTORY data */
|
|
struct ListInventoryData {
|
|
uint64_t vendorGuid = 0;
|
|
std::vector<VendorItem> items;
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/** CMSG_LIST_INVENTORY packet builder */
|
|
class ListInventoryPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid);
|
|
};
|
|
|
|
/** CMSG_BUY_ITEM packet builder */
|
|
class BuyItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count);
|
|
};
|
|
|
|
/** CMSG_SELL_ITEM packet builder */
|
|
class SellItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count);
|
|
};
|
|
|
|
/** CMSG_BUYBACK_ITEM packet builder */
|
|
class BuybackItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t vendorGuid, uint32_t slot);
|
|
};
|
|
|
|
/** SMSG_LIST_INVENTORY parser */
|
|
class ListInventoryParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ListInventoryData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Trainer
|
|
// ============================================================
|
|
|
|
struct TrainerSpell {
|
|
uint32_t spellId = 0;
|
|
uint8_t state = 0; // 0=unavailable(grey), 1=available(green), 2=known(green)
|
|
uint32_t spellCost = 0; // copper
|
|
uint32_t profDialog = 0;
|
|
uint32_t profButton = 0;
|
|
uint8_t reqLevel = 0;
|
|
uint32_t reqSkill = 0;
|
|
uint32_t reqSkillValue = 0;
|
|
uint32_t chainNode1 = 0;
|
|
uint32_t chainNode2 = 0;
|
|
uint32_t chainNode3 = 0;
|
|
};
|
|
|
|
struct TrainerListData {
|
|
uint64_t trainerGuid = 0;
|
|
uint32_t trainerType = 0; // 0=class, 1=mounts, 2=tradeskills, 3=pets
|
|
std::vector<TrainerSpell> spells;
|
|
std::string greeting;
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
class TrainerListParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, TrainerListData& data);
|
|
};
|
|
|
|
class TrainerBuySpellPacket {
|
|
public:
|
|
static network::Packet build(uint64_t trainerGuid, uint32_t spellId);
|
|
};
|
|
|
|
// ============================================================
|
|
// Talents
|
|
// ============================================================
|
|
|
|
/** Talent info for a single talent */
|
|
struct TalentInfo {
|
|
uint32_t talentId = 0; // Talent.dbc ID
|
|
uint8_t currentRank = 0; // 0-5 (0 = not learned)
|
|
};
|
|
|
|
/** SMSG_TALENTS_INFO data */
|
|
struct TalentsInfoData {
|
|
uint8_t talentSpec = 0; // Active spec (0 or 1 for dual-spec)
|
|
uint8_t unspentPoints = 0; // Talent points available
|
|
std::vector<TalentInfo> talents; // Learned talents
|
|
std::vector<uint32_t> glyphs; // Glyph spell IDs
|
|
|
|
bool isValid() const { return true; }
|
|
};
|
|
|
|
/** SMSG_TALENTS_INFO parser */
|
|
class TalentsInfoParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, TalentsInfoData& data);
|
|
};
|
|
|
|
/** CMSG_LEARN_TALENT packet builder */
|
|
class LearnTalentPacket {
|
|
public:
|
|
static network::Packet build(uint32_t talentId, uint32_t requestedRank);
|
|
};
|
|
|
|
/** MSG_TALENT_WIPE_CONFIRM packet builder */
|
|
class TalentWipeConfirmPacket {
|
|
public:
|
|
static network::Packet build(bool accept);
|
|
};
|
|
|
|
// ============================================================
|
|
// Taxi / Flight Paths
|
|
// ============================================================
|
|
|
|
static constexpr uint32_t TLK_TAXI_MASK_SIZE = 12;
|
|
|
|
/** SMSG_SHOWTAXINODES data */
|
|
struct ShowTaxiNodesData {
|
|
uint32_t windowInfo = 0; // 1 = show window
|
|
uint64_t npcGuid = 0;
|
|
uint32_t nearestNode = 0; // Taxi node player is at
|
|
uint32_t nodeMask[TLK_TAXI_MASK_SIZE] = {};
|
|
bool isNodeKnown(uint32_t nodeId) const {
|
|
if (nodeId == 0) return false;
|
|
uint32_t bitIndex = nodeId - 1;
|
|
uint32_t idx = bitIndex / 32;
|
|
uint32_t bit = bitIndex % 32;
|
|
return idx < TLK_TAXI_MASK_SIZE && (nodeMask[idx] & (1u << bit));
|
|
}
|
|
};
|
|
|
|
/** SMSG_SHOWTAXINODES parser */
|
|
class ShowTaxiNodesParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ShowTaxiNodesData& data);
|
|
};
|
|
|
|
/** SMSG_ACTIVATETAXIREPLY data */
|
|
struct ActivateTaxiReplyData {
|
|
uint32_t result = 0; // 0 = OK
|
|
};
|
|
|
|
/** SMSG_ACTIVATETAXIREPLY parser */
|
|
class ActivateTaxiReplyParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ActivateTaxiReplyData& data);
|
|
};
|
|
|
|
/** CMSG_ACTIVATETAXIEXPRESS packet builder */
|
|
class ActivateTaxiExpressPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t totalCost, const std::vector<uint32_t>& pathNodes);
|
|
};
|
|
|
|
/** CMSG_ACTIVATETAXI packet builder */
|
|
class ActivateTaxiPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t srcNode, uint32_t destNode);
|
|
};
|
|
|
|
/** CMSG_GAMEOBJ_USE packet builder */
|
|
class GameObjectUsePacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** CMSG_REPOP_REQUEST packet builder */
|
|
class RepopRequestPacket {
|
|
public:
|
|
static network::Packet build();
|
|
};
|
|
|
|
/** CMSG_SPIRIT_HEALER_ACTIVATE packet builder */
|
|
class SpiritHealerActivatePacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid);
|
|
};
|
|
|
|
/** CMSG_RESURRECT_RESPONSE packet builder */
|
|
class ResurrectResponsePacket {
|
|
public:
|
|
static network::Packet build(uint64_t casterGuid, bool accept);
|
|
};
|
|
|
|
// ============================================================
|
|
// Mail System
|
|
// ============================================================
|
|
|
|
struct MailAttachment {
|
|
uint8_t slot = 0;
|
|
uint32_t itemGuidLow = 0;
|
|
uint32_t itemId = 0;
|
|
uint32_t enchantId = 0;
|
|
uint32_t randomPropertyId = 0;
|
|
uint32_t randomSuffix = 0;
|
|
uint32_t stackCount = 1;
|
|
uint32_t chargesOrDurability = 0;
|
|
uint32_t maxDurability = 0;
|
|
};
|
|
|
|
struct MailMessage {
|
|
uint32_t messageId = 0;
|
|
uint8_t messageType = 0; // 0=normal, 2=auction, 3=creature, 4=gameobject
|
|
uint64_t senderGuid = 0;
|
|
uint32_t senderEntry = 0; // For non-player mail
|
|
std::string senderName;
|
|
std::string subject;
|
|
std::string body;
|
|
uint32_t stationeryId = 0;
|
|
uint32_t money = 0;
|
|
uint32_t cod = 0; // Cash on delivery
|
|
uint32_t flags = 0;
|
|
float expirationTime = 0.0f;
|
|
uint32_t mailTemplateId = 0;
|
|
bool read = false;
|
|
std::vector<MailAttachment> attachments;
|
|
};
|
|
|
|
/** CMSG_GET_MAIL_LIST packet builder */
|
|
class GetMailListPacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid);
|
|
};
|
|
|
|
/** CMSG_SEND_MAIL packet builder */
|
|
class SendMailPacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid, const std::string& recipient,
|
|
const std::string& subject, const std::string& body,
|
|
uint32_t money, uint32_t cod);
|
|
};
|
|
|
|
/** CMSG_MAIL_TAKE_MONEY packet builder */
|
|
class MailTakeMoneyPacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
|
|
};
|
|
|
|
/** CMSG_MAIL_TAKE_ITEM packet builder */
|
|
class MailTakeItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemIndex);
|
|
};
|
|
|
|
/** CMSG_MAIL_DELETE packet builder */
|
|
class MailDeletePacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId, uint32_t mailTemplateId);
|
|
};
|
|
|
|
/** CMSG_MAIL_MARK_AS_READ packet builder */
|
|
class MailMarkAsReadPacket {
|
|
public:
|
|
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
|
|
};
|
|
|
|
// ============================================================
|
|
// Bank System
|
|
// ============================================================
|
|
|
|
/** CMSG_BANKER_ACTIVATE packet builder */
|
|
class BankerActivatePacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** CMSG_BUY_BANK_SLOT packet builder */
|
|
class BuyBankSlotPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** CMSG_AUTOBANK_ITEM packet builder (deposit item to bank) */
|
|
class AutoBankItemPacket {
|
|
public:
|
|
static network::Packet build(uint8_t srcBag, uint8_t srcSlot);
|
|
};
|
|
|
|
/** CMSG_AUTOSTORE_BANK_ITEM packet builder (withdraw item from bank) */
|
|
class AutoStoreBankItemPacket {
|
|
public:
|
|
static network::Packet build(uint8_t srcBag, uint8_t srcSlot);
|
|
};
|
|
|
|
// ============================================================
|
|
// Guild Bank System
|
|
// ============================================================
|
|
|
|
struct GuildBankItemSlot {
|
|
uint8_t slotId = 0;
|
|
uint32_t itemEntry = 0;
|
|
uint32_t stackCount = 1;
|
|
uint32_t enchantId = 0;
|
|
uint32_t randomPropertyId = 0;
|
|
};
|
|
|
|
struct GuildBankTab {
|
|
std::string tabName;
|
|
std::string tabIcon;
|
|
std::vector<GuildBankItemSlot> items;
|
|
};
|
|
|
|
struct GuildBankData {
|
|
uint64_t money = 0;
|
|
uint8_t tabId = 0;
|
|
int32_t withdrawAmount = -1; // -1 = unlimited
|
|
std::vector<GuildBankTab> tabs; // Only populated on fullUpdate
|
|
std::vector<GuildBankItemSlot> tabItems; // Current tab items
|
|
};
|
|
|
|
/** CMSG_GUILD_BANKER_ACTIVATE packet builder */
|
|
class GuildBankerActivatePacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** CMSG_GUILD_BANK_QUERY_TAB packet builder */
|
|
class GuildBankQueryTabPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid, uint8_t tabId, bool fullUpdate);
|
|
};
|
|
|
|
/** CMSG_GUILD_BANK_BUY_TAB packet builder */
|
|
class GuildBankBuyTabPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid, uint8_t tabId);
|
|
};
|
|
|
|
/** CMSG_GUILD_BANK_DEPOSIT_MONEY packet builder */
|
|
class GuildBankDepositMoneyPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid, uint32_t amount);
|
|
};
|
|
|
|
/** CMSG_GUILD_BANK_WITHDRAW_MONEY packet builder */
|
|
class GuildBankWithdrawMoneyPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid, uint32_t amount);
|
|
};
|
|
|
|
/** CMSG_GUILD_BANK_SWAP_ITEMS packet builder */
|
|
class GuildBankSwapItemsPacket {
|
|
public:
|
|
// Bank to inventory
|
|
static network::Packet buildBankToInventory(uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
|
uint8_t destBag, uint8_t destSlot, uint32_t splitCount = 0);
|
|
// Inventory to bank
|
|
static network::Packet buildInventoryToBank(uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
|
uint8_t srcBag, uint8_t srcSlot, uint32_t splitCount = 0);
|
|
};
|
|
|
|
/** SMSG_GUILD_BANK_LIST parser */
|
|
class GuildBankListParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GuildBankData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// Auction House System
|
|
// ============================================================
|
|
|
|
struct AuctionEntry {
|
|
uint32_t auctionId = 0;
|
|
uint32_t itemEntry = 0;
|
|
uint32_t stackCount = 1;
|
|
uint32_t enchantId = 0;
|
|
uint32_t randomPropertyId = 0;
|
|
uint32_t suffixFactor = 0;
|
|
uint64_t ownerGuid = 0;
|
|
uint32_t startBid = 0;
|
|
uint32_t minBidIncrement = 0;
|
|
uint32_t buyoutPrice = 0;
|
|
uint32_t timeLeftMs = 0;
|
|
uint64_t bidderGuid = 0;
|
|
uint32_t currentBid = 0;
|
|
};
|
|
|
|
struct AuctionListResult {
|
|
std::vector<AuctionEntry> auctions;
|
|
uint32_t totalCount = 0;
|
|
uint32_t searchDelay = 0;
|
|
};
|
|
|
|
struct AuctionCommandResult {
|
|
uint32_t auctionId = 0;
|
|
uint32_t action = 0; // 0=create, 1=cancel, 2=bid, 3=buyout
|
|
uint32_t errorCode = 0; // 0=success
|
|
uint32_t bidError = 0; // secondary error for bid actions
|
|
};
|
|
|
|
struct AuctionHelloData {
|
|
uint64_t auctioneerGuid = 0;
|
|
uint32_t auctionHouseId = 0;
|
|
uint8_t enabled = 1;
|
|
};
|
|
|
|
/** MSG_AUCTION_HELLO packet builder */
|
|
class AuctionHelloPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid);
|
|
};
|
|
|
|
/** MSG_AUCTION_HELLO parser (server response) */
|
|
class AuctionHelloParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuctionHelloData& data);
|
|
};
|
|
|
|
/** CMSG_AUCTION_LIST_ITEMS packet builder */
|
|
class AuctionListItemsPacket {
|
|
public:
|
|
static network::Packet build(uint64_t guid, uint32_t offset,
|
|
const std::string& searchName,
|
|
uint8_t levelMin, uint8_t levelMax,
|
|
uint32_t invTypeMask, uint32_t itemClass,
|
|
uint32_t itemSubClass, uint32_t quality,
|
|
uint8_t usableOnly, uint8_t exactMatch);
|
|
};
|
|
|
|
/** CMSG_AUCTION_SELL_ITEM packet builder */
|
|
class AuctionSellItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t auctioneerGuid, uint64_t itemGuid,
|
|
uint32_t stackCount, uint32_t bid,
|
|
uint32_t buyout, uint32_t duration);
|
|
};
|
|
|
|
/** CMSG_AUCTION_PLACE_BID packet builder */
|
|
class AuctionPlaceBidPacket {
|
|
public:
|
|
static network::Packet build(uint64_t auctioneerGuid, uint32_t auctionId, uint32_t amount);
|
|
};
|
|
|
|
/** CMSG_AUCTION_REMOVE_ITEM packet builder */
|
|
class AuctionRemoveItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t auctioneerGuid, uint32_t auctionId);
|
|
};
|
|
|
|
/** CMSG_AUCTION_LIST_OWNER_ITEMS packet builder */
|
|
class AuctionListOwnerItemsPacket {
|
|
public:
|
|
static network::Packet build(uint64_t auctioneerGuid, uint32_t offset);
|
|
};
|
|
|
|
/** CMSG_AUCTION_LIST_BIDDER_ITEMS packet builder */
|
|
class AuctionListBidderItemsPacket {
|
|
public:
|
|
static network::Packet build(uint64_t auctioneerGuid, uint32_t offset,
|
|
const std::vector<uint32_t>& outbiddedIds = {});
|
|
};
|
|
|
|
/** SMSG_AUCTION_LIST_RESULT parser (shared for browse/owner/bidder) */
|
|
class AuctionListResultParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuctionListResult& data);
|
|
};
|
|
|
|
/** SMSG_AUCTION_COMMAND_RESULT parser */
|
|
class AuctionCommandResultParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, AuctionCommandResult& data);
|
|
};
|
|
|
|
} // namespace game
|
|
} // namespace wowee
|