mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Fix movement packets to include packed player GUID prefix so the server tracks position. Fix inventory money display being clipped by child panels. Add Back and Delete Character buttons to character selection screen with two-step delete confirmation.
1222 lines
32 KiB
C++
1222 lines
32 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>
|
|
|
|
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 unknown1 != 0; }
|
|
};
|
|
|
|
/**
|
|
* 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)
|
|
enum class CharCreateResult : uint8_t {
|
|
// Success codes
|
|
SUCCESS = 0x2F, // CHAR_CREATE_SUCCESS
|
|
|
|
// CHAR_CREATE error codes
|
|
IN_PROGRESS = 0x2E, // CHAR_CREATE_IN_PROGRESS
|
|
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
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
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
|
|
|
|
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:
|
|
/**
|
|
* Build a movement packet
|
|
*
|
|
* @param opcode Movement opcode (CMSG_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;
|
|
|
|
// 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);
|
|
|
|
private:
|
|
/**
|
|
* 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.)
|
|
|
|
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);
|
|
|
|
// ============================================================
|
|
// 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_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);
|
|
};
|
|
|
|
// ============================================================
|
|
// 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 displayInfoId = 0;
|
|
uint32_t quality = 0;
|
|
uint32_t inventoryType = 0;
|
|
int32_t maxStack = 1;
|
|
uint32_t containerSlots = 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;
|
|
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
|
|
// ============================================================
|
|
|
|
/** 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();
|
|
};
|
|
|
|
/** 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_CAST packet builder */
|
|
class CancelCastPacket {
|
|
public:
|
|
static network::Packet build(uint32_t spellId);
|
|
};
|
|
|
|
/** 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();
|
|
};
|
|
|
|
/** CMSG_GROUP_DISBAND (leave party) packet builder */
|
|
class GroupDisbandPacket {
|
|
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;
|
|
};
|
|
|
|
/** 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_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_GOSSIP_SELECT_OPTION packet builder */
|
|
class GossipSelectOptionPacket {
|
|
public:
|
|
static network::Packet build(uint64_t npcGuid, uint32_t optionId, const std::string& code = "");
|
|
};
|
|
|
|
/** SMSG_GOSSIP_MESSAGE parser */
|
|
class GossipMessageParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, GossipMessageData& data);
|
|
};
|
|
|
|
// ============================================================
|
|
// 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, uint8_t count);
|
|
};
|
|
|
|
/** CMSG_SELL_ITEM packet builder */
|
|
class SellItemPacket {
|
|
public:
|
|
static network::Packet build(uint64_t vendorGuid, uint64_t itemGuid, uint8_t count);
|
|
};
|
|
|
|
/** SMSG_LIST_INVENTORY parser */
|
|
class ListInventoryParser {
|
|
public:
|
|
static bool parse(network::Packet& packet, ListInventoryData& data);
|
|
};
|
|
|
|
} // namespace game
|
|
} // namespace wowee
|