mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
Add multi-expansion support with data-driven protocol layer
Replace hardcoded WotLK protocol constants with a data-driven architecture supporting Classic 1.12.1, TBC 2.4.3, and WotLK 3.3.5a. Each expansion has JSON profiles for opcodes, update fields, and DBC layouts, plus C++ polymorphic packet parsers for binary format differences (movement flags, speed fields, transport data, spline format, char enum layout). Key components: - ExpansionRegistry: scans Data/expansions/*/expansion.json at startup - OpcodeTable: logical enum <-> wire values loaded from JSON - UpdateFieldTable: field indices loaded from JSON per expansion - DBCLayout: schema-driven DBC field lookups replacing magic numbers - PacketParsers: WotLK/TBC/Classic parsers with correct flag positions - Multi-manifest AssetManager: layered manifests with priority ordering - HDPackManager: overlay texture packs with expansion compatibility - Auth screen expansion picker replacing hardcoded version dropdown
This commit is contained in:
parent
aa16a687c2
commit
7092844b5e
51 changed files with 5258 additions and 887 deletions
185
src/game/expansion_profile.cpp
Normal file
185
src/game/expansion_profile.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "game/expansion_profile.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
// Minimal JSON parsing (no external dependency) — expansion.json is tiny and flat.
|
||||
// We parse the subset we need: strings, integers, arrays of integers.
|
||||
namespace {
|
||||
|
||||
std::string trim(const std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\r\n\"");
|
||||
size_t end = s.find_last_not_of(" \t\r\n\",");
|
||||
if (start == std::string::npos) return "";
|
||||
return s.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
// Quick-and-dirty JSON value extractor for flat objects.
|
||||
// Returns the raw value string for a given key, or empty.
|
||||
std::string jsonValue(const std::string& json, const std::string& key) {
|
||||
std::string needle = "\"" + key + "\"";
|
||||
auto pos = json.find(needle);
|
||||
if (pos == std::string::npos) return "";
|
||||
pos = json.find(':', pos + needle.size());
|
||||
if (pos == std::string::npos) return "";
|
||||
++pos;
|
||||
// Skip whitespace
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\r' || json[pos] == '\n'))
|
||||
++pos;
|
||||
if (pos >= json.size()) return "";
|
||||
|
||||
if (json[pos] == '"') {
|
||||
// String value
|
||||
size_t end = json.find('"', pos + 1);
|
||||
return (end != std::string::npos) ? json.substr(pos + 1, end - pos - 1) : "";
|
||||
}
|
||||
if (json[pos] == '{') {
|
||||
// Nested object — return content between braces
|
||||
size_t depth = 1;
|
||||
size_t start = pos + 1;
|
||||
for (size_t i = start; i < json.size() && depth > 0; ++i) {
|
||||
if (json[i] == '{') ++depth;
|
||||
else if (json[i] == '}') { --depth; if (depth == 0) return json.substr(start, i - start); }
|
||||
}
|
||||
return "";
|
||||
}
|
||||
if (json[pos] == '[') {
|
||||
// Array — return content between brackets (including brackets)
|
||||
size_t end = json.find(']', pos);
|
||||
return (end != std::string::npos) ? json.substr(pos, end - pos + 1) : "";
|
||||
}
|
||||
// Number or other literal
|
||||
size_t end = json.find_first_of(",}\n\r", pos);
|
||||
return trim(json.substr(pos, end - pos));
|
||||
}
|
||||
|
||||
int jsonInt(const std::string& json, const std::string& key, int def = 0) {
|
||||
std::string v = jsonValue(json, key);
|
||||
if (v.empty()) return def;
|
||||
try { return std::stoi(v); } catch (...) { return def; }
|
||||
}
|
||||
|
||||
std::vector<uint32_t> jsonUintArray(const std::string& json, const std::string& key) {
|
||||
std::vector<uint32_t> result;
|
||||
std::string arr = jsonValue(json, key);
|
||||
if (arr.empty() || arr.front() != '[') return result;
|
||||
// Strip brackets
|
||||
arr = arr.substr(1, arr.size() - 2);
|
||||
std::istringstream ss(arr);
|
||||
std::string tok;
|
||||
while (std::getline(ss, tok, ',')) {
|
||||
std::string t = trim(tok);
|
||||
if (!t.empty()) {
|
||||
try { result.push_back(static_cast<uint32_t>(std::stoul(t))); } catch (...) {}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
||||
std::string ExpansionProfile::versionString() const {
|
||||
std::ostringstream ss;
|
||||
ss << (int)majorVersion << "." << (int)minorVersion << "." << (int)patchVersion;
|
||||
// Append letter suffix for known builds
|
||||
if (majorVersion == 3 && minorVersion == 3 && patchVersion == 5) ss << "a";
|
||||
else if (majorVersion == 2 && minorVersion == 4 && patchVersion == 3) ss << "";
|
||||
else if (majorVersion == 1 && minorVersion == 12 && patchVersion == 1) ss << "";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
size_t ExpansionRegistry::initialize(const std::string& dataRoot) {
|
||||
profiles_.clear();
|
||||
activeId_.clear();
|
||||
|
||||
std::string expansionsDir = dataRoot + "/expansions";
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::is_directory(expansionsDir, ec)) {
|
||||
LOG_WARNING("ExpansionRegistry: no expansions/ directory at ", expansionsDir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (auto& entry : std::filesystem::directory_iterator(expansionsDir, ec)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
std::string jsonPath = entry.path().string() + "/expansion.json";
|
||||
if (std::filesystem::exists(jsonPath, ec)) {
|
||||
loadProfile(jsonPath, entry.path().string());
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by build number (ascending: classic < tbc < wotlk < cata)
|
||||
std::sort(profiles_.begin(), profiles_.end(),
|
||||
[](const ExpansionProfile& a, const ExpansionProfile& b) { return a.build < b.build; });
|
||||
|
||||
// Default to WotLK if available, otherwise the last (highest build)
|
||||
if (!profiles_.empty()) {
|
||||
auto it = std::find_if(profiles_.begin(), profiles_.end(),
|
||||
[](const ExpansionProfile& p) { return p.id == "wotlk"; });
|
||||
activeId_ = (it != profiles_.end()) ? it->id : profiles_.back().id;
|
||||
}
|
||||
|
||||
LOG_INFO("ExpansionRegistry: discovered ", profiles_.size(), " expansion(s), active=", activeId_);
|
||||
return profiles_.size();
|
||||
}
|
||||
|
||||
const ExpansionProfile* ExpansionRegistry::getProfile(const std::string& id) const {
|
||||
for (auto& p : profiles_) {
|
||||
if (p.id == id) return &p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ExpansionRegistry::setActive(const std::string& id) {
|
||||
if (!getProfile(id)) return false;
|
||||
activeId_ = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
const ExpansionProfile* ExpansionRegistry::getActive() const {
|
||||
return getProfile(activeId_);
|
||||
}
|
||||
|
||||
bool ExpansionRegistry::loadProfile(const std::string& jsonPath, const std::string& dirPath) {
|
||||
std::ifstream f(jsonPath);
|
||||
if (!f.is_open()) return false;
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
ExpansionProfile p;
|
||||
p.id = jsonValue(json, "id");
|
||||
p.name = jsonValue(json, "name");
|
||||
p.shortName = jsonValue(json, "shortName");
|
||||
p.dataPath = dirPath;
|
||||
|
||||
// Version nested object
|
||||
std::string ver = jsonValue(json, "version");
|
||||
if (!ver.empty()) {
|
||||
p.majorVersion = static_cast<uint8_t>(jsonInt(ver, "major"));
|
||||
p.minorVersion = static_cast<uint8_t>(jsonInt(ver, "minor"));
|
||||
p.patchVersion = static_cast<uint8_t>(jsonInt(ver, "patch"));
|
||||
}
|
||||
|
||||
p.build = static_cast<uint16_t>(jsonInt(json, "build"));
|
||||
p.protocolVersion = static_cast<uint8_t>(jsonInt(json, "protocolVersion"));
|
||||
p.maxLevel = static_cast<uint32_t>(jsonInt(json, "maxLevel", 60));
|
||||
p.races = jsonUintArray(json, "races");
|
||||
p.classes = jsonUintArray(json, "classes");
|
||||
|
||||
if (p.id.empty() || p.build == 0) {
|
||||
LOG_WARNING("ExpansionRegistry: skipping invalid profile at ", jsonPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("ExpansionRegistry: loaded '", p.name, "' (", p.shortName,
|
||||
") v", p.versionString(), " build=", p.build);
|
||||
profiles_.push_back(std::move(p));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
#include "game/game_handler.hpp"
|
||||
#include "game/packet_parsers.hpp"
|
||||
#include "game/transport_manager.hpp"
|
||||
#include "game/warden_crypto.hpp"
|
||||
#include "game/warden_module.hpp"
|
||||
#include "game/opcodes.hpp"
|
||||
#include "game/update_field_table.hpp"
|
||||
#include "pipeline/dbc_layout.hpp"
|
||||
#include "network/world_socket.hpp"
|
||||
#include "network/packet.hpp"
|
||||
#include "auth/crypto.hpp"
|
||||
|
|
@ -50,16 +53,16 @@ const char* worldStateName(WorldState state) {
|
|||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
bool isAuthCharPipelineOpcode(uint16_t opcode) {
|
||||
switch (opcode) {
|
||||
case static_cast<uint16_t>(Opcode::SMSG_AUTH_CHALLENGE):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_AUTH_RESPONSE):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_CLIENTCACHE_VERSION):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_TUTORIAL_FLAGS):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_WARDEN_DATA):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_CHAR_ENUM):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_CHAR_CREATE):
|
||||
case static_cast<uint16_t>(Opcode::SMSG_CHAR_DELETE):
|
||||
bool isAuthCharPipelineOpcode(LogicalOpcode op) {
|
||||
switch (op) {
|
||||
case Opcode::SMSG_AUTH_CHALLENGE:
|
||||
case Opcode::SMSG_AUTH_RESPONSE:
|
||||
case Opcode::SMSG_CLIENTCACHE_VERSION:
|
||||
case Opcode::SMSG_TUTORIAL_FLAGS:
|
||||
case Opcode::SMSG_WARDEN_DATA:
|
||||
case Opcode::SMSG_CHAR_ENUM:
|
||||
case Opcode::SMSG_CHAR_CREATE:
|
||||
case Opcode::SMSG_CHAR_DELETE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
@ -71,6 +74,17 @@ bool isAuthCharPipelineOpcode(uint16_t opcode) {
|
|||
GameHandler::GameHandler() {
|
||||
LOG_DEBUG("GameHandler created");
|
||||
|
||||
// Initialize opcode table with WotLK defaults (may be overridden from JSON later)
|
||||
opcodeTable_.loadWotlkDefaults();
|
||||
setActiveOpcodeTable(&opcodeTable_);
|
||||
|
||||
// Initialize update field table with WotLK defaults (may be overridden from JSON later)
|
||||
updateFieldTable_.loadWotlkDefaults();
|
||||
setActiveUpdateFieldTable(&updateFieldTable_);
|
||||
|
||||
// Initialize packet parsers (WotLK default, may be replaced for other expansions)
|
||||
packetParsers_ = std::make_unique<WotlkPacketParsers>();
|
||||
|
||||
// Initialize transport manager
|
||||
transportManager_ = std::make_unique<TransportManager>();
|
||||
|
||||
|
|
@ -92,6 +106,10 @@ GameHandler::~GameHandler() {
|
|||
disconnect();
|
||||
}
|
||||
|
||||
void GameHandler::setPacketParsers(std::unique_ptr<PacketParsers> parsers) {
|
||||
packetParsers_ = std::move(parsers);
|
||||
}
|
||||
|
||||
bool GameHandler::connect(const std::string& host,
|
||||
uint16_t port,
|
||||
const std::vector<uint8_t>& sessionKey,
|
||||
|
|
@ -542,10 +560,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
|
||||
uint16_t opcode = packet.getOpcode();
|
||||
if (wardenGateSeen_ && opcode != static_cast<uint16_t>(Opcode::SMSG_WARDEN_DATA)) {
|
||||
auto preLogicalOp = opcodeTable_.fromWire(opcode);
|
||||
if (wardenGateSeen_ && (!preLogicalOp || *preLogicalOp != Opcode::SMSG_WARDEN_DATA)) {
|
||||
++wardenPacketsAfterGate_;
|
||||
}
|
||||
if (isAuthCharPipelineOpcode(opcode)) {
|
||||
if (preLogicalOp && isAuthCharPipelineOpcode(*preLogicalOp)) {
|
||||
LOG_INFO("AUTH/CHAR RX opcode=0x", std::hex, opcode, std::dec,
|
||||
" state=", worldStateName(state),
|
||||
" size=", packet.getSize());
|
||||
|
|
@ -554,10 +573,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
LOG_DEBUG("Received world packet: opcode=0x", std::hex, opcode, std::dec,
|
||||
" size=", packet.getSize(), " bytes");
|
||||
|
||||
// Route packet based on opcode
|
||||
Opcode opcodeEnum = static_cast<Opcode>(opcode);
|
||||
// Translate wire opcode to logical opcode via expansion table
|
||||
auto logicalOp = opcodeTable_.fromWire(opcode);
|
||||
if (!logicalOp) {
|
||||
LOG_DEBUG("Unknown wire opcode 0x", std::hex, opcode, std::dec, " - ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (opcodeEnum) {
|
||||
switch (*logicalOp) {
|
||||
case Opcode::SMSG_AUTH_CHALLENGE:
|
||||
if (state == WorldState::CONNECTED) {
|
||||
handleAuthChallenge(packet);
|
||||
|
|
@ -1132,7 +1155,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
if (entity->getType() != ObjectType::UNIT) continue;
|
||||
auto unit = std::static_pointer_cast<Unit>(entity);
|
||||
if (unit->getNpcFlags() & 0x02) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(guid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
|
|
@ -1629,7 +1652,7 @@ void GameHandler::deleteCharacter(uint64_t characterGuid) {
|
|||
return;
|
||||
}
|
||||
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CHAR_DELETE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_DELETE));
|
||||
packet.writeUInt64(characterGuid);
|
||||
socket->send(packet);
|
||||
LOG_INFO("CMSG_CHAR_DELETE sent for GUID: 0x", std::hex, characterGuid, std::dec);
|
||||
|
|
@ -1938,7 +1961,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
std::vector<uint8_t> encryptedResponse = wardenCrypto_->encrypt(hashResponse);
|
||||
|
||||
// Send HASH_RESULT response
|
||||
network::Packet response(static_cast<uint16_t>(Opcode::CMSG_WARDEN_DATA));
|
||||
network::Packet response(wireOpcode(Opcode::CMSG_WARDEN_DATA));
|
||||
for (uint8_t byte : encryptedResponse) {
|
||||
response.writeUInt8(byte);
|
||||
}
|
||||
|
|
@ -2151,7 +2174,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
LOG_INFO("Warden: Response encrypted (", encrypted.size(), " bytes): ", respEncHex);
|
||||
|
||||
// Build and send response packet
|
||||
network::Packet response(static_cast<uint16_t>(Opcode::CMSG_WARDEN_DATA));
|
||||
network::Packet response(wireOpcode(Opcode::CMSG_WARDEN_DATA));
|
||||
for (uint8_t byte : encrypted) {
|
||||
response.writeUInt8(byte);
|
||||
}
|
||||
|
|
@ -2344,7 +2367,7 @@ void GameHandler::sendMovement(Opcode opcode) {
|
|||
}
|
||||
|
||||
LOG_DEBUG("Sending movement packet: opcode=0x", std::hex,
|
||||
static_cast<uint16_t>(opcode), std::dec,
|
||||
wireOpcode(opcode), std::dec,
|
||||
(isOnTransport() ? " ONTRANSPORT" : ""));
|
||||
|
||||
// Convert canonical → server coordinates for the wire
|
||||
|
|
@ -2584,9 +2607,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
if (block.objectType == ObjectType::PLAYER) {
|
||||
queryPlayerName(block.guid);
|
||||
} else if (block.objectType == ObjectType::UNIT) {
|
||||
// Extract creature entry from fields (UNIT_FIELD_ENTRY = index 54 in 3.3.5a,
|
||||
// but the OBJECT_FIELD_ENTRY is at index 3)
|
||||
auto it = block.fields.find(3); // OBJECT_FIELD_ENTRY
|
||||
auto it = block.fields.find(fieldIndex(UF::OBJECT_FIELD_ENTRY));
|
||||
if (it != block.fields.end() && it->second != 0) {
|
||||
auto unit = std::static_pointer_cast<Unit>(entity);
|
||||
unit->setEntry(it->second);
|
||||
|
|
@ -2603,39 +2624,44 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
if (block.objectType == ObjectType::UNIT || block.objectType == ObjectType::PLAYER) {
|
||||
auto unit = std::static_pointer_cast<Unit>(entity);
|
||||
constexpr uint32_t UNIT_DYNFLAG_DEAD = 0x0008;
|
||||
const uint16_t ufHealth = fieldIndex(UF::UNIT_FIELD_HEALTH);
|
||||
const uint16_t ufPower = fieldIndex(UF::UNIT_FIELD_POWER1);
|
||||
const uint16_t ufMaxHealth = fieldIndex(UF::UNIT_FIELD_MAXHEALTH);
|
||||
const uint16_t ufMaxPower = fieldIndex(UF::UNIT_FIELD_MAXPOWER1);
|
||||
const uint16_t ufLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
|
||||
const uint16_t ufFaction = fieldIndex(UF::UNIT_FIELD_FACTIONTEMPLATE);
|
||||
const uint16_t ufFlags = fieldIndex(UF::UNIT_FIELD_FLAGS);
|
||||
const uint16_t ufDynFlags = fieldIndex(UF::UNIT_DYNAMIC_FLAGS);
|
||||
const uint16_t ufDisplayId = fieldIndex(UF::UNIT_FIELD_DISPLAYID);
|
||||
const uint16_t ufMountDisplayId = fieldIndex(UF::UNIT_FIELD_MOUNTDISPLAYID);
|
||||
const uint16_t ufNpcFlags = fieldIndex(UF::UNIT_NPC_FLAGS);
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
switch (key) {
|
||||
case 24:
|
||||
unit->setHealth(val);
|
||||
// Detect dead player on login
|
||||
if (block.guid == playerGuid && val == 0) {
|
||||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in dead");
|
||||
if (key == ufHealth) {
|
||||
unit->setHealth(val);
|
||||
if (block.guid == playerGuid && val == 0) {
|
||||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in dead");
|
||||
}
|
||||
} else if (key == ufPower) { unit->setPower(val); }
|
||||
else if (key == ufMaxHealth) { unit->setMaxHealth(val); }
|
||||
else if (key == ufMaxPower) { unit->setMaxPower(val); }
|
||||
else if (key == ufFaction) { unit->setFactionTemplate(val); }
|
||||
else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
else if (key == ufDynFlags) { unit->setDynamicFlags(val); }
|
||||
else if (key == ufLevel) { unit->setLevel(val); }
|
||||
else if (key == ufDisplayId) { unit->setDisplayId(val); }
|
||||
else if (key == ufMountDisplayId) {
|
||||
if (block.guid == playerGuid) {
|
||||
uint32_t old = currentMountDisplayId_;
|
||||
currentMountDisplayId_ = val;
|
||||
if (val != old && mountCallback_) mountCallback_(val);
|
||||
if (old != 0 && val == 0) {
|
||||
for (auto& a : playerAuras)
|
||||
if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{};
|
||||
}
|
||||
break;
|
||||
case 25: unit->setPower(val); break;
|
||||
case 32: unit->setMaxHealth(val); break;
|
||||
case 33: unit->setMaxPower(val); break;
|
||||
case 55: unit->setFactionTemplate(val); break; // UNIT_FIELD_FACTIONTEMPLATE
|
||||
case 59: unit->setUnitFlags(val); break; // UNIT_FIELD_FLAGS
|
||||
case 147: unit->setDynamicFlags(val); break; // UNIT_DYNAMIC_FLAGS
|
||||
case 54: unit->setLevel(val); break;
|
||||
case 67: unit->setDisplayId(val); break; // UNIT_FIELD_DISPLAYID
|
||||
case 69: // UNIT_FIELD_MOUNTDISPLAYID
|
||||
if (block.guid == playerGuid) {
|
||||
uint32_t old = currentMountDisplayId_;
|
||||
currentMountDisplayId_ = val;
|
||||
if (val != old && mountCallback_) mountCallback_(val);
|
||||
if (old != 0 && val == 0) {
|
||||
for (auto& a : playerAuras)
|
||||
if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{};
|
||||
}
|
||||
}
|
||||
unit->setMountDisplayId(val);
|
||||
break;
|
||||
case 82: unit->setNpcFlags(val); break; // UNIT_NPC_FLAGS
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
unit->setMountDisplayId(val);
|
||||
} else if (key == ufNpcFlags) { unit->setNpcFlags(val); }
|
||||
}
|
||||
if (block.guid == playerGuid) {
|
||||
constexpr uint32_t UNIT_FLAG_TAXI_FLIGHT = 0x00000100;
|
||||
|
|
@ -2651,11 +2677,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in dead (dynamic flags)");
|
||||
}
|
||||
// Detect ghost state on login via PLAYER_FLAGS (field 150)
|
||||
// Detect ghost state on login via PLAYER_FLAGS
|
||||
if (block.guid == playerGuid) {
|
||||
constexpr uint32_t PLAYER_FLAGS_IDX = 150; // UNIT_END(148) + 2
|
||||
constexpr uint32_t PLAYER_FLAGS_GHOST = 0x00000010;
|
||||
auto pfIt = block.fields.find(PLAYER_FLAGS_IDX);
|
||||
auto pfIt = block.fields.find(fieldIndex(UF::PLAYER_FLAGS));
|
||||
if (pfIt != block.fields.end() && (pfIt->second & PLAYER_FLAGS_GHOST) != 0) {
|
||||
releasedSpirit_ = true;
|
||||
playerDead_ = true;
|
||||
|
|
@ -2674,7 +2699,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
// Query quest giver status for NPCs with questgiver flag (0x02)
|
||||
if ((unit->getNpcFlags() & 0x02) && socket) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(block.guid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
|
|
@ -2683,12 +2708,11 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
// Extract displayId and entry for gameobjects (3.3.5a: GAMEOBJECT_DISPLAYID = field 8)
|
||||
if (block.objectType == ObjectType::GAMEOBJECT) {
|
||||
auto go = std::static_pointer_cast<GameObject>(entity);
|
||||
auto itDisp = block.fields.find(8);
|
||||
auto itDisp = block.fields.find(fieldIndex(UF::GAMEOBJECT_DISPLAYID));
|
||||
if (itDisp != block.fields.end()) {
|
||||
go->setDisplayId(itDisp->second);
|
||||
}
|
||||
// Extract entry and query name (OBJECT_FIELD_ENTRY = index 3)
|
||||
auto itEntry = block.fields.find(3);
|
||||
auto itEntry = block.fields.find(fieldIndex(UF::OBJECT_FIELD_ENTRY));
|
||||
if (itEntry != block.fields.end() && itEntry->second != 0) {
|
||||
go->setEntry(itEntry->second);
|
||||
auto cacheIt = gameObjectInfoCache_.find(itEntry->second);
|
||||
|
|
@ -2719,8 +2743,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
// Track online item objects
|
||||
if (block.objectType == ObjectType::ITEM) {
|
||||
auto entryIt = block.fields.find(3); // OBJECT_FIELD_ENTRY
|
||||
auto stackIt = block.fields.find(14); // ITEM_FIELD_STACK_COUNT
|
||||
auto entryIt = block.fields.find(fieldIndex(UF::OBJECT_FIELD_ENTRY));
|
||||
auto stackIt = block.fields.find(fieldIndex(UF::ITEM_FIELD_STACK_COUNT));
|
||||
if (entryIt != block.fields.end() && entryIt->second != 0) {
|
||||
OnlineItemInfo info;
|
||||
info.entry = entryIt->second;
|
||||
|
|
@ -2816,22 +2840,27 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
LOG_INFO(" Highest field index: ", maxField);
|
||||
|
||||
bool slotsChanged = false;
|
||||
const uint16_t ufPlayerXp = fieldIndex(UF::PLAYER_XP);
|
||||
const uint16_t ufPlayerNextXp = fieldIndex(UF::PLAYER_NEXT_LEVEL_XP);
|
||||
const uint16_t ufPlayerLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
|
||||
const uint16_t ufCoinage = fieldIndex(UF::PLAYER_FIELD_COINAGE);
|
||||
const uint16_t ufQuestStart = fieldIndex(UF::PLAYER_QUEST_LOG_START);
|
||||
const uint16_t ufQuestEnd = ufQuestStart + 25 * 5; // 25 quest slots, stride 5
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
if (key == 634) { playerXp_ = val; } // PLAYER_XP
|
||||
else if (key == 635) { playerNextLevelXp_ = val; } // PLAYER_NEXT_LEVEL_XP
|
||||
else if (key == 54) {
|
||||
serverPlayerLevel_ = val; // UNIT_FIELD_LEVEL
|
||||
if (key == ufPlayerXp) { playerXp_ = val; }
|
||||
else if (key == ufPlayerNextXp) { playerNextLevelXp_ = val; }
|
||||
else if (key == ufPlayerLevel) {
|
||||
serverPlayerLevel_ = val;
|
||||
for (auto& ch : characters) {
|
||||
if (ch.guid == playerGuid) { ch.level = val; break; }
|
||||
}
|
||||
}
|
||||
else if (key == 1170) {
|
||||
else if (key == ufCoinage) {
|
||||
playerMoneyCopper_ = val;
|
||||
LOG_INFO("Money set from update fields: ", val, " copper");
|
||||
} // PLAYER_FIELD_COINAGE
|
||||
// Parse quest log fields (PLAYER_QUEST_LOG_1_1 = UNIT_END + 10 = 158, stride 5)
|
||||
// Quest slots: 158, 163, 168, 173, ... (25 slots max = up to index 278)
|
||||
else if (key >= 158 && key < 283 && (key - 158) % 5 == 0) {
|
||||
}
|
||||
// Parse quest log fields (stride 5, 25 slots)
|
||||
else if (key >= ufQuestStart && key < ufQuestEnd && (key - ufQuestStart) % 5 == 0) {
|
||||
uint32_t questId = val;
|
||||
if (questId != 0) {
|
||||
// Check if quest is already in log
|
||||
|
|
@ -2853,7 +2882,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
|
||||
// Request quest details from server
|
||||
if (socket) {
|
||||
network::Packet qPkt(static_cast<uint16_t>(Opcode::CMSG_QUEST_QUERY));
|
||||
network::Packet qPkt(wireOpcode(Opcode::CMSG_QUEST_QUERY));
|
||||
qPkt.writeUInt32(questId);
|
||||
socket->send(qPkt);
|
||||
}
|
||||
|
|
@ -2907,92 +2936,89 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
constexpr uint32_t UNIT_DYNFLAG_DEAD = 0x0008;
|
||||
uint32_t oldDisplayId = unit->getDisplayId();
|
||||
bool displayIdChanged = false;
|
||||
const uint16_t ufHealth = fieldIndex(UF::UNIT_FIELD_HEALTH);
|
||||
const uint16_t ufPower = fieldIndex(UF::UNIT_FIELD_POWER1);
|
||||
const uint16_t ufMaxHealth = fieldIndex(UF::UNIT_FIELD_MAXHEALTH);
|
||||
const uint16_t ufMaxPower = fieldIndex(UF::UNIT_FIELD_MAXPOWER1);
|
||||
const uint16_t ufLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
|
||||
const uint16_t ufFaction = fieldIndex(UF::UNIT_FIELD_FACTIONTEMPLATE);
|
||||
const uint16_t ufFlags = fieldIndex(UF::UNIT_FIELD_FLAGS);
|
||||
const uint16_t ufDynFlags = fieldIndex(UF::UNIT_DYNAMIC_FLAGS);
|
||||
const uint16_t ufDisplayId = fieldIndex(UF::UNIT_FIELD_DISPLAYID);
|
||||
const uint16_t ufMountDisplayId = fieldIndex(UF::UNIT_FIELD_MOUNTDISPLAYID);
|
||||
const uint16_t ufNpcFlags = fieldIndex(UF::UNIT_NPC_FLAGS);
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
switch (key) {
|
||||
case 24: {
|
||||
uint32_t oldHealth = unit->getHealth();
|
||||
unit->setHealth(val);
|
||||
if (val == 0) {
|
||||
if (block.guid == autoAttackTarget) {
|
||||
stopAutoAttack();
|
||||
}
|
||||
hostileAttackers_.erase(block.guid);
|
||||
// Player death
|
||||
if (block.guid == playerGuid) {
|
||||
playerDead_ = true;
|
||||
releasedSpirit_ = false;
|
||||
stopAutoAttack();
|
||||
LOG_INFO("Player died!");
|
||||
}
|
||||
// Trigger death animation for NPC units
|
||||
if (entity->getType() == ObjectType::UNIT && npcDeathCallback_) {
|
||||
npcDeathCallback_(block.guid);
|
||||
}
|
||||
} else if (oldHealth == 0 && val > 0) {
|
||||
// Player resurrection or ghost form
|
||||
if (block.guid == playerGuid) {
|
||||
playerDead_ = false;
|
||||
if (!releasedSpirit_) {
|
||||
LOG_INFO("Player resurrected!");
|
||||
} else {
|
||||
LOG_INFO("Player entered ghost form");
|
||||
}
|
||||
}
|
||||
// Respawn: health went from 0 to >0, reset animation
|
||||
if (entity->getType() == ObjectType::UNIT && npcRespawnCallback_) {
|
||||
npcRespawnCallback_(block.guid);
|
||||
}
|
||||
if (key == ufHealth) {
|
||||
uint32_t oldHealth = unit->getHealth();
|
||||
unit->setHealth(val);
|
||||
if (val == 0) {
|
||||
if (block.guid == autoAttackTarget) {
|
||||
stopAutoAttack();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 25: unit->setPower(val); break;
|
||||
case 32: unit->setMaxHealth(val); break;
|
||||
case 33: unit->setMaxPower(val); break;
|
||||
case 59: unit->setUnitFlags(val); break; // UNIT_FIELD_FLAGS
|
||||
case 147: {
|
||||
uint32_t oldDyn = unit->getDynamicFlags();
|
||||
unit->setDynamicFlags(val);
|
||||
hostileAttackers_.erase(block.guid);
|
||||
if (block.guid == playerGuid) {
|
||||
bool wasDead = (oldDyn & UNIT_DYNFLAG_DEAD) != 0;
|
||||
bool nowDead = (val & UNIT_DYNFLAG_DEAD) != 0;
|
||||
if (!wasDead && nowDead) {
|
||||
playerDead_ = true;
|
||||
releasedSpirit_ = false;
|
||||
LOG_INFO("Player died (dynamic flags)");
|
||||
} else if (wasDead && !nowDead) {
|
||||
playerDead_ = false;
|
||||
releasedSpirit_ = false;
|
||||
LOG_INFO("Player resurrected (dynamic flags)");
|
||||
}
|
||||
playerDead_ = true;
|
||||
releasedSpirit_ = false;
|
||||
stopAutoAttack();
|
||||
LOG_INFO("Player died!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 54: unit->setLevel(val); break;
|
||||
case 55: // UNIT_FIELD_FACTIONTEMPLATE
|
||||
unit->setFactionTemplate(val);
|
||||
unit->setHostile(isHostileFaction(val));
|
||||
break;
|
||||
case 67:
|
||||
if (val != unit->getDisplayId()) {
|
||||
unit->setDisplayId(val);
|
||||
displayIdChanged = true;
|
||||
if (entity->getType() == ObjectType::UNIT && npcDeathCallback_) {
|
||||
npcDeathCallback_(block.guid);
|
||||
}
|
||||
break; // UNIT_FIELD_DISPLAYID
|
||||
case 69: // UNIT_FIELD_MOUNTDISPLAYID
|
||||
} else if (oldHealth == 0 && val > 0) {
|
||||
if (block.guid == playerGuid) {
|
||||
uint32_t old = currentMountDisplayId_;
|
||||
currentMountDisplayId_ = val;
|
||||
if (val != old && mountCallback_) mountCallback_(val);
|
||||
if (old != 0 && val == 0) {
|
||||
for (auto& a : playerAuras)
|
||||
if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{};
|
||||
playerDead_ = false;
|
||||
if (!releasedSpirit_) {
|
||||
LOG_INFO("Player resurrected!");
|
||||
} else {
|
||||
LOG_INFO("Player entered ghost form");
|
||||
}
|
||||
}
|
||||
unit->setMountDisplayId(val);
|
||||
break;
|
||||
case 82: unit->setNpcFlags(val); break; // UNIT_NPC_FLAGS
|
||||
default: break;
|
||||
}
|
||||
if (entity->getType() == ObjectType::UNIT && npcRespawnCallback_) {
|
||||
npcRespawnCallback_(block.guid);
|
||||
}
|
||||
}
|
||||
} else if (key == ufPower) { unit->setPower(val); }
|
||||
else if (key == ufMaxHealth) { unit->setMaxHealth(val); }
|
||||
else if (key == ufMaxPower) { unit->setMaxPower(val); }
|
||||
else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
else if (key == ufDynFlags) {
|
||||
uint32_t oldDyn = unit->getDynamicFlags();
|
||||
unit->setDynamicFlags(val);
|
||||
if (block.guid == playerGuid) {
|
||||
bool wasDead = (oldDyn & UNIT_DYNFLAG_DEAD) != 0;
|
||||
bool nowDead = (val & UNIT_DYNFLAG_DEAD) != 0;
|
||||
if (!wasDead && nowDead) {
|
||||
playerDead_ = true;
|
||||
releasedSpirit_ = false;
|
||||
LOG_INFO("Player died (dynamic flags)");
|
||||
} else if (wasDead && !nowDead) {
|
||||
playerDead_ = false;
|
||||
releasedSpirit_ = false;
|
||||
LOG_INFO("Player resurrected (dynamic flags)");
|
||||
}
|
||||
}
|
||||
} else if (key == ufLevel) { unit->setLevel(val); }
|
||||
else if (key == ufFaction) {
|
||||
unit->setFactionTemplate(val);
|
||||
unit->setHostile(isHostileFaction(val));
|
||||
} else if (key == ufDisplayId) {
|
||||
if (val != unit->getDisplayId()) {
|
||||
unit->setDisplayId(val);
|
||||
displayIdChanged = true;
|
||||
}
|
||||
} else if (key == ufMountDisplayId) {
|
||||
if (block.guid == playerGuid) {
|
||||
uint32_t old = currentMountDisplayId_;
|
||||
currentMountDisplayId_ = val;
|
||||
if (val != old && mountCallback_) mountCallback_(val);
|
||||
if (old != 0 && val == 0) {
|
||||
for (auto& a : playerAuras)
|
||||
if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{};
|
||||
}
|
||||
}
|
||||
unit->setMountDisplayId(val);
|
||||
} else if (key == ufNpcFlags) { unit->setNpcFlags(val); }
|
||||
}
|
||||
|
||||
// Some units are created without displayId and get it later via VALUES.
|
||||
|
|
@ -3005,7 +3031,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation());
|
||||
}
|
||||
if ((unit->getNpcFlags() & 0x02) && socket) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(block.guid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
|
|
@ -3031,19 +3057,23 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
detectInventorySlotBases(block.fields);
|
||||
bool slotsChanged = false;
|
||||
const uint16_t ufPlayerXp = fieldIndex(UF::PLAYER_XP);
|
||||
const uint16_t ufPlayerNextXp = fieldIndex(UF::PLAYER_NEXT_LEVEL_XP);
|
||||
const uint16_t ufPlayerLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
|
||||
const uint16_t ufCoinage = fieldIndex(UF::PLAYER_FIELD_COINAGE);
|
||||
const uint16_t ufPlayerFlags = fieldIndex(UF::PLAYER_FLAGS);
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
if (key == 634) {
|
||||
if (key == ufPlayerXp) {
|
||||
playerXp_ = val;
|
||||
LOG_INFO("XP updated: ", val);
|
||||
}
|
||||
else if (key == 635) {
|
||||
else if (key == ufPlayerNextXp) {
|
||||
playerNextLevelXp_ = val;
|
||||
LOG_INFO("Next level XP updated: ", val);
|
||||
}
|
||||
else if (key == 54) {
|
||||
else if (key == ufPlayerLevel) {
|
||||
serverPlayerLevel_ = val;
|
||||
LOG_INFO("Level updated: ", val);
|
||||
// Update Character struct for character selection screen
|
||||
for (auto& ch : characters) {
|
||||
if (ch.guid == playerGuid) {
|
||||
ch.level = val;
|
||||
|
|
@ -3051,11 +3081,11 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (key == 1170) {
|
||||
else if (key == ufCoinage) {
|
||||
playerMoneyCopper_ = val;
|
||||
LOG_INFO("Money updated via VALUES: ", val, " copper");
|
||||
}
|
||||
else if (key == 150) { // PLAYER_FLAGS (UNIT_END+2)
|
||||
else if (key == ufPlayerFlags) {
|
||||
constexpr uint32_t PLAYER_FLAGS_GHOST = 0x00000010;
|
||||
bool wasGhost = releasedSpirit_;
|
||||
bool nowGhost = (val & PLAYER_FLAGS_GHOST) != 0;
|
||||
|
|
@ -3080,7 +3110,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
// Update item stack count for online items
|
||||
if (entity->getType() == ObjectType::ITEM) {
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
if (key == 14) { // ITEM_FIELD_STACK_COUNT
|
||||
if (key == fieldIndex(UF::ITEM_FIELD_STACK_COUNT)) {
|
||||
auto it = onlineItems_.find(block.guid);
|
||||
if (it != onlineItems_.end()) it->second.stackCount = val;
|
||||
}
|
||||
|
|
@ -3240,7 +3270,7 @@ void GameHandler::handleCompressedUpdateObject(network::Packet& packet) {
|
|||
LOG_DEBUG(" Decompressed ", compressedSize, " -> ", destLen, " bytes");
|
||||
|
||||
// Create packet from decompressed data and parse it
|
||||
network::Packet decompressedPacket(static_cast<uint16_t>(Opcode::SMSG_UPDATE_OBJECT), decompressed);
|
||||
network::Packet decompressedPacket(wireOpcode(Opcode::SMSG_UPDATE_OBJECT), decompressed);
|
||||
handleUpdateObject(decompressedPacket);
|
||||
}
|
||||
|
||||
|
|
@ -3908,15 +3938,12 @@ void GameHandler::assistTarget() {
|
|||
}
|
||||
|
||||
// Try to read target GUID from update fields (UNIT_FIELD_TARGET)
|
||||
// Field offset 6 is typically UNIT_FIELD_TARGET in 3.3.5a
|
||||
uint64_t assistTargetGuid = 0;
|
||||
const auto& fields = target->getFields();
|
||||
auto it = fields.find(6);
|
||||
auto it = fields.find(fieldIndex(UF::UNIT_FIELD_TARGET_LO));
|
||||
if (it != fields.end()) {
|
||||
// Low 32 bits
|
||||
assistTargetGuid = it->second;
|
||||
// Try to get high 32 bits from next field
|
||||
auto it2 = fields.find(7);
|
||||
auto it2 = fields.find(fieldIndex(UF::UNIT_FIELD_TARGET_HI));
|
||||
if (it2 != fields.end()) {
|
||||
assistTargetGuid |= (static_cast<uint64_t>(it2->second) << 32);
|
||||
}
|
||||
|
|
@ -4581,8 +4608,7 @@ void GameHandler::detectInventorySlotBases(const std::map<uint16_t, uint32_t>& f
|
|||
// The lowest matching field is the first EQUIPPED slot (not necessarily HEAD).
|
||||
// With 2+ matches we can derive the true base: all matches must be at
|
||||
// even offsets from the base, spaced 2 fields per slot.
|
||||
// Use the known 3.3.5a default (324) and verify matches align to it.
|
||||
constexpr int knownBase = 324;
|
||||
const int knownBase = static_cast<int>(fieldIndex(UF::PLAYER_FIELD_INV_SLOT_HEAD));
|
||||
constexpr int slotStride = 2;
|
||||
bool allAlign = true;
|
||||
for (uint16_t p : matchingPairs) {
|
||||
|
|
@ -4628,10 +4654,8 @@ void GameHandler::detectInventorySlotBases(const std::map<uint16_t, uint32_t>& f
|
|||
|
||||
bool GameHandler::applyInventoryFields(const std::map<uint16_t, uint32_t>& fields) {
|
||||
bool slotsChanged = false;
|
||||
// WoW 3.3.5a: PLAYER_FIELD_INV_SLOT_HEAD = UNIT_END + 0x00B0 = 324
|
||||
// PLAYER_FIELD_PACK_SLOT_1 = UNIT_END + 0x00DE = 370
|
||||
int equipBase = (invSlotBase_ >= 0) ? invSlotBase_ : 324;
|
||||
int packBase = (packSlotBase_ >= 0) ? packSlotBase_ : 370;
|
||||
int equipBase = (invSlotBase_ >= 0) ? invSlotBase_ : static_cast<int>(fieldIndex(UF::PLAYER_FIELD_INV_SLOT_HEAD));
|
||||
int packBase = (packSlotBase_ >= 0) ? packSlotBase_ : static_cast<int>(fieldIndex(UF::PLAYER_FIELD_PACK_SLOT_1));
|
||||
|
||||
for (const auto& [key, val] : fields) {
|
||||
if (key >= equipBase && key <= equipBase + (game::Inventory::NUM_EQUIP_SLOTS * 2 - 1)) {
|
||||
|
|
@ -4854,7 +4878,7 @@ void GameHandler::dismount() {
|
|||
taxiClientActive_ = false;
|
||||
LOG_INFO("Dismount desync recovery: force-cleared local mount state");
|
||||
}
|
||||
network::Packet pkt(static_cast<uint16_t>(Opcode::CMSG_CANCEL_MOUNT_AURA));
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_CANCEL_MOUNT_AURA));
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent CMSG_CANCEL_MOUNT_AURA");
|
||||
}
|
||||
|
|
@ -4886,7 +4910,7 @@ void GameHandler::handleForceRunSpeedChange(network::Packet& packet) {
|
|||
// Always ACK the speed change to prevent server stall.
|
||||
// Packet format mirrors movement packets: packed guid + counter + movement info + new speed.
|
||||
if (socket) {
|
||||
network::Packet ack(static_cast<uint16_t>(Opcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK));
|
||||
network::Packet ack(wireOpcode(Opcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK));
|
||||
MovementPacket::writePackedGuid(ack, playerGuid);
|
||||
ack.writeUInt32(counter);
|
||||
|
||||
|
|
@ -5781,7 +5805,7 @@ void GameHandler::selectGossipQuest(uint32_t questId) {
|
|||
if (isInLog && isCompletable) {
|
||||
// Quest is ready to turn in - request reward
|
||||
LOG_INFO("Turning in quest: questId=", questId, " npcGuid=", currentGossip.npcGuid);
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_REQUEST_REWARD));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_REQUEST_REWARD));
|
||||
packet.writeUInt64(currentGossip.npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
socket->send(packet);
|
||||
|
|
@ -5831,7 +5855,7 @@ void GameHandler::acceptQuest() {
|
|||
|
||||
// Re-query quest giver status so marker updates (! → ?)
|
||||
if (npcGuid) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(npcGuid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
|
|
@ -5849,7 +5873,7 @@ void GameHandler::abandonQuest(uint32_t questId) {
|
|||
// Tell server to remove it (slot index in server quest log)
|
||||
// We send the local index; server maps it via PLAYER_QUEST_LOG fields
|
||||
if (state == WorldState::IN_WORLD && socket) {
|
||||
network::Packet pkt(static_cast<uint16_t>(Opcode::CMSG_QUESTLOG_REMOVE_QUEST));
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_QUESTLOG_REMOVE_QUEST));
|
||||
pkt.writeUInt8(static_cast<uint8_t>(i));
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
|
@ -5923,7 +5947,7 @@ void GameHandler::chooseQuestReward(uint32_t rewardIndex) {
|
|||
|
||||
// Re-query quest giver status so markers update
|
||||
if (npcGuid) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
network::Packet qsPkt(wireOpcode(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(npcGuid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
|
|
@ -6274,13 +6298,13 @@ void GameHandler::loadSpellNameCache() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Fields: 0=SpellID, 136=SpellName_enUS, 153=RankText_enUS
|
||||
const auto* spellL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr;
|
||||
uint32_t count = dbc->getRecordCount();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
uint32_t id = dbc->getUInt32(i, 0);
|
||||
uint32_t id = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0);
|
||||
if (id == 0) continue;
|
||||
std::string name = dbc->getString(i, 136);
|
||||
std::string rank = dbc->getString(i, 153);
|
||||
std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136);
|
||||
std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153);
|
||||
if (!name.empty()) {
|
||||
spellNameCache_[id] = {std::move(name), std::move(rank)};
|
||||
}
|
||||
|
|
@ -6295,12 +6319,12 @@ void GameHandler::loadSkillLineAbilityDbc() {
|
|||
auto* am = core::Application::getInstance().getAssetManager();
|
||||
if (!am || !am->isInitialized()) return;
|
||||
|
||||
// SkillLineAbility.dbc: field 1=skillLineID, field 2=spellID
|
||||
auto slaDbc = am->loadDBC("SkillLineAbility.dbc");
|
||||
if (slaDbc && slaDbc->isLoaded()) {
|
||||
const auto* slaL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("SkillLineAbility") : nullptr;
|
||||
for (uint32_t i = 0; i < slaDbc->getRecordCount(); i++) {
|
||||
uint32_t skillLineId = slaDbc->getUInt32(i, 1);
|
||||
uint32_t spellId = slaDbc->getUInt32(i, 2);
|
||||
uint32_t skillLineId = slaDbc->getUInt32(i, slaL ? (*slaL)["SkillLineID"] : 1);
|
||||
uint32_t spellId = slaDbc->getUInt32(i, slaL ? (*slaL)["SpellID"] : 2);
|
||||
if (spellId > 0 && skillLineId > 0) {
|
||||
spellToSkillLine_[spellId] = skillLineId;
|
||||
}
|
||||
|
|
@ -6380,25 +6404,34 @@ void GameHandler::loadTalentDbc() {
|
|||
// 12-14: PrereqRank[0-2]
|
||||
// (other fields less relevant for basic functionality)
|
||||
|
||||
const auto* talL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Talent") : nullptr;
|
||||
const uint32_t tID = talL ? (*talL)["ID"] : 0;
|
||||
const uint32_t tTabID = talL ? (*talL)["TabID"] : 1;
|
||||
const uint32_t tRow = talL ? (*talL)["Row"] : 2;
|
||||
const uint32_t tCol = talL ? (*talL)["Column"] : 3;
|
||||
const uint32_t tRank0 = talL ? (*talL)["RankSpell0"] : 4;
|
||||
const uint32_t tPrereq0 = talL ? (*talL)["PrereqTalent0"] : 9;
|
||||
const uint32_t tPrereqR0 = talL ? (*talL)["PrereqRank0"] : 12;
|
||||
|
||||
uint32_t count = talentDbc->getRecordCount();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
TalentEntry entry;
|
||||
entry.talentId = talentDbc->getUInt32(i, 0);
|
||||
entry.talentId = talentDbc->getUInt32(i, tID);
|
||||
if (entry.talentId == 0) continue;
|
||||
|
||||
entry.tabId = talentDbc->getUInt32(i, 1);
|
||||
entry.row = static_cast<uint8_t>(talentDbc->getUInt32(i, 2));
|
||||
entry.column = static_cast<uint8_t>(talentDbc->getUInt32(i, 3));
|
||||
entry.tabId = talentDbc->getUInt32(i, tTabID);
|
||||
entry.row = static_cast<uint8_t>(talentDbc->getUInt32(i, tRow));
|
||||
entry.column = static_cast<uint8_t>(talentDbc->getUInt32(i, tCol));
|
||||
|
||||
// Rank spells (1-5 ranks)
|
||||
for (int r = 0; r < 5; ++r) {
|
||||
entry.rankSpells[r] = talentDbc->getUInt32(i, 4 + r);
|
||||
entry.rankSpells[r] = talentDbc->getUInt32(i, tRank0 + r);
|
||||
}
|
||||
|
||||
// Prerequisites
|
||||
for (int p = 0; p < 3; ++p) {
|
||||
entry.prereqTalent[p] = talentDbc->getUInt32(i, 9 + p);
|
||||
entry.prereqRank[p] = static_cast<uint8_t>(talentDbc->getUInt32(i, 12 + p));
|
||||
entry.prereqTalent[p] = talentDbc->getUInt32(i, tPrereq0 + p);
|
||||
entry.prereqRank[p] = static_cast<uint8_t>(talentDbc->getUInt32(i, tPrereqR0 + p));
|
||||
}
|
||||
|
||||
// Calculate max rank
|
||||
|
|
@ -6429,16 +6462,17 @@ void GameHandler::loadTalentDbc() {
|
|||
// 22: OrderIndex
|
||||
// 23-39: BackgroundFile (16 localized strings + flags = 17 fields)
|
||||
|
||||
const auto* ttL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("TalentTab") : nullptr;
|
||||
uint32_t count = tabDbc->getRecordCount();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
TalentTabEntry entry;
|
||||
entry.tabId = tabDbc->getUInt32(i, 0);
|
||||
entry.tabId = tabDbc->getUInt32(i, ttL ? (*ttL)["ID"] : 0);
|
||||
if (entry.tabId == 0) continue;
|
||||
|
||||
entry.name = tabDbc->getString(i, 1);
|
||||
entry.classMask = tabDbc->getUInt32(i, 20);
|
||||
entry.orderIndex = static_cast<uint8_t>(tabDbc->getUInt32(i, 22));
|
||||
entry.backgroundFile = tabDbc->getString(i, 23);
|
||||
entry.name = tabDbc->getString(i, ttL ? (*ttL)["Name"] : 1);
|
||||
entry.classMask = tabDbc->getUInt32(i, ttL ? (*ttL)["ClassMask"] : 20);
|
||||
entry.orderIndex = static_cast<uint8_t>(tabDbc->getUInt32(i, ttL ? (*ttL)["OrderIndex"] : 22));
|
||||
entry.backgroundFile = tabDbc->getString(i, ttL ? (*ttL)["BackgroundFile"] : 23);
|
||||
|
||||
talentTabCache_[entry.tabId] = entry;
|
||||
|
||||
|
|
@ -6616,7 +6650,7 @@ void GameHandler::handleTeleportAck(network::Packet& packet) {
|
|||
// Send the ack back to the server
|
||||
// Client→server MSG_MOVE_TELEPORT_ACK: u64 guid + u32 counter + u32 time
|
||||
if (socket) {
|
||||
network::Packet ack(static_cast<uint16_t>(Opcode::MSG_MOVE_TELEPORT_ACK));
|
||||
network::Packet ack(wireOpcode(Opcode::MSG_MOVE_TELEPORT_ACK));
|
||||
// Write packed guid
|
||||
uint8_t mask = 0;
|
||||
uint8_t bytes[8];
|
||||
|
|
@ -6698,7 +6732,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
|
|||
|
||||
// Send MSG_MOVE_WORLDPORT_ACK to tell the server we're ready
|
||||
if (socket) {
|
||||
network::Packet ack(static_cast<uint16_t>(Opcode::MSG_MOVE_WORLDPORT_ACK));
|
||||
network::Packet ack(wireOpcode(Opcode::MSG_MOVE_WORLDPORT_ACK));
|
||||
socket->send(ack);
|
||||
LOG_INFO("Sent MSG_MOVE_WORLDPORT_ACK");
|
||||
}
|
||||
|
|
@ -6720,25 +6754,28 @@ void GameHandler::loadTaxiDbc() {
|
|||
auto* am = core::Application::getInstance().getAssetManager();
|
||||
if (!am || !am->isInitialized()) return;
|
||||
|
||||
// Load TaxiNodes.dbc: 0=ID, 1=mapId, 2=x, 3=y, 4=z, 5=name(enUS locale)
|
||||
auto nodesDbc = am->loadDBC("TaxiNodes.dbc");
|
||||
if (nodesDbc && nodesDbc->isLoaded()) {
|
||||
const auto* tnL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("TaxiNodes") : nullptr;
|
||||
uint32_t fieldCount = nodesDbc->getFieldCount();
|
||||
for (uint32_t i = 0; i < nodesDbc->getRecordCount(); i++) {
|
||||
TaxiNode node;
|
||||
node.id = nodesDbc->getUInt32(i, 0);
|
||||
node.mapId = nodesDbc->getUInt32(i, 1);
|
||||
node.x = nodesDbc->getFloat(i, 2);
|
||||
node.y = nodesDbc->getFloat(i, 3);
|
||||
node.z = nodesDbc->getFloat(i, 4);
|
||||
node.name = nodesDbc->getString(i, 5);
|
||||
// TaxiNodes.dbc (3.3.5a): last two fields are mount display IDs (Alliance, Horde)
|
||||
if (fieldCount >= 24) {
|
||||
node.mountDisplayIdAlliance = nodesDbc->getUInt32(i, 22);
|
||||
node.mountDisplayIdHorde = nodesDbc->getUInt32(i, 23);
|
||||
if (node.mountDisplayIdAlliance == 0 && node.mountDisplayIdHorde == 0 && fieldCount >= 22) {
|
||||
node.mountDisplayIdAlliance = nodesDbc->getUInt32(i, 20);
|
||||
node.mountDisplayIdHorde = nodesDbc->getUInt32(i, 21);
|
||||
node.id = nodesDbc->getUInt32(i, tnL ? (*tnL)["ID"] : 0);
|
||||
node.mapId = nodesDbc->getUInt32(i, tnL ? (*tnL)["MapID"] : 1);
|
||||
node.x = nodesDbc->getFloat(i, tnL ? (*tnL)["X"] : 2);
|
||||
node.y = nodesDbc->getFloat(i, tnL ? (*tnL)["Y"] : 3);
|
||||
node.z = nodesDbc->getFloat(i, tnL ? (*tnL)["Z"] : 4);
|
||||
node.name = nodesDbc->getString(i, tnL ? (*tnL)["Name"] : 5);
|
||||
const uint32_t mountAllianceField = tnL ? (*tnL)["MountDisplayIdAlliance"] : 22;
|
||||
const uint32_t mountHordeField = tnL ? (*tnL)["MountDisplayIdHorde"] : 23;
|
||||
const uint32_t mountAllianceFB = tnL ? (*tnL)["MountDisplayIdAllianceFallback"] : 20;
|
||||
const uint32_t mountHordeFB = tnL ? (*tnL)["MountDisplayIdHordeFallback"] : 21;
|
||||
if (fieldCount > mountHordeField) {
|
||||
node.mountDisplayIdAlliance = nodesDbc->getUInt32(i, mountAllianceField);
|
||||
node.mountDisplayIdHorde = nodesDbc->getUInt32(i, mountHordeField);
|
||||
if (node.mountDisplayIdAlliance == 0 && node.mountDisplayIdHorde == 0 && fieldCount > mountHordeFB) {
|
||||
node.mountDisplayIdAlliance = nodesDbc->getUInt32(i, mountAllianceFB);
|
||||
node.mountDisplayIdHorde = nodesDbc->getUInt32(i, mountHordeFB);
|
||||
}
|
||||
}
|
||||
if (node.id > 0) {
|
||||
|
|
@ -6757,15 +6794,15 @@ void GameHandler::loadTaxiDbc() {
|
|||
LOG_WARNING("Could not load TaxiNodes.dbc");
|
||||
}
|
||||
|
||||
// Load TaxiPath.dbc: 0=pathId, 1=fromNode, 2=toNode, 3=cost
|
||||
auto pathDbc = am->loadDBC("TaxiPath.dbc");
|
||||
if (pathDbc && pathDbc->isLoaded()) {
|
||||
const auto* tpL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("TaxiPath") : nullptr;
|
||||
for (uint32_t i = 0; i < pathDbc->getRecordCount(); i++) {
|
||||
TaxiPathEdge edge;
|
||||
edge.pathId = pathDbc->getUInt32(i, 0);
|
||||
edge.fromNode = pathDbc->getUInt32(i, 1);
|
||||
edge.toNode = pathDbc->getUInt32(i, 2);
|
||||
edge.cost = pathDbc->getUInt32(i, 3);
|
||||
edge.pathId = pathDbc->getUInt32(i, tpL ? (*tpL)["ID"] : 0);
|
||||
edge.fromNode = pathDbc->getUInt32(i, tpL ? (*tpL)["FromNode"] : 1);
|
||||
edge.toNode = pathDbc->getUInt32(i, tpL ? (*tpL)["ToNode"] : 2);
|
||||
edge.cost = pathDbc->getUInt32(i, tpL ? (*tpL)["Cost"] : 3);
|
||||
taxiPathEdges_.push_back(edge);
|
||||
}
|
||||
LOG_INFO("Loaded ", taxiPathEdges_.size(), " taxi path edges from TaxiPath.dbc");
|
||||
|
|
@ -6773,19 +6810,18 @@ void GameHandler::loadTaxiDbc() {
|
|||
LOG_WARNING("Could not load TaxiPath.dbc");
|
||||
}
|
||||
|
||||
// Load TaxiPathNode.dbc: actual spline waypoints for each path
|
||||
// 0=ID, 1=PathID, 2=NodeIndex, 3=MapID, 4=X, 5=Y, 6=Z
|
||||
auto pathNodeDbc = am->loadDBC("TaxiPathNode.dbc");
|
||||
if (pathNodeDbc && pathNodeDbc->isLoaded()) {
|
||||
const auto* tpnL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("TaxiPathNode") : nullptr;
|
||||
for (uint32_t i = 0; i < pathNodeDbc->getRecordCount(); i++) {
|
||||
TaxiPathNode node;
|
||||
node.id = pathNodeDbc->getUInt32(i, 0);
|
||||
node.pathId = pathNodeDbc->getUInt32(i, 1);
|
||||
node.nodeIndex = pathNodeDbc->getUInt32(i, 2);
|
||||
node.mapId = pathNodeDbc->getUInt32(i, 3);
|
||||
node.x = pathNodeDbc->getFloat(i, 4);
|
||||
node.y = pathNodeDbc->getFloat(i, 5);
|
||||
node.z = pathNodeDbc->getFloat(i, 6);
|
||||
node.id = pathNodeDbc->getUInt32(i, tpnL ? (*tpnL)["ID"] : 0);
|
||||
node.pathId = pathNodeDbc->getUInt32(i, tpnL ? (*tpnL)["PathID"] : 1);
|
||||
node.nodeIndex = pathNodeDbc->getUInt32(i, tpnL ? (*tpnL)["NodeIndex"] : 2);
|
||||
node.mapId = pathNodeDbc->getUInt32(i, tpnL ? (*tpnL)["MapID"] : 3);
|
||||
node.x = pathNodeDbc->getFloat(i, tpnL ? (*tpnL)["X"] : 4);
|
||||
node.y = pathNodeDbc->getFloat(i, tpnL ? (*tpnL)["Y"] : 5);
|
||||
node.z = pathNodeDbc->getFloat(i, tpnL ? (*tpnL)["Z"] : 6);
|
||||
taxiPathNodes_[node.pathId].push_back(node);
|
||||
}
|
||||
// Sort waypoints by nodeIndex for each path
|
||||
|
|
@ -7667,10 +7703,11 @@ void GameHandler::loadSkillLineDbc() {
|
|||
return;
|
||||
}
|
||||
|
||||
const auto* slL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("SkillLine") : nullptr;
|
||||
for (uint32_t i = 0; i < dbc->getRecordCount(); i++) {
|
||||
uint32_t id = dbc->getUInt32(i, 0);
|
||||
uint32_t category = dbc->getUInt32(i, 1);
|
||||
std::string name = dbc->getString(i, 3);
|
||||
uint32_t id = dbc->getUInt32(i, slL ? (*slL)["ID"] : 0);
|
||||
uint32_t category = dbc->getUInt32(i, slL ? (*slL)["Category"] : 1);
|
||||
std::string name = dbc->getString(i, slL ? (*slL)["Name"] : 3);
|
||||
if (id > 0 && !name.empty()) {
|
||||
skillLineNames_[id] = name;
|
||||
skillLineCategories_[id] = category;
|
||||
|
|
@ -7682,8 +7719,7 @@ void GameHandler::loadSkillLineDbc() {
|
|||
void GameHandler::extractSkillFields(const std::map<uint16_t, uint32_t>& fields) {
|
||||
loadSkillLineDbc();
|
||||
|
||||
// PLAYER_SKILL_INFO_1_1 = field 636, 128 slots x 3 fields each (636..1019)
|
||||
static constexpr uint16_t PLAYER_SKILL_INFO_START = 636;
|
||||
const uint16_t PLAYER_SKILL_INFO_START = fieldIndex(UF::PLAYER_SKILL_INFO_START);
|
||||
static constexpr int MAX_SKILL_SLOTS = 128;
|
||||
|
||||
std::map<uint32_t, PlayerSkill> newSkills;
|
||||
|
|
@ -7745,7 +7781,7 @@ void GameHandler::extractExploredZoneFields(const std::map<uint16_t, uint32_t>&
|
|||
|
||||
bool foundAny = false;
|
||||
for (size_t i = 0; i < PLAYER_EXPLORED_ZONES_COUNT; i++) {
|
||||
const uint16_t fieldIdx = static_cast<uint16_t>(PLAYER_EXPLORED_ZONES_START + i);
|
||||
const uint16_t fieldIdx = static_cast<uint16_t>(fieldIndex(UF::PLAYER_EXPLORED_ZONES_START) + i);
|
||||
auto it = fields.find(fieldIdx);
|
||||
if (it == fields.end()) continue;
|
||||
playerExploredZones_[i] = it->second;
|
||||
|
|
|
|||
649
src/game/opcode_table.cpp
Normal file
649
src/game/opcode_table.cpp
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
#include "game/opcode_table.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
||||
// Global active opcode table pointer
|
||||
static const OpcodeTable* g_activeOpcodeTable = nullptr;
|
||||
|
||||
void setActiveOpcodeTable(const OpcodeTable* table) { g_activeOpcodeTable = table; }
|
||||
const OpcodeTable* getActiveOpcodeTable() { return g_activeOpcodeTable; }
|
||||
|
||||
// Name ↔ LogicalOpcode mapping table (generated from the enum)
|
||||
struct OpcodeNameEntry {
|
||||
const char* name;
|
||||
LogicalOpcode op;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
static const OpcodeNameEntry kOpcodeNames[] = {
|
||||
{"CMSG_PING", LogicalOpcode::CMSG_PING},
|
||||
{"CMSG_AUTH_SESSION", LogicalOpcode::CMSG_AUTH_SESSION},
|
||||
{"CMSG_CHAR_CREATE", LogicalOpcode::CMSG_CHAR_CREATE},
|
||||
{"CMSG_CHAR_ENUM", LogicalOpcode::CMSG_CHAR_ENUM},
|
||||
{"CMSG_CHAR_DELETE", LogicalOpcode::CMSG_CHAR_DELETE},
|
||||
{"CMSG_PLAYER_LOGIN", LogicalOpcode::CMSG_PLAYER_LOGIN},
|
||||
{"CMSG_MOVE_START_FORWARD", LogicalOpcode::CMSG_MOVE_START_FORWARD},
|
||||
{"CMSG_MOVE_START_BACKWARD", LogicalOpcode::CMSG_MOVE_START_BACKWARD},
|
||||
{"CMSG_MOVE_STOP", LogicalOpcode::CMSG_MOVE_STOP},
|
||||
{"CMSG_MOVE_START_STRAFE_LEFT", LogicalOpcode::CMSG_MOVE_START_STRAFE_LEFT},
|
||||
{"CMSG_MOVE_START_STRAFE_RIGHT", LogicalOpcode::CMSG_MOVE_START_STRAFE_RIGHT},
|
||||
{"CMSG_MOVE_STOP_STRAFE", LogicalOpcode::CMSG_MOVE_STOP_STRAFE},
|
||||
{"CMSG_MOVE_JUMP", LogicalOpcode::CMSG_MOVE_JUMP},
|
||||
{"CMSG_MOVE_START_TURN_LEFT", LogicalOpcode::CMSG_MOVE_START_TURN_LEFT},
|
||||
{"CMSG_MOVE_START_TURN_RIGHT", LogicalOpcode::CMSG_MOVE_START_TURN_RIGHT},
|
||||
{"CMSG_MOVE_STOP_TURN", LogicalOpcode::CMSG_MOVE_STOP_TURN},
|
||||
{"CMSG_MOVE_SET_FACING", LogicalOpcode::CMSG_MOVE_SET_FACING},
|
||||
{"CMSG_MOVE_FALL_LAND", LogicalOpcode::CMSG_MOVE_FALL_LAND},
|
||||
{"CMSG_MOVE_START_SWIM", LogicalOpcode::CMSG_MOVE_START_SWIM},
|
||||
{"CMSG_MOVE_STOP_SWIM", LogicalOpcode::CMSG_MOVE_STOP_SWIM},
|
||||
{"CMSG_MOVE_HEARTBEAT", LogicalOpcode::CMSG_MOVE_HEARTBEAT},
|
||||
{"SMSG_AUTH_CHALLENGE", LogicalOpcode::SMSG_AUTH_CHALLENGE},
|
||||
{"SMSG_AUTH_RESPONSE", LogicalOpcode::SMSG_AUTH_RESPONSE},
|
||||
{"SMSG_CHAR_CREATE", LogicalOpcode::SMSG_CHAR_CREATE},
|
||||
{"SMSG_CHAR_ENUM", LogicalOpcode::SMSG_CHAR_ENUM},
|
||||
{"SMSG_CHAR_DELETE", LogicalOpcode::SMSG_CHAR_DELETE},
|
||||
{"SMSG_PONG", LogicalOpcode::SMSG_PONG},
|
||||
{"SMSG_LOGIN_VERIFY_WORLD", LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD},
|
||||
{"SMSG_LOGIN_SETTIMESPEED", LogicalOpcode::SMSG_LOGIN_SETTIMESPEED},
|
||||
{"SMSG_TUTORIAL_FLAGS", LogicalOpcode::SMSG_TUTORIAL_FLAGS},
|
||||
{"SMSG_WARDEN_DATA", LogicalOpcode::SMSG_WARDEN_DATA},
|
||||
{"CMSG_WARDEN_DATA", LogicalOpcode::CMSG_WARDEN_DATA},
|
||||
{"SMSG_ACCOUNT_DATA_TIMES", LogicalOpcode::SMSG_ACCOUNT_DATA_TIMES},
|
||||
{"SMSG_CLIENTCACHE_VERSION", LogicalOpcode::SMSG_CLIENTCACHE_VERSION},
|
||||
{"SMSG_FEATURE_SYSTEM_STATUS", LogicalOpcode::SMSG_FEATURE_SYSTEM_STATUS},
|
||||
{"SMSG_MOTD", LogicalOpcode::SMSG_MOTD},
|
||||
{"SMSG_UPDATE_OBJECT", LogicalOpcode::SMSG_UPDATE_OBJECT},
|
||||
{"SMSG_COMPRESSED_UPDATE_OBJECT", LogicalOpcode::SMSG_COMPRESSED_UPDATE_OBJECT},
|
||||
{"SMSG_MONSTER_MOVE_TRANSPORT", LogicalOpcode::SMSG_MONSTER_MOVE_TRANSPORT},
|
||||
{"SMSG_DESTROY_OBJECT", LogicalOpcode::SMSG_DESTROY_OBJECT},
|
||||
{"CMSG_MESSAGECHAT", LogicalOpcode::CMSG_MESSAGECHAT},
|
||||
{"SMSG_MESSAGECHAT", LogicalOpcode::SMSG_MESSAGECHAT},
|
||||
{"CMSG_WHO", LogicalOpcode::CMSG_WHO},
|
||||
{"SMSG_WHO", LogicalOpcode::SMSG_WHO},
|
||||
{"CMSG_REQUEST_PLAYED_TIME", LogicalOpcode::CMSG_REQUEST_PLAYED_TIME},
|
||||
{"SMSG_PLAYED_TIME", LogicalOpcode::SMSG_PLAYED_TIME},
|
||||
{"CMSG_QUERY_TIME", LogicalOpcode::CMSG_QUERY_TIME},
|
||||
{"SMSG_QUERY_TIME_RESPONSE", LogicalOpcode::SMSG_QUERY_TIME_RESPONSE},
|
||||
{"SMSG_FRIEND_STATUS", LogicalOpcode::SMSG_FRIEND_STATUS},
|
||||
{"CMSG_ADD_FRIEND", LogicalOpcode::CMSG_ADD_FRIEND},
|
||||
{"CMSG_DEL_FRIEND", LogicalOpcode::CMSG_DEL_FRIEND},
|
||||
{"CMSG_SET_CONTACT_NOTES", LogicalOpcode::CMSG_SET_CONTACT_NOTES},
|
||||
{"CMSG_ADD_IGNORE", LogicalOpcode::CMSG_ADD_IGNORE},
|
||||
{"CMSG_DEL_IGNORE", LogicalOpcode::CMSG_DEL_IGNORE},
|
||||
{"CMSG_PLAYER_LOGOUT", LogicalOpcode::CMSG_PLAYER_LOGOUT},
|
||||
{"CMSG_LOGOUT_REQUEST", LogicalOpcode::CMSG_LOGOUT_REQUEST},
|
||||
{"CMSG_LOGOUT_CANCEL", LogicalOpcode::CMSG_LOGOUT_CANCEL},
|
||||
{"SMSG_LOGOUT_RESPONSE", LogicalOpcode::SMSG_LOGOUT_RESPONSE},
|
||||
{"SMSG_LOGOUT_COMPLETE", LogicalOpcode::SMSG_LOGOUT_COMPLETE},
|
||||
{"CMSG_STAND_STATE_CHANGE", LogicalOpcode::CMSG_STAND_STATE_CHANGE},
|
||||
{"CMSG_SHOWING_HELM", LogicalOpcode::CMSG_SHOWING_HELM},
|
||||
{"CMSG_SHOWING_CLOAK", LogicalOpcode::CMSG_SHOWING_CLOAK},
|
||||
{"CMSG_TOGGLE_PVP", LogicalOpcode::CMSG_TOGGLE_PVP},
|
||||
{"CMSG_GUILD_INVITE", LogicalOpcode::CMSG_GUILD_INVITE},
|
||||
{"CMSG_GUILD_ACCEPT", LogicalOpcode::CMSG_GUILD_ACCEPT},
|
||||
{"CMSG_GUILD_DECLINE_INVITATION", LogicalOpcode::CMSG_GUILD_DECLINE_INVITATION},
|
||||
{"CMSG_GUILD_INFO", LogicalOpcode::CMSG_GUILD_INFO},
|
||||
{"CMSG_GUILD_GET_ROSTER", LogicalOpcode::CMSG_GUILD_GET_ROSTER},
|
||||
{"CMSG_GUILD_PROMOTE_MEMBER", LogicalOpcode::CMSG_GUILD_PROMOTE_MEMBER},
|
||||
{"CMSG_GUILD_DEMOTE_MEMBER", LogicalOpcode::CMSG_GUILD_DEMOTE_MEMBER},
|
||||
{"CMSG_GUILD_LEAVE", LogicalOpcode::CMSG_GUILD_LEAVE},
|
||||
{"CMSG_GUILD_MOTD", LogicalOpcode::CMSG_GUILD_MOTD},
|
||||
{"SMSG_GUILD_INFO", LogicalOpcode::SMSG_GUILD_INFO},
|
||||
{"SMSG_GUILD_ROSTER", LogicalOpcode::SMSG_GUILD_ROSTER},
|
||||
{"MSG_RAID_READY_CHECK", LogicalOpcode::MSG_RAID_READY_CHECK},
|
||||
{"MSG_RAID_READY_CHECK_CONFIRM", LogicalOpcode::MSG_RAID_READY_CHECK_CONFIRM},
|
||||
{"CMSG_DUEL_PROPOSED", LogicalOpcode::CMSG_DUEL_PROPOSED},
|
||||
{"CMSG_DUEL_ACCEPTED", LogicalOpcode::CMSG_DUEL_ACCEPTED},
|
||||
{"CMSG_DUEL_CANCELLED", LogicalOpcode::CMSG_DUEL_CANCELLED},
|
||||
{"SMSG_DUEL_REQUESTED", LogicalOpcode::SMSG_DUEL_REQUESTED},
|
||||
{"CMSG_INITIATE_TRADE", LogicalOpcode::CMSG_INITIATE_TRADE},
|
||||
{"MSG_RANDOM_ROLL", LogicalOpcode::MSG_RANDOM_ROLL},
|
||||
{"CMSG_SET_SELECTION", LogicalOpcode::CMSG_SET_SELECTION},
|
||||
{"CMSG_NAME_QUERY", LogicalOpcode::CMSG_NAME_QUERY},
|
||||
{"SMSG_NAME_QUERY_RESPONSE", LogicalOpcode::SMSG_NAME_QUERY_RESPONSE},
|
||||
{"CMSG_CREATURE_QUERY", LogicalOpcode::CMSG_CREATURE_QUERY},
|
||||
{"SMSG_CREATURE_QUERY_RESPONSE", LogicalOpcode::SMSG_CREATURE_QUERY_RESPONSE},
|
||||
{"CMSG_GAMEOBJECT_QUERY", LogicalOpcode::CMSG_GAMEOBJECT_QUERY},
|
||||
{"SMSG_GAMEOBJECT_QUERY_RESPONSE", LogicalOpcode::SMSG_GAMEOBJECT_QUERY_RESPONSE},
|
||||
{"CMSG_SET_ACTIVE_MOVER", LogicalOpcode::CMSG_SET_ACTIVE_MOVER},
|
||||
{"CMSG_BINDER_ACTIVATE", LogicalOpcode::CMSG_BINDER_ACTIVATE},
|
||||
{"SMSG_LOG_XPGAIN", LogicalOpcode::SMSG_LOG_XPGAIN},
|
||||
{"SMSG_MONSTER_MOVE", LogicalOpcode::SMSG_MONSTER_MOVE},
|
||||
{"CMSG_ATTACKSWING", LogicalOpcode::CMSG_ATTACKSWING},
|
||||
{"CMSG_ATTACKSTOP", LogicalOpcode::CMSG_ATTACKSTOP},
|
||||
{"SMSG_ATTACKSTART", LogicalOpcode::SMSG_ATTACKSTART},
|
||||
{"SMSG_ATTACKSTOP", LogicalOpcode::SMSG_ATTACKSTOP},
|
||||
{"SMSG_ATTACKERSTATEUPDATE", LogicalOpcode::SMSG_ATTACKERSTATEUPDATE},
|
||||
{"SMSG_SPELLNONMELEEDAMAGELOG", LogicalOpcode::SMSG_SPELLNONMELEEDAMAGELOG},
|
||||
{"SMSG_SPELLHEALLOG", LogicalOpcode::SMSG_SPELLHEALLOG},
|
||||
{"SMSG_SPELLENERGIZELOG", LogicalOpcode::SMSG_SPELLENERGIZELOG},
|
||||
{"SMSG_PERIODICAURALOG", LogicalOpcode::SMSG_PERIODICAURALOG},
|
||||
{"SMSG_ENVIRONMENTALDAMAGELOG", LogicalOpcode::SMSG_ENVIRONMENTALDAMAGELOG},
|
||||
{"CMSG_CAST_SPELL", LogicalOpcode::CMSG_CAST_SPELL},
|
||||
{"CMSG_CANCEL_CAST", LogicalOpcode::CMSG_CANCEL_CAST},
|
||||
{"CMSG_CANCEL_AURA", LogicalOpcode::CMSG_CANCEL_AURA},
|
||||
{"SMSG_CAST_FAILED", LogicalOpcode::SMSG_CAST_FAILED},
|
||||
{"SMSG_SPELL_START", LogicalOpcode::SMSG_SPELL_START},
|
||||
{"SMSG_SPELL_GO", LogicalOpcode::SMSG_SPELL_GO},
|
||||
{"SMSG_SPELL_FAILURE", LogicalOpcode::SMSG_SPELL_FAILURE},
|
||||
{"SMSG_SPELL_COOLDOWN", LogicalOpcode::SMSG_SPELL_COOLDOWN},
|
||||
{"SMSG_COOLDOWN_EVENT", LogicalOpcode::SMSG_COOLDOWN_EVENT},
|
||||
{"SMSG_UPDATE_AURA_DURATION", LogicalOpcode::SMSG_UPDATE_AURA_DURATION},
|
||||
{"SMSG_INITIAL_SPELLS", LogicalOpcode::SMSG_INITIAL_SPELLS},
|
||||
{"SMSG_LEARNED_SPELL", LogicalOpcode::SMSG_LEARNED_SPELL},
|
||||
{"SMSG_SUPERCEDED_SPELL", LogicalOpcode::SMSG_SUPERCEDED_SPELL},
|
||||
{"SMSG_REMOVED_SPELL", LogicalOpcode::SMSG_REMOVED_SPELL},
|
||||
{"SMSG_SEND_UNLEARN_SPELLS", LogicalOpcode::SMSG_SEND_UNLEARN_SPELLS},
|
||||
{"SMSG_SPELL_DELAYED", LogicalOpcode::SMSG_SPELL_DELAYED},
|
||||
{"SMSG_AURA_UPDATE", LogicalOpcode::SMSG_AURA_UPDATE},
|
||||
{"SMSG_AURA_UPDATE_ALL", LogicalOpcode::SMSG_AURA_UPDATE_ALL},
|
||||
{"SMSG_SET_FLAT_SPELL_MODIFIER", LogicalOpcode::SMSG_SET_FLAT_SPELL_MODIFIER},
|
||||
{"SMSG_SET_PCT_SPELL_MODIFIER", LogicalOpcode::SMSG_SET_PCT_SPELL_MODIFIER},
|
||||
{"SMSG_TALENTS_INFO", LogicalOpcode::SMSG_TALENTS_INFO},
|
||||
{"CMSG_LEARN_TALENT", LogicalOpcode::CMSG_LEARN_TALENT},
|
||||
{"MSG_TALENT_WIPE_CONFIRM", LogicalOpcode::MSG_TALENT_WIPE_CONFIRM},
|
||||
{"CMSG_GROUP_INVITE", LogicalOpcode::CMSG_GROUP_INVITE},
|
||||
{"SMSG_GROUP_INVITE", LogicalOpcode::SMSG_GROUP_INVITE},
|
||||
{"CMSG_GROUP_ACCEPT", LogicalOpcode::CMSG_GROUP_ACCEPT},
|
||||
{"CMSG_GROUP_DECLINE", LogicalOpcode::CMSG_GROUP_DECLINE},
|
||||
{"SMSG_GROUP_DECLINE", LogicalOpcode::SMSG_GROUP_DECLINE},
|
||||
{"CMSG_GROUP_UNINVITE_GUID", LogicalOpcode::CMSG_GROUP_UNINVITE_GUID},
|
||||
{"SMSG_GROUP_UNINVITE", LogicalOpcode::SMSG_GROUP_UNINVITE},
|
||||
{"CMSG_GROUP_SET_LEADER", LogicalOpcode::CMSG_GROUP_SET_LEADER},
|
||||
{"SMSG_GROUP_SET_LEADER", LogicalOpcode::SMSG_GROUP_SET_LEADER},
|
||||
{"CMSG_GROUP_DISBAND", LogicalOpcode::CMSG_GROUP_DISBAND},
|
||||
{"SMSG_GROUP_LIST", LogicalOpcode::SMSG_GROUP_LIST},
|
||||
{"SMSG_PARTY_COMMAND_RESULT", LogicalOpcode::SMSG_PARTY_COMMAND_RESULT},
|
||||
{"MSG_RAID_TARGET_UPDATE", LogicalOpcode::MSG_RAID_TARGET_UPDATE},
|
||||
{"CMSG_REQUEST_RAID_INFO", LogicalOpcode::CMSG_REQUEST_RAID_INFO},
|
||||
{"SMSG_RAID_INSTANCE_INFO", LogicalOpcode::SMSG_RAID_INSTANCE_INFO},
|
||||
{"CMSG_AUTOSTORE_LOOT_ITEM", LogicalOpcode::CMSG_AUTOSTORE_LOOT_ITEM},
|
||||
{"CMSG_LOOT", LogicalOpcode::CMSG_LOOT},
|
||||
{"CMSG_LOOT_MONEY", LogicalOpcode::CMSG_LOOT_MONEY},
|
||||
{"CMSG_LOOT_RELEASE", LogicalOpcode::CMSG_LOOT_RELEASE},
|
||||
{"SMSG_LOOT_RESPONSE", LogicalOpcode::SMSG_LOOT_RESPONSE},
|
||||
{"SMSG_LOOT_RELEASE_RESPONSE", LogicalOpcode::SMSG_LOOT_RELEASE_RESPONSE},
|
||||
{"SMSG_LOOT_REMOVED", LogicalOpcode::SMSG_LOOT_REMOVED},
|
||||
{"SMSG_LOOT_MONEY_NOTIFY", LogicalOpcode::SMSG_LOOT_MONEY_NOTIFY},
|
||||
{"SMSG_LOOT_CLEAR_MONEY", LogicalOpcode::SMSG_LOOT_CLEAR_MONEY},
|
||||
{"CMSG_ACTIVATETAXI", LogicalOpcode::CMSG_ACTIVATETAXI},
|
||||
{"CMSG_GOSSIP_HELLO", LogicalOpcode::CMSG_GOSSIP_HELLO},
|
||||
{"CMSG_GOSSIP_SELECT_OPTION", LogicalOpcode::CMSG_GOSSIP_SELECT_OPTION},
|
||||
{"SMSG_GOSSIP_MESSAGE", LogicalOpcode::SMSG_GOSSIP_MESSAGE},
|
||||
{"SMSG_GOSSIP_COMPLETE", LogicalOpcode::SMSG_GOSSIP_COMPLETE},
|
||||
{"SMSG_NPC_TEXT_UPDATE", LogicalOpcode::SMSG_NPC_TEXT_UPDATE},
|
||||
{"CMSG_GAMEOBJECT_USE", LogicalOpcode::CMSG_GAMEOBJECT_USE},
|
||||
{"CMSG_QUESTGIVER_STATUS_QUERY", LogicalOpcode::CMSG_QUESTGIVER_STATUS_QUERY},
|
||||
{"SMSG_QUESTGIVER_STATUS", LogicalOpcode::SMSG_QUESTGIVER_STATUS},
|
||||
{"SMSG_QUESTGIVER_STATUS_MULTIPLE", LogicalOpcode::SMSG_QUESTGIVER_STATUS_MULTIPLE},
|
||||
{"CMSG_QUESTGIVER_HELLO", LogicalOpcode::CMSG_QUESTGIVER_HELLO},
|
||||
{"CMSG_QUESTGIVER_QUERY_QUEST", LogicalOpcode::CMSG_QUESTGIVER_QUERY_QUEST},
|
||||
{"SMSG_QUESTGIVER_QUEST_DETAILS", LogicalOpcode::SMSG_QUESTGIVER_QUEST_DETAILS},
|
||||
{"CMSG_QUESTGIVER_ACCEPT_QUEST", LogicalOpcode::CMSG_QUESTGIVER_ACCEPT_QUEST},
|
||||
{"CMSG_QUESTGIVER_COMPLETE_QUEST", LogicalOpcode::CMSG_QUESTGIVER_COMPLETE_QUEST},
|
||||
{"SMSG_QUESTGIVER_REQUEST_ITEMS", LogicalOpcode::SMSG_QUESTGIVER_REQUEST_ITEMS},
|
||||
{"CMSG_QUESTGIVER_REQUEST_REWARD", LogicalOpcode::CMSG_QUESTGIVER_REQUEST_REWARD},
|
||||
{"SMSG_QUESTGIVER_OFFER_REWARD", LogicalOpcode::SMSG_QUESTGIVER_OFFER_REWARD},
|
||||
{"CMSG_QUESTGIVER_CHOOSE_REWARD", LogicalOpcode::CMSG_QUESTGIVER_CHOOSE_REWARD},
|
||||
{"SMSG_QUESTGIVER_QUEST_INVALID", LogicalOpcode::SMSG_QUESTGIVER_QUEST_INVALID},
|
||||
{"SMSG_QUESTGIVER_QUEST_COMPLETE", LogicalOpcode::SMSG_QUESTGIVER_QUEST_COMPLETE},
|
||||
{"CMSG_QUESTLOG_REMOVE_QUEST", LogicalOpcode::CMSG_QUESTLOG_REMOVE_QUEST},
|
||||
{"SMSG_QUESTUPDATE_ADD_KILL", LogicalOpcode::SMSG_QUESTUPDATE_ADD_KILL},
|
||||
{"SMSG_QUESTUPDATE_COMPLETE", LogicalOpcode::SMSG_QUESTUPDATE_COMPLETE},
|
||||
{"CMSG_QUEST_QUERY", LogicalOpcode::CMSG_QUEST_QUERY},
|
||||
{"SMSG_QUEST_QUERY_RESPONSE", LogicalOpcode::SMSG_QUEST_QUERY_RESPONSE},
|
||||
{"SMSG_QUESTLOG_FULL", LogicalOpcode::SMSG_QUESTLOG_FULL},
|
||||
{"CMSG_LIST_INVENTORY", LogicalOpcode::CMSG_LIST_INVENTORY},
|
||||
{"SMSG_LIST_INVENTORY", LogicalOpcode::SMSG_LIST_INVENTORY},
|
||||
{"CMSG_SELL_ITEM", LogicalOpcode::CMSG_SELL_ITEM},
|
||||
{"SMSG_SELL_ITEM", LogicalOpcode::SMSG_SELL_ITEM},
|
||||
{"CMSG_BUY_ITEM", LogicalOpcode::CMSG_BUY_ITEM},
|
||||
{"SMSG_BUY_FAILED", LogicalOpcode::SMSG_BUY_FAILED},
|
||||
{"CMSG_TRAINER_LIST", LogicalOpcode::CMSG_TRAINER_LIST},
|
||||
{"SMSG_TRAINER_LIST", LogicalOpcode::SMSG_TRAINER_LIST},
|
||||
{"CMSG_TRAINER_BUY_SPELL", LogicalOpcode::CMSG_TRAINER_BUY_SPELL},
|
||||
{"SMSG_TRAINER_BUY_FAILED", LogicalOpcode::SMSG_TRAINER_BUY_FAILED},
|
||||
{"CMSG_ITEM_QUERY_SINGLE", LogicalOpcode::CMSG_ITEM_QUERY_SINGLE},
|
||||
{"SMSG_ITEM_QUERY_SINGLE_RESPONSE", LogicalOpcode::SMSG_ITEM_QUERY_SINGLE_RESPONSE},
|
||||
{"CMSG_USE_ITEM", LogicalOpcode::CMSG_USE_ITEM},
|
||||
{"CMSG_AUTOEQUIP_ITEM", LogicalOpcode::CMSG_AUTOEQUIP_ITEM},
|
||||
{"CMSG_SWAP_ITEM", LogicalOpcode::CMSG_SWAP_ITEM},
|
||||
{"CMSG_SWAP_INV_ITEM", LogicalOpcode::CMSG_SWAP_INV_ITEM},
|
||||
{"SMSG_INVENTORY_CHANGE_FAILURE", LogicalOpcode::SMSG_INVENTORY_CHANGE_FAILURE},
|
||||
{"CMSG_INSPECT", LogicalOpcode::CMSG_INSPECT},
|
||||
{"SMSG_INSPECT_RESULTS", LogicalOpcode::SMSG_INSPECT_RESULTS},
|
||||
{"CMSG_REPOP_REQUEST", LogicalOpcode::CMSG_REPOP_REQUEST},
|
||||
{"SMSG_RESURRECT_REQUEST", LogicalOpcode::SMSG_RESURRECT_REQUEST},
|
||||
{"CMSG_RESURRECT_RESPONSE", LogicalOpcode::CMSG_RESURRECT_RESPONSE},
|
||||
{"CMSG_SPIRIT_HEALER_ACTIVATE", LogicalOpcode::CMSG_SPIRIT_HEALER_ACTIVATE},
|
||||
{"SMSG_SPIRIT_HEALER_CONFIRM", LogicalOpcode::SMSG_SPIRIT_HEALER_CONFIRM},
|
||||
{"SMSG_RESURRECT_CANCEL", LogicalOpcode::SMSG_RESURRECT_CANCEL},
|
||||
{"MSG_MOVE_TELEPORT_ACK", LogicalOpcode::MSG_MOVE_TELEPORT_ACK},
|
||||
{"SMSG_TRANSFER_PENDING", LogicalOpcode::SMSG_TRANSFER_PENDING},
|
||||
{"SMSG_NEW_WORLD", LogicalOpcode::SMSG_NEW_WORLD},
|
||||
{"MSG_MOVE_WORLDPORT_ACK", LogicalOpcode::MSG_MOVE_WORLDPORT_ACK},
|
||||
{"SMSG_TRANSFER_ABORTED", LogicalOpcode::SMSG_TRANSFER_ABORTED},
|
||||
{"SMSG_FORCE_RUN_SPEED_CHANGE", LogicalOpcode::SMSG_FORCE_RUN_SPEED_CHANGE},
|
||||
{"CMSG_FORCE_RUN_SPEED_CHANGE_ACK", LogicalOpcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK},
|
||||
{"CMSG_CANCEL_MOUNT_AURA", LogicalOpcode::CMSG_CANCEL_MOUNT_AURA},
|
||||
{"SMSG_SHOWTAXINODES", LogicalOpcode::SMSG_SHOWTAXINODES},
|
||||
{"SMSG_ACTIVATETAXIREPLY", LogicalOpcode::SMSG_ACTIVATETAXIREPLY},
|
||||
{"SMSG_ACTIVATETAXIREPLY_ALT", LogicalOpcode::SMSG_ACTIVATETAXIREPLY_ALT},
|
||||
{"SMSG_NEW_TAXI_PATH", LogicalOpcode::SMSG_NEW_TAXI_PATH},
|
||||
{"CMSG_ACTIVATETAXIEXPRESS", LogicalOpcode::CMSG_ACTIVATETAXIEXPRESS},
|
||||
{"SMSG_BATTLEFIELD_PORT_DENIED", LogicalOpcode::SMSG_BATTLEFIELD_PORT_DENIED},
|
||||
{"SMSG_REMOVED_FROM_PVP_QUEUE", LogicalOpcode::SMSG_REMOVED_FROM_PVP_QUEUE},
|
||||
{"SMSG_TRAINER_BUY_SUCCEEDED", LogicalOpcode::SMSG_TRAINER_BUY_SUCCEEDED},
|
||||
{"SMSG_BINDPOINTUPDATE", LogicalOpcode::SMSG_BINDPOINTUPDATE},
|
||||
{"CMSG_BATTLEFIELD_LIST", LogicalOpcode::CMSG_BATTLEFIELD_LIST},
|
||||
{"SMSG_BATTLEFIELD_LIST", LogicalOpcode::SMSG_BATTLEFIELD_LIST},
|
||||
{"CMSG_BATTLEFIELD_JOIN", LogicalOpcode::CMSG_BATTLEFIELD_JOIN},
|
||||
{"CMSG_BATTLEFIELD_STATUS", LogicalOpcode::CMSG_BATTLEFIELD_STATUS},
|
||||
{"SMSG_BATTLEFIELD_STATUS", LogicalOpcode::SMSG_BATTLEFIELD_STATUS},
|
||||
{"CMSG_BATTLEFIELD_PORT", LogicalOpcode::CMSG_BATTLEFIELD_PORT},
|
||||
{"CMSG_BATTLEMASTER_HELLO", LogicalOpcode::CMSG_BATTLEMASTER_HELLO},
|
||||
{"MSG_PVP_LOG_DATA", LogicalOpcode::MSG_PVP_LOG_DATA},
|
||||
{"CMSG_LEAVE_BATTLEFIELD", LogicalOpcode::CMSG_LEAVE_BATTLEFIELD},
|
||||
{"SMSG_GROUP_JOINED_BATTLEGROUND", LogicalOpcode::SMSG_GROUP_JOINED_BATTLEGROUND},
|
||||
{"MSG_BATTLEGROUND_PLAYER_POSITIONS", LogicalOpcode::MSG_BATTLEGROUND_PLAYER_POSITIONS},
|
||||
{"SMSG_BATTLEGROUND_PLAYER_JOINED", LogicalOpcode::SMSG_BATTLEGROUND_PLAYER_JOINED},
|
||||
{"SMSG_BATTLEGROUND_PLAYER_LEFT", LogicalOpcode::SMSG_BATTLEGROUND_PLAYER_LEFT},
|
||||
{"CMSG_BATTLEMASTER_JOIN", LogicalOpcode::CMSG_BATTLEMASTER_JOIN},
|
||||
{"SMSG_JOINED_BATTLEGROUND_QUEUE", LogicalOpcode::SMSG_JOINED_BATTLEGROUND_QUEUE},
|
||||
{"CMSG_ARENA_TEAM_CREATE", LogicalOpcode::CMSG_ARENA_TEAM_CREATE},
|
||||
{"SMSG_ARENA_TEAM_COMMAND_RESULT", LogicalOpcode::SMSG_ARENA_TEAM_COMMAND_RESULT},
|
||||
{"CMSG_ARENA_TEAM_QUERY", LogicalOpcode::CMSG_ARENA_TEAM_QUERY},
|
||||
{"SMSG_ARENA_TEAM_QUERY_RESPONSE", LogicalOpcode::SMSG_ARENA_TEAM_QUERY_RESPONSE},
|
||||
{"CMSG_ARENA_TEAM_ROSTER", LogicalOpcode::CMSG_ARENA_TEAM_ROSTER},
|
||||
{"SMSG_ARENA_TEAM_ROSTER", LogicalOpcode::SMSG_ARENA_TEAM_ROSTER},
|
||||
{"CMSG_ARENA_TEAM_INVITE", LogicalOpcode::CMSG_ARENA_TEAM_INVITE},
|
||||
{"SMSG_ARENA_TEAM_INVITE", LogicalOpcode::SMSG_ARENA_TEAM_INVITE},
|
||||
{"CMSG_ARENA_TEAM_ACCEPT", LogicalOpcode::CMSG_ARENA_TEAM_ACCEPT},
|
||||
{"CMSG_ARENA_TEAM_DECLINE", LogicalOpcode::CMSG_ARENA_TEAM_DECLINE},
|
||||
{"CMSG_ARENA_TEAM_LEAVE", LogicalOpcode::CMSG_ARENA_TEAM_LEAVE},
|
||||
{"CMSG_ARENA_TEAM_REMOVE", LogicalOpcode::CMSG_ARENA_TEAM_REMOVE},
|
||||
{"CMSG_ARENA_TEAM_DISBAND", LogicalOpcode::CMSG_ARENA_TEAM_DISBAND},
|
||||
{"CMSG_ARENA_TEAM_LEADER", LogicalOpcode::CMSG_ARENA_TEAM_LEADER},
|
||||
{"SMSG_ARENA_TEAM_EVENT", LogicalOpcode::SMSG_ARENA_TEAM_EVENT},
|
||||
{"CMSG_BATTLEMASTER_JOIN_ARENA", LogicalOpcode::CMSG_BATTLEMASTER_JOIN_ARENA},
|
||||
{"SMSG_ARENA_TEAM_STATS", LogicalOpcode::SMSG_ARENA_TEAM_STATS},
|
||||
{"SMSG_ARENA_ERROR", LogicalOpcode::SMSG_ARENA_ERROR},
|
||||
{"MSG_INSPECT_ARENA_TEAMS", LogicalOpcode::MSG_INSPECT_ARENA_TEAMS},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static constexpr size_t kOpcodeNameCount = sizeof(kOpcodeNames) / sizeof(kOpcodeNames[0]);
|
||||
|
||||
std::optional<LogicalOpcode> OpcodeTable::nameToLogical(const std::string& name) {
|
||||
for (size_t i = 0; i < kOpcodeNameCount; ++i) {
|
||||
if (name == kOpcodeNames[i].name) return kOpcodeNames[i].op;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* OpcodeTable::logicalToName(LogicalOpcode op) {
|
||||
uint16_t val = static_cast<uint16_t>(op);
|
||||
for (size_t i = 0; i < kOpcodeNameCount; ++i) {
|
||||
if (static_cast<uint16_t>(kOpcodeNames[i].op) == val) return kOpcodeNames[i].name;
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
void OpcodeTable::loadWotlkDefaults() {
|
||||
// WotLK 3.3.5a wire values — matches the original hardcoded Opcode enum
|
||||
struct { LogicalOpcode op; uint16_t wire; } defaults[] = {
|
||||
{LogicalOpcode::CMSG_PING, 0x1DC},
|
||||
{LogicalOpcode::CMSG_AUTH_SESSION, 0x1ED},
|
||||
{LogicalOpcode::CMSG_CHAR_CREATE, 0x036},
|
||||
{LogicalOpcode::CMSG_CHAR_ENUM, 0x037},
|
||||
{LogicalOpcode::CMSG_CHAR_DELETE, 0x038},
|
||||
{LogicalOpcode::CMSG_PLAYER_LOGIN, 0x03D},
|
||||
{LogicalOpcode::CMSG_MOVE_START_FORWARD, 0x0B5},
|
||||
{LogicalOpcode::CMSG_MOVE_START_BACKWARD, 0x0B6},
|
||||
{LogicalOpcode::CMSG_MOVE_STOP, 0x0B7},
|
||||
{LogicalOpcode::CMSG_MOVE_START_STRAFE_LEFT, 0x0B8},
|
||||
{LogicalOpcode::CMSG_MOVE_START_STRAFE_RIGHT, 0x0B9},
|
||||
{LogicalOpcode::CMSG_MOVE_STOP_STRAFE, 0x0BA},
|
||||
{LogicalOpcode::CMSG_MOVE_JUMP, 0x0BB},
|
||||
{LogicalOpcode::CMSG_MOVE_START_TURN_LEFT, 0x0BC},
|
||||
{LogicalOpcode::CMSG_MOVE_START_TURN_RIGHT, 0x0BD},
|
||||
{LogicalOpcode::CMSG_MOVE_STOP_TURN, 0x0BE},
|
||||
{LogicalOpcode::CMSG_MOVE_SET_FACING, 0x0DA},
|
||||
{LogicalOpcode::CMSG_MOVE_FALL_LAND, 0x0C9},
|
||||
{LogicalOpcode::CMSG_MOVE_START_SWIM, 0x0CA},
|
||||
{LogicalOpcode::CMSG_MOVE_STOP_SWIM, 0x0CB},
|
||||
{LogicalOpcode::CMSG_MOVE_HEARTBEAT, 0x0EE},
|
||||
{LogicalOpcode::SMSG_AUTH_CHALLENGE, 0x1EC},
|
||||
{LogicalOpcode::SMSG_AUTH_RESPONSE, 0x1EE},
|
||||
{LogicalOpcode::SMSG_CHAR_CREATE, 0x03A},
|
||||
{LogicalOpcode::SMSG_CHAR_ENUM, 0x03B},
|
||||
{LogicalOpcode::SMSG_CHAR_DELETE, 0x03C},
|
||||
{LogicalOpcode::SMSG_PONG, 0x1DD},
|
||||
{LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD, 0x236},
|
||||
{LogicalOpcode::SMSG_LOGIN_SETTIMESPEED, 0x042},
|
||||
{LogicalOpcode::SMSG_TUTORIAL_FLAGS, 0x0FD},
|
||||
{LogicalOpcode::SMSG_WARDEN_DATA, 0x2E6},
|
||||
{LogicalOpcode::CMSG_WARDEN_DATA, 0x2E7},
|
||||
{LogicalOpcode::SMSG_ACCOUNT_DATA_TIMES, 0x209},
|
||||
{LogicalOpcode::SMSG_CLIENTCACHE_VERSION, 0x4AB},
|
||||
{LogicalOpcode::SMSG_FEATURE_SYSTEM_STATUS, 0x3ED},
|
||||
{LogicalOpcode::SMSG_MOTD, 0x33D},
|
||||
{LogicalOpcode::SMSG_UPDATE_OBJECT, 0x0A9},
|
||||
{LogicalOpcode::SMSG_COMPRESSED_UPDATE_OBJECT, 0x1F6},
|
||||
{LogicalOpcode::SMSG_MONSTER_MOVE_TRANSPORT, 0x2AE},
|
||||
{LogicalOpcode::SMSG_DESTROY_OBJECT, 0x0AA},
|
||||
{LogicalOpcode::CMSG_MESSAGECHAT, 0x095},
|
||||
{LogicalOpcode::SMSG_MESSAGECHAT, 0x096},
|
||||
{LogicalOpcode::CMSG_WHO, 0x062},
|
||||
{LogicalOpcode::SMSG_WHO, 0x063},
|
||||
{LogicalOpcode::CMSG_REQUEST_PLAYED_TIME, 0x1CC},
|
||||
{LogicalOpcode::SMSG_PLAYED_TIME, 0x1CD},
|
||||
{LogicalOpcode::CMSG_QUERY_TIME, 0x1CE},
|
||||
{LogicalOpcode::SMSG_QUERY_TIME_RESPONSE, 0x1CF},
|
||||
{LogicalOpcode::SMSG_FRIEND_STATUS, 0x068},
|
||||
{LogicalOpcode::CMSG_ADD_FRIEND, 0x069},
|
||||
{LogicalOpcode::CMSG_DEL_FRIEND, 0x06A},
|
||||
{LogicalOpcode::CMSG_SET_CONTACT_NOTES, 0x06B},
|
||||
{LogicalOpcode::CMSG_ADD_IGNORE, 0x06C},
|
||||
{LogicalOpcode::CMSG_DEL_IGNORE, 0x06D},
|
||||
{LogicalOpcode::CMSG_PLAYER_LOGOUT, 0x04A},
|
||||
{LogicalOpcode::CMSG_LOGOUT_REQUEST, 0x04B},
|
||||
{LogicalOpcode::CMSG_LOGOUT_CANCEL, 0x04E},
|
||||
{LogicalOpcode::SMSG_LOGOUT_RESPONSE, 0x04C},
|
||||
{LogicalOpcode::SMSG_LOGOUT_COMPLETE, 0x04D},
|
||||
{LogicalOpcode::CMSG_STAND_STATE_CHANGE, 0x101},
|
||||
{LogicalOpcode::CMSG_SHOWING_HELM, 0x2B9},
|
||||
{LogicalOpcode::CMSG_SHOWING_CLOAK, 0x2BA},
|
||||
{LogicalOpcode::CMSG_TOGGLE_PVP, 0x253},
|
||||
{LogicalOpcode::CMSG_GUILD_INVITE, 0x082},
|
||||
{LogicalOpcode::CMSG_GUILD_ACCEPT, 0x084},
|
||||
{LogicalOpcode::CMSG_GUILD_DECLINE_INVITATION, 0x085},
|
||||
{LogicalOpcode::CMSG_GUILD_INFO, 0x087},
|
||||
{LogicalOpcode::CMSG_GUILD_GET_ROSTER, 0x089},
|
||||
{LogicalOpcode::CMSG_GUILD_PROMOTE_MEMBER, 0x08B},
|
||||
{LogicalOpcode::CMSG_GUILD_DEMOTE_MEMBER, 0x08C},
|
||||
{LogicalOpcode::CMSG_GUILD_LEAVE, 0x08D},
|
||||
{LogicalOpcode::CMSG_GUILD_MOTD, 0x091},
|
||||
{LogicalOpcode::SMSG_GUILD_INFO, 0x088},
|
||||
{LogicalOpcode::SMSG_GUILD_ROSTER, 0x08A},
|
||||
{LogicalOpcode::MSG_RAID_READY_CHECK, 0x322},
|
||||
{LogicalOpcode::MSG_RAID_READY_CHECK_CONFIRM, 0x3AE},
|
||||
{LogicalOpcode::CMSG_DUEL_PROPOSED, 0x166},
|
||||
{LogicalOpcode::CMSG_DUEL_ACCEPTED, 0x16C},
|
||||
{LogicalOpcode::CMSG_DUEL_CANCELLED, 0x16D},
|
||||
{LogicalOpcode::SMSG_DUEL_REQUESTED, 0x167},
|
||||
{LogicalOpcode::CMSG_INITIATE_TRADE, 0x116},
|
||||
{LogicalOpcode::MSG_RANDOM_ROLL, 0x1FB},
|
||||
{LogicalOpcode::CMSG_SET_SELECTION, 0x13D},
|
||||
{LogicalOpcode::CMSG_NAME_QUERY, 0x050},
|
||||
{LogicalOpcode::SMSG_NAME_QUERY_RESPONSE, 0x051},
|
||||
{LogicalOpcode::CMSG_CREATURE_QUERY, 0x060},
|
||||
{LogicalOpcode::SMSG_CREATURE_QUERY_RESPONSE, 0x061},
|
||||
{LogicalOpcode::CMSG_GAMEOBJECT_QUERY, 0x05E},
|
||||
{LogicalOpcode::SMSG_GAMEOBJECT_QUERY_RESPONSE, 0x05F},
|
||||
{LogicalOpcode::CMSG_SET_ACTIVE_MOVER, 0x26A},
|
||||
{LogicalOpcode::CMSG_BINDER_ACTIVATE, 0x1B5},
|
||||
{LogicalOpcode::SMSG_LOG_XPGAIN, 0x1D0},
|
||||
{LogicalOpcode::SMSG_MONSTER_MOVE, 0x0DD},
|
||||
{LogicalOpcode::CMSG_ATTACKSWING, 0x141},
|
||||
{LogicalOpcode::CMSG_ATTACKSTOP, 0x142},
|
||||
{LogicalOpcode::SMSG_ATTACKSTART, 0x143},
|
||||
{LogicalOpcode::SMSG_ATTACKSTOP, 0x144},
|
||||
{LogicalOpcode::SMSG_ATTACKERSTATEUPDATE, 0x14A},
|
||||
{LogicalOpcode::SMSG_SPELLNONMELEEDAMAGELOG, 0x250},
|
||||
{LogicalOpcode::SMSG_SPELLHEALLOG, 0x150},
|
||||
{LogicalOpcode::SMSG_SPELLENERGIZELOG, 0x25B},
|
||||
{LogicalOpcode::SMSG_PERIODICAURALOG, 0x24E},
|
||||
{LogicalOpcode::SMSG_ENVIRONMENTALDAMAGELOG, 0x1FC},
|
||||
{LogicalOpcode::CMSG_CAST_SPELL, 0x12E},
|
||||
{LogicalOpcode::CMSG_CANCEL_CAST, 0x12F},
|
||||
{LogicalOpcode::CMSG_CANCEL_AURA, 0x033},
|
||||
{LogicalOpcode::SMSG_CAST_FAILED, 0x130},
|
||||
{LogicalOpcode::SMSG_SPELL_START, 0x131},
|
||||
{LogicalOpcode::SMSG_SPELL_GO, 0x132},
|
||||
{LogicalOpcode::SMSG_SPELL_FAILURE, 0x133},
|
||||
{LogicalOpcode::SMSG_SPELL_COOLDOWN, 0x134},
|
||||
{LogicalOpcode::SMSG_COOLDOWN_EVENT, 0x135},
|
||||
{LogicalOpcode::SMSG_UPDATE_AURA_DURATION, 0x137},
|
||||
{LogicalOpcode::SMSG_INITIAL_SPELLS, 0x12A},
|
||||
{LogicalOpcode::SMSG_LEARNED_SPELL, 0x12B},
|
||||
{LogicalOpcode::SMSG_SUPERCEDED_SPELL, 0x12C},
|
||||
{LogicalOpcode::SMSG_REMOVED_SPELL, 0x203},
|
||||
{LogicalOpcode::SMSG_SEND_UNLEARN_SPELLS, 0x41F},
|
||||
{LogicalOpcode::SMSG_SPELL_DELAYED, 0x1E2},
|
||||
{LogicalOpcode::SMSG_AURA_UPDATE, 0x3FA},
|
||||
{LogicalOpcode::SMSG_AURA_UPDATE_ALL, 0x495},
|
||||
{LogicalOpcode::SMSG_SET_FLAT_SPELL_MODIFIER, 0x266},
|
||||
{LogicalOpcode::SMSG_SET_PCT_SPELL_MODIFIER, 0x267},
|
||||
{LogicalOpcode::SMSG_TALENTS_INFO, 0x4C0},
|
||||
{LogicalOpcode::CMSG_LEARN_TALENT, 0x251},
|
||||
{LogicalOpcode::MSG_TALENT_WIPE_CONFIRM, 0x2AB},
|
||||
{LogicalOpcode::CMSG_GROUP_INVITE, 0x06E},
|
||||
{LogicalOpcode::SMSG_GROUP_INVITE, 0x06F},
|
||||
{LogicalOpcode::CMSG_GROUP_ACCEPT, 0x072},
|
||||
{LogicalOpcode::CMSG_GROUP_DECLINE, 0x073},
|
||||
{LogicalOpcode::SMSG_GROUP_DECLINE, 0x074},
|
||||
{LogicalOpcode::CMSG_GROUP_UNINVITE_GUID, 0x076},
|
||||
{LogicalOpcode::SMSG_GROUP_UNINVITE, 0x077},
|
||||
{LogicalOpcode::CMSG_GROUP_SET_LEADER, 0x078},
|
||||
{LogicalOpcode::SMSG_GROUP_SET_LEADER, 0x079},
|
||||
{LogicalOpcode::CMSG_GROUP_DISBAND, 0x07B},
|
||||
{LogicalOpcode::SMSG_GROUP_LIST, 0x07D},
|
||||
{LogicalOpcode::SMSG_PARTY_COMMAND_RESULT, 0x07E},
|
||||
{LogicalOpcode::MSG_RAID_TARGET_UPDATE, 0x321},
|
||||
{LogicalOpcode::CMSG_REQUEST_RAID_INFO, 0x2CD},
|
||||
{LogicalOpcode::SMSG_RAID_INSTANCE_INFO, 0x2CC},
|
||||
{LogicalOpcode::CMSG_AUTOSTORE_LOOT_ITEM, 0x108},
|
||||
{LogicalOpcode::CMSG_LOOT, 0x15D},
|
||||
{LogicalOpcode::CMSG_LOOT_MONEY, 0x15E},
|
||||
{LogicalOpcode::CMSG_LOOT_RELEASE, 0x15F},
|
||||
{LogicalOpcode::SMSG_LOOT_RESPONSE, 0x160},
|
||||
{LogicalOpcode::SMSG_LOOT_RELEASE_RESPONSE, 0x161},
|
||||
{LogicalOpcode::SMSG_LOOT_REMOVED, 0x162},
|
||||
{LogicalOpcode::SMSG_LOOT_MONEY_NOTIFY, 0x163},
|
||||
{LogicalOpcode::SMSG_LOOT_CLEAR_MONEY, 0x165},
|
||||
{LogicalOpcode::CMSG_ACTIVATETAXI, 0x19D},
|
||||
{LogicalOpcode::CMSG_GOSSIP_HELLO, 0x17B},
|
||||
{LogicalOpcode::CMSG_GOSSIP_SELECT_OPTION, 0x17C},
|
||||
{LogicalOpcode::SMSG_GOSSIP_MESSAGE, 0x17D},
|
||||
{LogicalOpcode::SMSG_GOSSIP_COMPLETE, 0x17E},
|
||||
{LogicalOpcode::SMSG_NPC_TEXT_UPDATE, 0x180},
|
||||
{LogicalOpcode::CMSG_GAMEOBJECT_USE, 0x01B},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_STATUS_QUERY, 0x182},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_STATUS, 0x183},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_STATUS_MULTIPLE, 0x198},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_HELLO, 0x184},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_QUERY_QUEST, 0x186},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_QUEST_DETAILS, 0x188},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_ACCEPT_QUEST, 0x189},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_COMPLETE_QUEST, 0x18A},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_REQUEST_ITEMS, 0x18B},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_REQUEST_REWARD, 0x18C},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_OFFER_REWARD, 0x18D},
|
||||
{LogicalOpcode::CMSG_QUESTGIVER_CHOOSE_REWARD, 0x18E},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_QUEST_INVALID, 0x18F},
|
||||
{LogicalOpcode::SMSG_QUESTGIVER_QUEST_COMPLETE, 0x191},
|
||||
{LogicalOpcode::CMSG_QUESTLOG_REMOVE_QUEST, 0x194},
|
||||
{LogicalOpcode::SMSG_QUESTUPDATE_ADD_KILL, 0x196},
|
||||
{LogicalOpcode::SMSG_QUESTUPDATE_COMPLETE, 0x195},
|
||||
{LogicalOpcode::CMSG_QUEST_QUERY, 0x05C},
|
||||
{LogicalOpcode::SMSG_QUEST_QUERY_RESPONSE, 0x05D},
|
||||
{LogicalOpcode::SMSG_QUESTLOG_FULL, 0x1A3},
|
||||
{LogicalOpcode::CMSG_LIST_INVENTORY, 0x19E},
|
||||
{LogicalOpcode::SMSG_LIST_INVENTORY, 0x19F},
|
||||
{LogicalOpcode::CMSG_SELL_ITEM, 0x1A0},
|
||||
{LogicalOpcode::SMSG_SELL_ITEM, 0x1A1},
|
||||
{LogicalOpcode::CMSG_BUY_ITEM, 0x1A2},
|
||||
{LogicalOpcode::SMSG_BUY_FAILED, 0x1A5},
|
||||
{LogicalOpcode::CMSG_TRAINER_LIST, 0x01B0},
|
||||
{LogicalOpcode::SMSG_TRAINER_LIST, 0x01B1},
|
||||
{LogicalOpcode::CMSG_TRAINER_BUY_SPELL, 0x01B2},
|
||||
{LogicalOpcode::SMSG_TRAINER_BUY_FAILED, 0x01B4},
|
||||
{LogicalOpcode::CMSG_ITEM_QUERY_SINGLE, 0x056},
|
||||
{LogicalOpcode::SMSG_ITEM_QUERY_SINGLE_RESPONSE, 0x058},
|
||||
{LogicalOpcode::CMSG_USE_ITEM, 0x00AB},
|
||||
{LogicalOpcode::CMSG_AUTOEQUIP_ITEM, 0x10A},
|
||||
{LogicalOpcode::CMSG_SWAP_ITEM, 0x10C},
|
||||
{LogicalOpcode::CMSG_SWAP_INV_ITEM, 0x10D},
|
||||
{LogicalOpcode::SMSG_INVENTORY_CHANGE_FAILURE, 0x112},
|
||||
{LogicalOpcode::CMSG_INSPECT, 0x114},
|
||||
{LogicalOpcode::SMSG_INSPECT_RESULTS, 0x115},
|
||||
{LogicalOpcode::CMSG_REPOP_REQUEST, 0x015A},
|
||||
{LogicalOpcode::SMSG_RESURRECT_REQUEST, 0x015B},
|
||||
{LogicalOpcode::CMSG_RESURRECT_RESPONSE, 0x015C},
|
||||
{LogicalOpcode::CMSG_SPIRIT_HEALER_ACTIVATE, 0x021C},
|
||||
{LogicalOpcode::SMSG_SPIRIT_HEALER_CONFIRM, 0x0222},
|
||||
{LogicalOpcode::SMSG_RESURRECT_CANCEL, 0x0390},
|
||||
{LogicalOpcode::MSG_MOVE_TELEPORT_ACK, 0x0C7},
|
||||
{LogicalOpcode::SMSG_TRANSFER_PENDING, 0x003F},
|
||||
{LogicalOpcode::SMSG_NEW_WORLD, 0x003E},
|
||||
{LogicalOpcode::MSG_MOVE_WORLDPORT_ACK, 0x00DC},
|
||||
{LogicalOpcode::SMSG_TRANSFER_ABORTED, 0x0040},
|
||||
{LogicalOpcode::SMSG_FORCE_RUN_SPEED_CHANGE, 0x00E2},
|
||||
{LogicalOpcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK, 0x00E3},
|
||||
{LogicalOpcode::CMSG_CANCEL_MOUNT_AURA, 0x0375},
|
||||
{LogicalOpcode::SMSG_SHOWTAXINODES, 0x01A9},
|
||||
{LogicalOpcode::SMSG_ACTIVATETAXIREPLY, 0x01AE},
|
||||
{LogicalOpcode::SMSG_ACTIVATETAXIREPLY_ALT, 0x029D},
|
||||
{LogicalOpcode::SMSG_NEW_TAXI_PATH, 0x01AF},
|
||||
{LogicalOpcode::CMSG_ACTIVATETAXIEXPRESS, 0x0312},
|
||||
{LogicalOpcode::SMSG_BATTLEFIELD_PORT_DENIED, 0x014B},
|
||||
{LogicalOpcode::SMSG_REMOVED_FROM_PVP_QUEUE, 0x0170},
|
||||
{LogicalOpcode::SMSG_TRAINER_BUY_SUCCEEDED, 0x01B3},
|
||||
{LogicalOpcode::SMSG_BINDPOINTUPDATE, 0x0155},
|
||||
{LogicalOpcode::CMSG_BATTLEFIELD_LIST, 0x023C},
|
||||
{LogicalOpcode::SMSG_BATTLEFIELD_LIST, 0x023D},
|
||||
{LogicalOpcode::CMSG_BATTLEFIELD_JOIN, 0x023E},
|
||||
{LogicalOpcode::CMSG_BATTLEFIELD_STATUS, 0x02D3},
|
||||
{LogicalOpcode::SMSG_BATTLEFIELD_STATUS, 0x02D4},
|
||||
{LogicalOpcode::CMSG_BATTLEFIELD_PORT, 0x02D5},
|
||||
{LogicalOpcode::CMSG_BATTLEMASTER_HELLO, 0x02D7},
|
||||
{LogicalOpcode::MSG_PVP_LOG_DATA, 0x02E0},
|
||||
{LogicalOpcode::CMSG_LEAVE_BATTLEFIELD, 0x02E1},
|
||||
{LogicalOpcode::SMSG_GROUP_JOINED_BATTLEGROUND, 0x02E8},
|
||||
{LogicalOpcode::MSG_BATTLEGROUND_PLAYER_POSITIONS, 0x02E9},
|
||||
{LogicalOpcode::SMSG_BATTLEGROUND_PLAYER_JOINED, 0x02EC},
|
||||
{LogicalOpcode::SMSG_BATTLEGROUND_PLAYER_LEFT, 0x02ED},
|
||||
{LogicalOpcode::CMSG_BATTLEMASTER_JOIN, 0x02EE},
|
||||
{LogicalOpcode::SMSG_JOINED_BATTLEGROUND_QUEUE, 0x038A},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_CREATE, 0x0348},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_COMMAND_RESULT, 0x0349},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_QUERY, 0x034B},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_QUERY_RESPONSE, 0x034C},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_ROSTER, 0x034D},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_ROSTER, 0x034E},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_INVITE, 0x034F},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_INVITE, 0x0350},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_ACCEPT, 0x0351},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_DECLINE, 0x0352},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_LEAVE, 0x0353},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_REMOVE, 0x0354},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_DISBAND, 0x0355},
|
||||
{LogicalOpcode::CMSG_ARENA_TEAM_LEADER, 0x0356},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_EVENT, 0x0357},
|
||||
{LogicalOpcode::CMSG_BATTLEMASTER_JOIN_ARENA, 0x0358},
|
||||
{LogicalOpcode::SMSG_ARENA_TEAM_STATS, 0x035B},
|
||||
{LogicalOpcode::SMSG_ARENA_ERROR, 0x0376},
|
||||
{LogicalOpcode::MSG_INSPECT_ARENA_TEAMS, 0x0377},
|
||||
};
|
||||
|
||||
logicalToWire_.clear();
|
||||
wireToLogical_.clear();
|
||||
for (auto& d : defaults) {
|
||||
uint16_t logIdx = static_cast<uint16_t>(d.op);
|
||||
logicalToWire_[logIdx] = d.wire;
|
||||
wireToLogical_[d.wire] = logIdx;
|
||||
}
|
||||
LOG_INFO("OpcodeTable: loaded ", logicalToWire_.size(), " WotLK default opcodes");
|
||||
}
|
||||
|
||||
bool OpcodeTable::loadFromJson(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) {
|
||||
LOG_WARNING("OpcodeTable: cannot open ", path, ", using defaults");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
logicalToWire_.clear();
|
||||
wireToLogical_.clear();
|
||||
|
||||
// Parse simple JSON: { "NAME": "0xHEX", ... } or { "NAME": 123, ... }
|
||||
size_t pos = 0;
|
||||
size_t loaded = 0;
|
||||
while (pos < json.size()) {
|
||||
// Find next quoted key
|
||||
size_t keyStart = json.find('"', pos);
|
||||
if (keyStart == std::string::npos) break;
|
||||
size_t keyEnd = json.find('"', keyStart + 1);
|
||||
if (keyEnd == std::string::npos) break;
|
||||
std::string key = json.substr(keyStart + 1, keyEnd - keyStart - 1);
|
||||
|
||||
// Find colon then value
|
||||
size_t colon = json.find(':', keyEnd);
|
||||
if (colon == std::string::npos) break;
|
||||
|
||||
// Skip whitespace
|
||||
size_t valStart = colon + 1;
|
||||
while (valStart < json.size() && (json[valStart] == ' ' || json[valStart] == '\t' ||
|
||||
json[valStart] == '\r' || json[valStart] == '\n' || json[valStart] == '"'))
|
||||
++valStart;
|
||||
|
||||
size_t valEnd = json.find_first_of(",}\"\r\n", valStart);
|
||||
if (valEnd == std::string::npos) valEnd = json.size();
|
||||
std::string valStr = json.substr(valStart, valEnd - valStart);
|
||||
|
||||
// Parse hex or decimal value
|
||||
uint16_t wire = 0;
|
||||
try {
|
||||
if (valStr.size() > 2 && (valStr[0] == '0' && (valStr[1] == 'x' || valStr[1] == 'X'))) {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr, nullptr, 16));
|
||||
} else {
|
||||
wire = static_cast<uint16_t>(std::stoul(valStr));
|
||||
}
|
||||
} catch (...) {
|
||||
pos = valEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto logOp = nameToLogical(key);
|
||||
if (logOp) {
|
||||
uint16_t logIdx = static_cast<uint16_t>(*logOp);
|
||||
logicalToWire_[logIdx] = wire;
|
||||
wireToLogical_[wire] = logIdx;
|
||||
++loaded;
|
||||
}
|
||||
|
||||
pos = valEnd + 1;
|
||||
}
|
||||
|
||||
LOG_INFO("OpcodeTable: loaded ", loaded, " opcodes from ", path);
|
||||
return loaded > 0;
|
||||
}
|
||||
|
||||
uint16_t OpcodeTable::toWire(LogicalOpcode op) const {
|
||||
auto it = logicalToWire_.find(static_cast<uint16_t>(op));
|
||||
return (it != logicalToWire_.end()) ? it->second : 0xFFFF;
|
||||
}
|
||||
|
||||
std::optional<LogicalOpcode> OpcodeTable::fromWire(uint16_t wireValue) const {
|
||||
auto it = wireToLogical_.find(wireValue);
|
||||
if (it != wireToLogical_.end()) {
|
||||
return static_cast<LogicalOpcode>(it->second);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool OpcodeTable::hasOpcode(LogicalOpcode op) const {
|
||||
return logicalToWire_.count(static_cast<uint16_t>(op)) > 0;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
344
src/game/packet_parsers_classic.cpp
Normal file
344
src/game/packet_parsers_classic.cpp
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
#include "game/packet_parsers.hpp"
|
||||
#include "core/logger.hpp"
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
||||
// ============================================================================
|
||||
// Classic 1.12.1 movement flag constants
|
||||
// Key differences from TBC:
|
||||
// - SPLINE_ENABLED at 0x00400000 (TBC/WotLK: 0x08000000)
|
||||
// - No FLYING flag (flight was added in TBC)
|
||||
// - ONTRANSPORT at 0x02000000 (not used for pitch in Classic)
|
||||
// Same as TBC: ON_TRANSPORT=0x200, JUMPING=0x2000, SWIMMING=0x200000,
|
||||
// SPLINE_ELEVATION=0x04000000
|
||||
// ============================================================================
|
||||
namespace ClassicMoveFlags {
|
||||
constexpr uint32_t ONTRANSPORT = 0x02000000; // Gates transport data (vmangos authoritative)
|
||||
constexpr uint32_t JUMPING = 0x00002000; // Gates jump data
|
||||
constexpr uint32_t SWIMMING = 0x00200000; // Gates pitch
|
||||
constexpr uint32_t SPLINE_ENABLED = 0x00400000; // TBC/WotLK: 0x08000000
|
||||
constexpr uint32_t SPLINE_ELEVATION = 0x04000000; // Same as TBC
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classic parseMovementBlock
|
||||
// Key differences from TBC:
|
||||
// - NO moveFlags2 (TBC reads u8, WotLK reads u16)
|
||||
// - SPLINE_ENABLED at 0x00400000 (not 0x08000000)
|
||||
// - Transport data: NO timestamp (TBC adds u32 timestamp)
|
||||
// - Pitch: only SWIMMING (no ONTRANSPORT secondary pitch, no FLYING)
|
||||
// Same as TBC: u8 UpdateFlags, JUMPING=0x2000, 8 speeds, no pitchRate
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseMovementBlock(network::Packet& packet, UpdateBlock& block) {
|
||||
// Classic: UpdateFlags is uint8 (same as TBC)
|
||||
uint8_t updateFlags = packet.readUInt8();
|
||||
block.updateFlags = static_cast<uint16_t>(updateFlags);
|
||||
|
||||
LOG_DEBUG(" [Classic] UpdateFlags: 0x", std::hex, (int)updateFlags, std::dec);
|
||||
|
||||
const uint8_t UPDATEFLAG_LIVING = 0x20;
|
||||
const uint8_t UPDATEFLAG_HAS_POSITION = 0x40;
|
||||
const uint8_t UPDATEFLAG_HAS_TARGET = 0x04;
|
||||
const uint8_t UPDATEFLAG_TRANSPORT = 0x02;
|
||||
const uint8_t UPDATEFLAG_LOWGUID = 0x08;
|
||||
const uint8_t UPDATEFLAG_HIGHGUID = 0x10;
|
||||
|
||||
if (updateFlags & UPDATEFLAG_LIVING) {
|
||||
// Movement flags (u32 only — NO extra flags byte in Classic)
|
||||
uint32_t moveFlags = packet.readUInt32();
|
||||
/*uint32_t time =*/ packet.readUInt32();
|
||||
|
||||
// Position
|
||||
block.x = packet.readFloat();
|
||||
block.y = packet.readFloat();
|
||||
block.z = packet.readFloat();
|
||||
block.orientation = packet.readFloat();
|
||||
block.hasMovement = true;
|
||||
|
||||
LOG_DEBUG(" [Classic] LIVING: (", block.x, ", ", block.y, ", ", block.z,
|
||||
"), o=", block.orientation, " moveFlags=0x", std::hex, moveFlags, std::dec);
|
||||
|
||||
// Transport data (Classic: ONTRANSPORT=0x02000000, no timestamp)
|
||||
if (moveFlags & ClassicMoveFlags::ONTRANSPORT) {
|
||||
block.onTransport = true;
|
||||
block.transportGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
block.transportX = packet.readFloat();
|
||||
block.transportY = packet.readFloat();
|
||||
block.transportZ = packet.readFloat();
|
||||
block.transportO = packet.readFloat();
|
||||
// Classic: NO transport timestamp (TBC adds u32 timestamp)
|
||||
// Classic: NO transport seat byte
|
||||
}
|
||||
|
||||
// Pitch (Classic: only SWIMMING, no FLYING or ONTRANSPORT pitch)
|
||||
if (moveFlags & ClassicMoveFlags::SWIMMING) {
|
||||
/*float pitch =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Fall time (always present)
|
||||
/*uint32_t fallTime =*/ packet.readUInt32();
|
||||
|
||||
// Jumping (Classic: JUMPING=0x2000, same as TBC)
|
||||
if (moveFlags & ClassicMoveFlags::JUMPING) {
|
||||
/*float jumpVelocity =*/ packet.readFloat();
|
||||
/*float jumpSinAngle =*/ packet.readFloat();
|
||||
/*float jumpCosAngle =*/ packet.readFloat();
|
||||
/*float jumpXYSpeed =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Spline elevation
|
||||
if (moveFlags & ClassicMoveFlags::SPLINE_ELEVATION) {
|
||||
/*float splineElevation =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Speeds (Classic: 6 values — no flight speeds, no pitchRate)
|
||||
// TBC added flying_speed + backwards_flying_speed (8 total)
|
||||
// WotLK added pitchRate (9 total)
|
||||
/*float walkSpeed =*/ packet.readFloat();
|
||||
float runSpeed = packet.readFloat();
|
||||
/*float runBackSpeed =*/ packet.readFloat();
|
||||
/*float swimSpeed =*/ packet.readFloat();
|
||||
/*float swimBackSpeed =*/ packet.readFloat();
|
||||
/*float turnRate =*/ packet.readFloat();
|
||||
|
||||
block.runSpeed = runSpeed;
|
||||
|
||||
// Spline data (Classic: SPLINE_ENABLED=0x00400000)
|
||||
if (moveFlags & ClassicMoveFlags::SPLINE_ENABLED) {
|
||||
uint32_t splineFlags = packet.readUInt32();
|
||||
LOG_DEBUG(" [Classic] Spline: flags=0x", std::hex, splineFlags, std::dec);
|
||||
|
||||
if (splineFlags & 0x00010000) { // FINAL_POINT
|
||||
/*float finalX =*/ packet.readFloat();
|
||||
/*float finalY =*/ packet.readFloat();
|
||||
/*float finalZ =*/ packet.readFloat();
|
||||
} else if (splineFlags & 0x00020000) { // FINAL_TARGET
|
||||
/*uint64_t finalTarget =*/ packet.readUInt64();
|
||||
} else if (splineFlags & 0x00040000) { // FINAL_ANGLE
|
||||
/*float finalAngle =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Classic spline: timePassed, duration, id, nodes, finalNode (same as TBC)
|
||||
/*uint32_t timePassed =*/ packet.readUInt32();
|
||||
/*uint32_t duration =*/ packet.readUInt32();
|
||||
/*uint32_t splineId =*/ packet.readUInt32();
|
||||
|
||||
uint32_t pointCount = packet.readUInt32();
|
||||
if (pointCount > 256) {
|
||||
LOG_WARNING(" [Classic] Spline pointCount=", pointCount, " exceeds max, capping");
|
||||
pointCount = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < pointCount; i++) {
|
||||
/*float px =*/ packet.readFloat();
|
||||
/*float py =*/ packet.readFloat();
|
||||
/*float pz =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Classic: NO splineMode byte
|
||||
/*float endPointX =*/ packet.readFloat();
|
||||
/*float endPointY =*/ packet.readFloat();
|
||||
/*float endPointZ =*/ packet.readFloat();
|
||||
}
|
||||
}
|
||||
else if (updateFlags & UPDATEFLAG_HAS_POSITION) {
|
||||
block.x = packet.readFloat();
|
||||
block.y = packet.readFloat();
|
||||
block.z = packet.readFloat();
|
||||
block.orientation = packet.readFloat();
|
||||
block.hasMovement = true;
|
||||
|
||||
LOG_DEBUG(" [Classic] STATIONARY: (", block.x, ", ", block.y, ", ", block.z, ")");
|
||||
}
|
||||
|
||||
// Target GUID
|
||||
if (updateFlags & UPDATEFLAG_HAS_TARGET) {
|
||||
/*uint64_t targetGuid =*/ UpdateObjectParser::readPackedGuid(packet);
|
||||
}
|
||||
|
||||
// Transport time
|
||||
if (updateFlags & UPDATEFLAG_TRANSPORT) {
|
||||
/*uint32_t transportTime =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
// Low GUID
|
||||
if (updateFlags & UPDATEFLAG_LOWGUID) {
|
||||
/*uint32_t lowGuid =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
// High GUID
|
||||
if (updateFlags & UPDATEFLAG_HIGHGUID) {
|
||||
/*uint32_t highGuid =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classic writeMovementPayload
|
||||
// Key differences from TBC:
|
||||
// - NO flags2 byte (TBC writes u8)
|
||||
// - Transport data: NO timestamp
|
||||
// - Pitch: only SWIMMING (no ONTRANSPORT pitch)
|
||||
// ============================================================================
|
||||
void ClassicPacketParsers::writeMovementPayload(network::Packet& packet, const MovementInfo& info) {
|
||||
// Movement flags (uint32)
|
||||
packet.writeUInt32(info.flags);
|
||||
|
||||
// Classic: NO flags2 byte (TBC has u8, WotLK has u16)
|
||||
|
||||
// Timestamp
|
||||
packet.writeUInt32(info.time);
|
||||
|
||||
// Position
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.x), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.y), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.z), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.orientation), sizeof(float));
|
||||
|
||||
// Transport data (Classic ONTRANSPORT = 0x02000000, no timestamp)
|
||||
if (info.flags & ClassicMoveFlags::ONTRANSPORT) {
|
||||
// Packed transport GUID
|
||||
uint8_t transMask = 0;
|
||||
uint8_t transGuidBytes[8];
|
||||
int transGuidByteCount = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint8_t byte = static_cast<uint8_t>((info.transportGuid >> (i * 8)) & 0xFF);
|
||||
if (byte != 0) {
|
||||
transMask |= (1 << i);
|
||||
transGuidBytes[transGuidByteCount++] = byte;
|
||||
}
|
||||
}
|
||||
packet.writeUInt8(transMask);
|
||||
for (int i = 0; i < transGuidByteCount; i++) {
|
||||
packet.writeUInt8(transGuidBytes[i]);
|
||||
}
|
||||
|
||||
// Transport local position
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportX), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportY), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportZ), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportO), sizeof(float));
|
||||
|
||||
// Classic: NO transport timestamp
|
||||
// Classic: NO transport seat byte
|
||||
}
|
||||
|
||||
// Pitch (Classic: only SWIMMING)
|
||||
if (info.flags & ClassicMoveFlags::SWIMMING) {
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.pitch), sizeof(float));
|
||||
}
|
||||
|
||||
// Fall time (always present)
|
||||
packet.writeUInt32(info.fallTime);
|
||||
|
||||
// Jump data (Classic JUMPING = 0x2000)
|
||||
if (info.flags & ClassicMoveFlags::JUMPING) {
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpVelocity), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpSinAngle), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpCosAngle), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpXYSpeed), sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classic buildMovementPacket
|
||||
// Classic/TBC: client movement packets do NOT include PackedGuid prefix
|
||||
// (WotLK added PackedGuid to client packets)
|
||||
// ============================================================================
|
||||
network::Packet ClassicPacketParsers::buildMovementPacket(LogicalOpcode opcode,
|
||||
const MovementInfo& info,
|
||||
uint64_t /*playerGuid*/) {
|
||||
network::Packet packet(wireOpcode(opcode));
|
||||
|
||||
// Classic: NO PackedGuid prefix for client packets
|
||||
writeMovementPayload(packet, info);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classic 1.12.1 parseCharEnum
|
||||
// Differences from TBC:
|
||||
// - Equipment: 20 items, but NO enchantment field per slot
|
||||
// Classic: displayId(u32) + inventoryType(u8) = 5 bytes/slot
|
||||
// TBC/WotLK: displayId(u32) + inventoryType(u8) + enchant(u32) = 9 bytes/slot
|
||||
// - After flags: uint8 firstLogin (same as TBC)
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseCharEnum(network::Packet& packet, CharEnumResponse& response) {
|
||||
uint8_t count = packet.readUInt8();
|
||||
|
||||
LOG_INFO("[Classic] Parsing SMSG_CHAR_ENUM: ", (int)count, " characters");
|
||||
|
||||
response.characters.clear();
|
||||
response.characters.reserve(count);
|
||||
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
Character character;
|
||||
|
||||
// GUID (8 bytes)
|
||||
character.guid = packet.readUInt64();
|
||||
|
||||
// Name (null-terminated string)
|
||||
character.name = packet.readString();
|
||||
|
||||
// Race, class, gender
|
||||
character.race = static_cast<Race>(packet.readUInt8());
|
||||
character.characterClass = static_cast<Class>(packet.readUInt8());
|
||||
character.gender = static_cast<Gender>(packet.readUInt8());
|
||||
|
||||
// Appearance (5 bytes: skin, face, hairStyle, hairColor packed + facialFeatures)
|
||||
character.appearanceBytes = packet.readUInt32();
|
||||
character.facialFeatures = packet.readUInt8();
|
||||
|
||||
// Level
|
||||
character.level = packet.readUInt8();
|
||||
|
||||
// Location
|
||||
character.zoneId = packet.readUInt32();
|
||||
character.mapId = packet.readUInt32();
|
||||
character.x = packet.readFloat();
|
||||
character.y = packet.readFloat();
|
||||
character.z = packet.readFloat();
|
||||
|
||||
// Guild ID
|
||||
character.guildId = packet.readUInt32();
|
||||
|
||||
// Flags
|
||||
character.flags = packet.readUInt32();
|
||||
|
||||
// Classic: uint8 firstLogin (same as TBC)
|
||||
/*uint8_t firstLogin =*/ packet.readUInt8();
|
||||
|
||||
// Pet data (always present)
|
||||
character.pet.displayModel = packet.readUInt32();
|
||||
character.pet.level = packet.readUInt32();
|
||||
character.pet.family = packet.readUInt32();
|
||||
|
||||
// Equipment (Classic: 20 items, NO enchantment field)
|
||||
character.equipment.reserve(20);
|
||||
for (int j = 0; j < 20; ++j) {
|
||||
EquipmentItem item;
|
||||
item.displayModel = packet.readUInt32();
|
||||
item.inventoryType = packet.readUInt8();
|
||||
item.enchantment = 0; // Classic has no enchant field in char enum
|
||||
character.equipment.push_back(item);
|
||||
}
|
||||
|
||||
LOG_INFO(" Character ", (int)(i + 1), ": ", character.name);
|
||||
LOG_INFO(" GUID: 0x", std::hex, character.guid, std::dec);
|
||||
LOG_INFO(" ", getRaceName(character.race), " ",
|
||||
getClassName(character.characterClass), " (",
|
||||
getGenderName(character.gender), ")");
|
||||
LOG_INFO(" Level: ", (int)character.level);
|
||||
LOG_INFO(" Location: Zone ", character.zoneId, ", Map ", character.mapId);
|
||||
|
||||
response.characters.push_back(character);
|
||||
}
|
||||
|
||||
LOG_INFO("[Classic] Parsed ", response.characters.size(), " characters");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
424
src/game/packet_parsers_tbc.cpp
Normal file
424
src/game/packet_parsers_tbc.cpp
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
#include "game/packet_parsers.hpp"
|
||||
#include "core/logger.hpp"
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
||||
// ============================================================================
|
||||
// TBC 2.4.3 movement flag constants (shifted relative to WotLK 3.3.5a)
|
||||
// ============================================================================
|
||||
namespace TbcMoveFlags {
|
||||
constexpr uint32_t ON_TRANSPORT = 0x00000200; // Gates transport data (same as WotLK)
|
||||
constexpr uint32_t JUMPING = 0x00002000; // Gates jump data (WotLK: FALLING=0x1000)
|
||||
constexpr uint32_t SWIMMING = 0x00200000; // Same as WotLK
|
||||
constexpr uint32_t FLYING = 0x01000000; // WotLK: 0x02000000
|
||||
constexpr uint32_t ONTRANSPORT = 0x02000000; // Secondary pitch check
|
||||
constexpr uint32_t SPLINE_ELEVATION = 0x04000000; // Same as WotLK
|
||||
constexpr uint32_t SPLINE_ENABLED = 0x08000000; // Same as WotLK
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC parseMovementBlock
|
||||
// Key differences from WotLK:
|
||||
// - UpdateFlags is uint8 (not uint16)
|
||||
// - No VEHICLE (0x0080), POSITION (0x0100), ROTATION (0x0200) flags
|
||||
// - moveFlags2 is uint8 (not uint16)
|
||||
// - No transport seat byte
|
||||
// - No interpolated movement (flags2 & 0x0200) check
|
||||
// - Pitch check: SWIMMING, else ONTRANSPORT(0x02000000)
|
||||
// - Spline data: has splineId, no durationMod/durationModNext/verticalAccel/effectStartTime/splineMode
|
||||
// - Flag 0x08 (HIGH_GUID) reads 2 u32s (Classic: 1 u32)
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseMovementBlock(network::Packet& packet, UpdateBlock& block) {
|
||||
// TBC 2.4.3: UpdateFlags is uint8 (1 byte)
|
||||
uint8_t updateFlags = packet.readUInt8();
|
||||
block.updateFlags = static_cast<uint16_t>(updateFlags);
|
||||
|
||||
LOG_DEBUG(" [TBC] UpdateFlags: 0x", std::hex, (int)updateFlags, std::dec);
|
||||
|
||||
// TBC UpdateFlag bit values (same as lower byte of WotLK):
|
||||
// 0x01 = SELF
|
||||
// 0x02 = TRANSPORT
|
||||
// 0x04 = HAS_TARGET
|
||||
// 0x08 = LOWGUID
|
||||
// 0x10 = HIGHGUID
|
||||
// 0x20 = LIVING
|
||||
// 0x40 = HAS_POSITION (stationary)
|
||||
const uint8_t UPDATEFLAG_LIVING = 0x20;
|
||||
const uint8_t UPDATEFLAG_HAS_POSITION = 0x40;
|
||||
const uint8_t UPDATEFLAG_HAS_TARGET = 0x04;
|
||||
const uint8_t UPDATEFLAG_TRANSPORT = 0x02;
|
||||
const uint8_t UPDATEFLAG_LOWGUID = 0x08;
|
||||
const uint8_t UPDATEFLAG_HIGHGUID = 0x10;
|
||||
|
||||
if (updateFlags & UPDATEFLAG_LIVING) {
|
||||
// Full movement block for living units
|
||||
uint32_t moveFlags = packet.readUInt32();
|
||||
uint8_t moveFlags2 = packet.readUInt8(); // TBC: uint8, not uint16
|
||||
(void)moveFlags2;
|
||||
/*uint32_t time =*/ packet.readUInt32();
|
||||
|
||||
// Position
|
||||
block.x = packet.readFloat();
|
||||
block.y = packet.readFloat();
|
||||
block.z = packet.readFloat();
|
||||
block.orientation = packet.readFloat();
|
||||
block.hasMovement = true;
|
||||
|
||||
LOG_DEBUG(" [TBC] LIVING: (", block.x, ", ", block.y, ", ", block.z,
|
||||
"), o=", block.orientation, " moveFlags=0x", std::hex, moveFlags, std::dec);
|
||||
|
||||
// Transport data
|
||||
if (moveFlags & TbcMoveFlags::ON_TRANSPORT) {
|
||||
block.onTransport = true;
|
||||
block.transportGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
block.transportX = packet.readFloat();
|
||||
block.transportY = packet.readFloat();
|
||||
block.transportZ = packet.readFloat();
|
||||
block.transportO = packet.readFloat();
|
||||
/*uint32_t tTime =*/ packet.readUInt32();
|
||||
// TBC: NO transport seat byte
|
||||
// TBC: NO interpolated movement check
|
||||
}
|
||||
|
||||
// Pitch: SWIMMING, or else ONTRANSPORT (TBC-specific secondary pitch)
|
||||
if (moveFlags & TbcMoveFlags::SWIMMING) {
|
||||
/*float pitch =*/ packet.readFloat();
|
||||
} else if (moveFlags & TbcMoveFlags::ONTRANSPORT) {
|
||||
/*float pitch =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Fall time (always present)
|
||||
/*uint32_t fallTime =*/ packet.readUInt32();
|
||||
|
||||
// Jumping (TBC: JUMPING=0x2000, WotLK: FALLING=0x1000)
|
||||
if (moveFlags & TbcMoveFlags::JUMPING) {
|
||||
/*float jumpVelocity =*/ packet.readFloat();
|
||||
/*float jumpSinAngle =*/ packet.readFloat();
|
||||
/*float jumpCosAngle =*/ packet.readFloat();
|
||||
/*float jumpXYSpeed =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Spline elevation (TBC: 0x02000000, WotLK: 0x04000000)
|
||||
if (moveFlags & TbcMoveFlags::SPLINE_ELEVATION) {
|
||||
/*float splineElevation =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Speeds (TBC: 8 values — walk, run, runBack, swim, fly, flyBack, swimBack, turn)
|
||||
// WotLK adds pitchRate (9 total)
|
||||
/*float walkSpeed =*/ packet.readFloat();
|
||||
float runSpeed = packet.readFloat();
|
||||
/*float runBackSpeed =*/ packet.readFloat();
|
||||
/*float swimSpeed =*/ packet.readFloat();
|
||||
/*float flySpeed =*/ packet.readFloat();
|
||||
/*float flyBackSpeed =*/ packet.readFloat();
|
||||
/*float swimBackSpeed =*/ packet.readFloat();
|
||||
/*float turnRate =*/ packet.readFloat();
|
||||
|
||||
block.runSpeed = runSpeed;
|
||||
|
||||
// Spline data (TBC/WotLK: SPLINE_ENABLED = 0x08000000)
|
||||
if (moveFlags & TbcMoveFlags::SPLINE_ENABLED) {
|
||||
uint32_t splineFlags = packet.readUInt32();
|
||||
LOG_DEBUG(" [TBC] Spline: flags=0x", std::hex, splineFlags, std::dec);
|
||||
|
||||
if (splineFlags & 0x00010000) { // FINAL_POINT
|
||||
/*float finalX =*/ packet.readFloat();
|
||||
/*float finalY =*/ packet.readFloat();
|
||||
/*float finalZ =*/ packet.readFloat();
|
||||
} else if (splineFlags & 0x00020000) { // FINAL_TARGET
|
||||
/*uint64_t finalTarget =*/ packet.readUInt64();
|
||||
} else if (splineFlags & 0x00040000) { // FINAL_ANGLE
|
||||
/*float finalAngle =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// TBC spline: timePassed, duration, id, nodes, finalNode
|
||||
// (no durationMod, durationModNext, verticalAccel, effectStartTime, splineMode)
|
||||
/*uint32_t timePassed =*/ packet.readUInt32();
|
||||
/*uint32_t duration =*/ packet.readUInt32();
|
||||
/*uint32_t splineId =*/ packet.readUInt32();
|
||||
|
||||
uint32_t pointCount = packet.readUInt32();
|
||||
if (pointCount > 256) {
|
||||
LOG_WARNING(" [TBC] Spline pointCount=", pointCount, " exceeds max, capping");
|
||||
pointCount = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < pointCount; i++) {
|
||||
/*float px =*/ packet.readFloat();
|
||||
/*float py =*/ packet.readFloat();
|
||||
/*float pz =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// TBC: NO splineMode byte (WotLK adds it)
|
||||
/*float endPointX =*/ packet.readFloat();
|
||||
/*float endPointY =*/ packet.readFloat();
|
||||
/*float endPointZ =*/ packet.readFloat();
|
||||
}
|
||||
}
|
||||
else if (updateFlags & UPDATEFLAG_HAS_POSITION) {
|
||||
// TBC: Simple stationary position (same as WotLK STATIONARY)
|
||||
block.x = packet.readFloat();
|
||||
block.y = packet.readFloat();
|
||||
block.z = packet.readFloat();
|
||||
block.orientation = packet.readFloat();
|
||||
block.hasMovement = true;
|
||||
|
||||
LOG_DEBUG(" [TBC] STATIONARY: (", block.x, ", ", block.y, ", ", block.z, ")");
|
||||
}
|
||||
// TBC: No UPDATEFLAG_POSITION (0x0100) code path
|
||||
|
||||
// Target GUID
|
||||
if (updateFlags & UPDATEFLAG_HAS_TARGET) {
|
||||
/*uint64_t targetGuid =*/ UpdateObjectParser::readPackedGuid(packet);
|
||||
}
|
||||
|
||||
// Transport time
|
||||
if (updateFlags & UPDATEFLAG_TRANSPORT) {
|
||||
/*uint32_t transportTime =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
// TBC: No VEHICLE flag (WotLK 0x0080)
|
||||
// TBC: No ROTATION flag (WotLK 0x0200)
|
||||
|
||||
// HIGH_GUID (0x08) — TBC has 2 u32s, Classic has 1 u32
|
||||
if (updateFlags & UPDATEFLAG_LOWGUID) {
|
||||
/*uint32_t unknown0 =*/ packet.readUInt32();
|
||||
/*uint32_t unknown1 =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
// ALL (0x10)
|
||||
if (updateFlags & UPDATEFLAG_HIGHGUID) {
|
||||
/*uint32_t unknown2 =*/ packet.readUInt32();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC writeMovementPayload
|
||||
// Key differences from WotLK:
|
||||
// - flags2 is uint8 (not uint16)
|
||||
// - No transport seat byte
|
||||
// - No interpolated movement (flags2 & 0x0200) write
|
||||
// - Pitch check uses TBC flag positions
|
||||
// ============================================================================
|
||||
void TbcPacketParsers::writeMovementPayload(network::Packet& packet, const MovementInfo& info) {
|
||||
// Movement flags (uint32, same as WotLK)
|
||||
packet.writeUInt32(info.flags);
|
||||
|
||||
// TBC: flags2 is uint8 (WotLK: uint16)
|
||||
packet.writeUInt8(static_cast<uint8_t>(info.flags2 & 0xFF));
|
||||
|
||||
// Timestamp
|
||||
packet.writeUInt32(info.time);
|
||||
|
||||
// Position
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.x), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.y), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.z), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.orientation), sizeof(float));
|
||||
|
||||
// Transport data (TBC ON_TRANSPORT = 0x200, same bit as WotLK)
|
||||
if (info.flags & TbcMoveFlags::ON_TRANSPORT) {
|
||||
// Packed transport GUID
|
||||
uint8_t transMask = 0;
|
||||
uint8_t transGuidBytes[8];
|
||||
int transGuidByteCount = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint8_t byte = static_cast<uint8_t>((info.transportGuid >> (i * 8)) & 0xFF);
|
||||
if (byte != 0) {
|
||||
transMask |= (1 << i);
|
||||
transGuidBytes[transGuidByteCount++] = byte;
|
||||
}
|
||||
}
|
||||
packet.writeUInt8(transMask);
|
||||
for (int i = 0; i < transGuidByteCount; i++) {
|
||||
packet.writeUInt8(transGuidBytes[i]);
|
||||
}
|
||||
|
||||
// Transport local position
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportX), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportY), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportZ), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.transportO), sizeof(float));
|
||||
|
||||
// Transport time
|
||||
packet.writeUInt32(info.transportTime);
|
||||
|
||||
// TBC: NO transport seat byte
|
||||
// TBC: NO interpolated movement time
|
||||
}
|
||||
|
||||
// Pitch: SWIMMING or else ONTRANSPORT (TBC flag positions)
|
||||
if (info.flags & TbcMoveFlags::SWIMMING) {
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.pitch), sizeof(float));
|
||||
} else if (info.flags & TbcMoveFlags::ONTRANSPORT) {
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.pitch), sizeof(float));
|
||||
}
|
||||
|
||||
// Fall time (always present)
|
||||
packet.writeUInt32(info.fallTime);
|
||||
|
||||
// Jump data (TBC JUMPING = 0x2000, WotLK FALLING = 0x1000)
|
||||
if (info.flags & TbcMoveFlags::JUMPING) {
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpVelocity), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpSinAngle), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpCosAngle), sizeof(float));
|
||||
packet.writeBytes(reinterpret_cast<const uint8_t*>(&info.jumpXYSpeed), sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC buildMovementPacket
|
||||
// Classic/TBC: client movement packets do NOT include PackedGuid prefix
|
||||
// (WotLK added PackedGuid to client packets)
|
||||
// ============================================================================
|
||||
network::Packet TbcPacketParsers::buildMovementPacket(LogicalOpcode opcode,
|
||||
const MovementInfo& info,
|
||||
uint64_t /*playerGuid*/) {
|
||||
network::Packet packet(wireOpcode(opcode));
|
||||
|
||||
// TBC: NO PackedGuid prefix for client packets
|
||||
writeMovementPayload(packet, info);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC parseCharEnum
|
||||
// Differences from WotLK:
|
||||
// - After flags: uint8 firstLogin (not uint32 customization + uint8 unknown)
|
||||
// - Equipment: 20 items (not 23)
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseCharEnum(network::Packet& packet, CharEnumResponse& response) {
|
||||
uint8_t count = packet.readUInt8();
|
||||
|
||||
LOG_INFO("[TBC] Parsing SMSG_CHAR_ENUM: ", (int)count, " characters");
|
||||
|
||||
response.characters.clear();
|
||||
response.characters.reserve(count);
|
||||
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
Character character;
|
||||
|
||||
// GUID (8 bytes)
|
||||
character.guid = packet.readUInt64();
|
||||
|
||||
// Name (null-terminated string)
|
||||
character.name = packet.readString();
|
||||
|
||||
// Race, class, gender
|
||||
character.race = static_cast<Race>(packet.readUInt8());
|
||||
character.characterClass = static_cast<Class>(packet.readUInt8());
|
||||
character.gender = static_cast<Gender>(packet.readUInt8());
|
||||
|
||||
// Appearance (5 bytes: skin, face, hairStyle, hairColor packed + facialFeatures)
|
||||
character.appearanceBytes = packet.readUInt32();
|
||||
character.facialFeatures = packet.readUInt8();
|
||||
|
||||
// Level
|
||||
character.level = packet.readUInt8();
|
||||
|
||||
// Location
|
||||
character.zoneId = packet.readUInt32();
|
||||
character.mapId = packet.readUInt32();
|
||||
character.x = packet.readFloat();
|
||||
character.y = packet.readFloat();
|
||||
character.z = packet.readFloat();
|
||||
|
||||
// Guild ID
|
||||
character.guildId = packet.readUInt32();
|
||||
|
||||
// Flags
|
||||
character.flags = packet.readUInt32();
|
||||
|
||||
// TBC: uint8 firstLogin (WotLK: uint32 customization + uint8 unknown)
|
||||
/*uint8_t firstLogin =*/ packet.readUInt8();
|
||||
|
||||
// Pet data (always present)
|
||||
character.pet.displayModel = packet.readUInt32();
|
||||
character.pet.level = packet.readUInt32();
|
||||
character.pet.family = packet.readUInt32();
|
||||
|
||||
// Equipment (TBC: 20 items, WotLK: 23 items)
|
||||
character.equipment.reserve(20);
|
||||
for (int j = 0; j < 20; ++j) {
|
||||
EquipmentItem item;
|
||||
item.displayModel = packet.readUInt32();
|
||||
item.inventoryType = packet.readUInt8();
|
||||
item.enchantment = packet.readUInt32();
|
||||
character.equipment.push_back(item);
|
||||
}
|
||||
|
||||
LOG_INFO(" Character ", (int)(i + 1), ": ", character.name);
|
||||
LOG_INFO(" GUID: 0x", std::hex, character.guid, std::dec);
|
||||
LOG_INFO(" ", getRaceName(character.race), " ",
|
||||
getClassName(character.characterClass), " (",
|
||||
getGenderName(character.gender), ")");
|
||||
LOG_INFO(" Level: ", (int)character.level);
|
||||
LOG_INFO(" Location: Zone ", character.zoneId, ", Map ", character.mapId);
|
||||
|
||||
response.characters.push_back(character);
|
||||
}
|
||||
|
||||
LOG_INFO("[TBC] Parsed ", response.characters.size(), " characters");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC parseUpdateObject
|
||||
// Key difference from WotLK: u8 has_transport byte after blockCount
|
||||
// (WotLK removed this field)
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjectData& data) {
|
||||
// Read block count
|
||||
data.blockCount = packet.readUInt32();
|
||||
|
||||
// TBC/Classic: has_transport byte (WotLK removed this)
|
||||
/*uint8_t hasTransport =*/ packet.readUInt8();
|
||||
|
||||
LOG_DEBUG("[TBC] SMSG_UPDATE_OBJECT: objectCount=", data.blockCount);
|
||||
|
||||
// Check for out-of-range objects first
|
||||
if (packet.getReadPos() + 1 <= packet.getSize()) {
|
||||
uint8_t firstByte = packet.readUInt8();
|
||||
|
||||
if (firstByte == static_cast<uint8_t>(UpdateType::OUT_OF_RANGE_OBJECTS)) {
|
||||
uint32_t count = packet.readUInt32();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
uint64_t guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
data.outOfRangeGuids.push_back(guid);
|
||||
LOG_DEBUG(" Out of range: 0x", std::hex, guid, std::dec);
|
||||
}
|
||||
} else {
|
||||
packet.setReadPos(packet.getReadPos() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse update blocks
|
||||
data.blocks.reserve(data.blockCount);
|
||||
for (uint32_t i = 0; i < data.blockCount; ++i) {
|
||||
LOG_DEBUG("Parsing block ", i + 1, " / ", data.blockCount);
|
||||
UpdateBlock block;
|
||||
if (!UpdateObjectParser::parseUpdateBlock(packet, block)) {
|
||||
LOG_ERROR("Failed to parse update block ", i + 1);
|
||||
return false;
|
||||
}
|
||||
data.blocks.push_back(block);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TBC parseAuraUpdate - SMSG_AURA_UPDATE doesn't exist in TBC
|
||||
// TBC uses inline aura update fields + SMSG_INIT_EXTRA_AURA_INFO (0x3A3) /
|
||||
// SMSG_SET_EXTRA_AURA_INFO (0x3A4) instead
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseAuraUpdate(network::Packet& /*packet*/, AuraUpdateData& /*data*/, bool /*isAll*/) {
|
||||
LOG_WARNING("[TBC] parseAuraUpdate called but SMSG_AURA_UPDATE does not exist in TBC 2.4.3");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
156
src/game/update_field_table.cpp
Normal file
156
src/game/update_field_table.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#include "game/update_field_table.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
||||
static const UpdateFieldTable* g_activeUpdateFieldTable = nullptr;
|
||||
|
||||
void setActiveUpdateFieldTable(const UpdateFieldTable* table) { g_activeUpdateFieldTable = table; }
|
||||
const UpdateFieldTable* getActiveUpdateFieldTable() { return g_activeUpdateFieldTable; }
|
||||
|
||||
struct UFNameEntry {
|
||||
const char* name;
|
||||
UF field;
|
||||
};
|
||||
|
||||
static const UFNameEntry kUFNames[] = {
|
||||
{"OBJECT_FIELD_ENTRY", UF::OBJECT_FIELD_ENTRY},
|
||||
{"UNIT_FIELD_TARGET_LO", UF::UNIT_FIELD_TARGET_LO},
|
||||
{"UNIT_FIELD_TARGET_HI", UF::UNIT_FIELD_TARGET_HI},
|
||||
{"UNIT_FIELD_HEALTH", UF::UNIT_FIELD_HEALTH},
|
||||
{"UNIT_FIELD_POWER1", UF::UNIT_FIELD_POWER1},
|
||||
{"UNIT_FIELD_MAXHEALTH", UF::UNIT_FIELD_MAXHEALTH},
|
||||
{"UNIT_FIELD_MAXPOWER1", UF::UNIT_FIELD_MAXPOWER1},
|
||||
{"UNIT_FIELD_LEVEL", UF::UNIT_FIELD_LEVEL},
|
||||
{"UNIT_FIELD_FACTIONTEMPLATE", UF::UNIT_FIELD_FACTIONTEMPLATE},
|
||||
{"UNIT_FIELD_FLAGS", UF::UNIT_FIELD_FLAGS},
|
||||
{"UNIT_FIELD_FLAGS_2", UF::UNIT_FIELD_FLAGS_2},
|
||||
{"UNIT_FIELD_DISPLAYID", UF::UNIT_FIELD_DISPLAYID},
|
||||
{"UNIT_FIELD_MOUNTDISPLAYID", UF::UNIT_FIELD_MOUNTDISPLAYID},
|
||||
{"UNIT_NPC_FLAGS", UF::UNIT_NPC_FLAGS},
|
||||
{"UNIT_DYNAMIC_FLAGS", UF::UNIT_DYNAMIC_FLAGS},
|
||||
{"UNIT_END", UF::UNIT_END},
|
||||
{"PLAYER_FLAGS", UF::PLAYER_FLAGS},
|
||||
{"PLAYER_XP", UF::PLAYER_XP},
|
||||
{"PLAYER_NEXT_LEVEL_XP", UF::PLAYER_NEXT_LEVEL_XP},
|
||||
{"PLAYER_FIELD_COINAGE", UF::PLAYER_FIELD_COINAGE},
|
||||
{"PLAYER_QUEST_LOG_START", UF::PLAYER_QUEST_LOG_START},
|
||||
{"PLAYER_FIELD_INV_SLOT_HEAD", UF::PLAYER_FIELD_INV_SLOT_HEAD},
|
||||
{"PLAYER_FIELD_PACK_SLOT_1", UF::PLAYER_FIELD_PACK_SLOT_1},
|
||||
{"PLAYER_SKILL_INFO_START", UF::PLAYER_SKILL_INFO_START},
|
||||
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
|
||||
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
|
||||
{"ITEM_FIELD_STACK_COUNT", UF::ITEM_FIELD_STACK_COUNT},
|
||||
};
|
||||
|
||||
static constexpr size_t kUFNameCount = sizeof(kUFNames) / sizeof(kUFNames[0]);
|
||||
|
||||
void UpdateFieldTable::loadWotlkDefaults() {
|
||||
fieldMap_.clear();
|
||||
struct { UF field; uint16_t idx; } defaults[] = {
|
||||
{UF::OBJECT_FIELD_ENTRY, 3},
|
||||
{UF::UNIT_FIELD_TARGET_LO, 6},
|
||||
{UF::UNIT_FIELD_TARGET_HI, 7},
|
||||
{UF::UNIT_FIELD_HEALTH, 24},
|
||||
{UF::UNIT_FIELD_POWER1, 25},
|
||||
{UF::UNIT_FIELD_MAXHEALTH, 32},
|
||||
{UF::UNIT_FIELD_MAXPOWER1, 33},
|
||||
{UF::UNIT_FIELD_LEVEL, 54},
|
||||
{UF::UNIT_FIELD_FACTIONTEMPLATE, 55},
|
||||
{UF::UNIT_FIELD_FLAGS, 59},
|
||||
{UF::UNIT_FIELD_FLAGS_2, 60},
|
||||
{UF::UNIT_FIELD_DISPLAYID, 67},
|
||||
{UF::UNIT_FIELD_MOUNTDISPLAYID, 69},
|
||||
{UF::UNIT_NPC_FLAGS, 82},
|
||||
{UF::UNIT_DYNAMIC_FLAGS, 147},
|
||||
{UF::UNIT_END, 148},
|
||||
{UF::PLAYER_FLAGS, 150},
|
||||
{UF::PLAYER_XP, 634},
|
||||
{UF::PLAYER_NEXT_LEVEL_XP, 635},
|
||||
{UF::PLAYER_FIELD_COINAGE, 1170},
|
||||
{UF::PLAYER_QUEST_LOG_START, 158},
|
||||
{UF::PLAYER_FIELD_INV_SLOT_HEAD, 324},
|
||||
{UF::PLAYER_FIELD_PACK_SLOT_1, 370},
|
||||
{UF::PLAYER_SKILL_INFO_START, 636},
|
||||
{UF::PLAYER_EXPLORED_ZONES_START, 1041},
|
||||
{UF::GAMEOBJECT_DISPLAYID, 8},
|
||||
{UF::ITEM_FIELD_STACK_COUNT, 14},
|
||||
};
|
||||
for (auto& d : defaults) {
|
||||
fieldMap_[static_cast<uint16_t>(d.field)] = d.idx;
|
||||
}
|
||||
LOG_INFO("UpdateFieldTable: loaded ", fieldMap_.size(), " WotLK default fields");
|
||||
}
|
||||
|
||||
bool UpdateFieldTable::loadFromJson(const std::string& path) {
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) {
|
||||
LOG_WARNING("UpdateFieldTable: cannot open ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
fieldMap_.clear();
|
||||
size_t loaded = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < json.size()) {
|
||||
size_t keyStart = json.find('"', pos);
|
||||
if (keyStart == std::string::npos) break;
|
||||
size_t keyEnd = json.find('"', keyStart + 1);
|
||||
if (keyEnd == std::string::npos) break;
|
||||
std::string key = json.substr(keyStart + 1, keyEnd - keyStart - 1);
|
||||
|
||||
size_t colon = json.find(':', keyEnd);
|
||||
if (colon == std::string::npos) break;
|
||||
|
||||
size_t valStart = colon + 1;
|
||||
while (valStart < json.size() && (json[valStart] == ' ' || json[valStart] == '\t' ||
|
||||
json[valStart] == '\r' || json[valStart] == '\n'))
|
||||
++valStart;
|
||||
|
||||
size_t valEnd = json.find_first_of(",}\r\n", valStart);
|
||||
if (valEnd == std::string::npos) valEnd = json.size();
|
||||
std::string valStr = json.substr(valStart, valEnd - valStart);
|
||||
// Trim whitespace
|
||||
while (!valStr.empty() && (valStr.back() == ' ' || valStr.back() == '\t'))
|
||||
valStr.pop_back();
|
||||
|
||||
uint16_t idx = 0;
|
||||
try { idx = static_cast<uint16_t>(std::stoul(valStr)); } catch (...) {
|
||||
pos = valEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find matching UF enum
|
||||
for (size_t i = 0; i < kUFNameCount; ++i) {
|
||||
if (key == kUFNames[i].name) {
|
||||
fieldMap_[static_cast<uint16_t>(kUFNames[i].field)] = idx;
|
||||
++loaded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pos = valEnd + 1;
|
||||
}
|
||||
|
||||
LOG_INFO("UpdateFieldTable: loaded ", loaded, " fields from ", path);
|
||||
return loaded > 0;
|
||||
}
|
||||
|
||||
uint16_t UpdateFieldTable::index(UF field) const {
|
||||
auto it = fieldMap_.find(static_cast<uint16_t>(field));
|
||||
return (it != fieldMap_.end()) ? it->second : 0xFFFF;
|
||||
}
|
||||
|
||||
bool UpdateFieldTable::hasField(UF field) const {
|
||||
return fieldMap_.count(static_cast<uint16_t>(field)) > 0;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
@ -39,40 +39,33 @@ network::Packet AuthSessionPacket::build(uint32_t build,
|
|||
LOG_DEBUG(" Auth hash: ", authHash.size(), " bytes");
|
||||
|
||||
// Create packet (opcode will be added by WorldSocket)
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_AUTH_SESSION));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_AUTH_SESSION));
|
||||
|
||||
// AzerothCore 3.3.5a expects this exact field order:
|
||||
// Build, LoginServerID, Account, LoginServerType, LocalChallenge,
|
||||
// RegionID, BattlegroupID, RealmID, DosResponse, Digest, AddonInfo
|
||||
bool isTbc = (build <= 8606); // TBC 2.4.3 = 8606, WotLK starts at 11159+
|
||||
|
||||
// Build number (uint32, little-endian)
|
||||
packet.writeUInt32(build);
|
||||
|
||||
// Login server ID (uint32, always 0)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Account name (null-terminated string)
|
||||
packet.writeString(upperAccount);
|
||||
|
||||
// Login server type (uint32, always 0)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// LocalChallenge / Client seed (uint32, little-endian)
|
||||
packet.writeUInt32(clientSeed);
|
||||
|
||||
// Region ID (uint32)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Battlegroup ID (uint32)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Realm ID (uint32)
|
||||
packet.writeUInt32(realmId);
|
||||
LOG_DEBUG(" Realm ID: ", realmId);
|
||||
|
||||
// DOS response (uint64) - required for 3.x
|
||||
packet.writeUInt32(0);
|
||||
packet.writeUInt32(0);
|
||||
if (isTbc) {
|
||||
// TBC 2.4.3 format (6 fields):
|
||||
// Build, ServerID, Account, ClientSeed, Digest, AddonInfo
|
||||
packet.writeUInt32(build);
|
||||
packet.writeUInt32(realmId); // server_id
|
||||
packet.writeString(upperAccount);
|
||||
packet.writeUInt32(clientSeed);
|
||||
} else {
|
||||
// WotLK 3.3.5a format (11 fields):
|
||||
// Build, LoginServerID, Account, LoginServerType, LocalChallenge,
|
||||
// RegionID, BattlegroupID, RealmID, DosResponse, Digest, AddonInfo
|
||||
packet.writeUInt32(build);
|
||||
packet.writeUInt32(0); // LoginServerID
|
||||
packet.writeString(upperAccount);
|
||||
packet.writeUInt32(0); // LoginServerType
|
||||
packet.writeUInt32(clientSeed);
|
||||
packet.writeUInt32(0); // RegionID
|
||||
packet.writeUInt32(0); // BattlegroupID
|
||||
packet.writeUInt32(realmId); // RealmID
|
||||
LOG_DEBUG(" Realm ID: ", realmId);
|
||||
packet.writeUInt32(0); // DOS response (uint64)
|
||||
packet.writeUInt32(0);
|
||||
}
|
||||
|
||||
// Authentication hash/digest (20 bytes)
|
||||
packet.writeBytes(authHash.data(), authHash.size());
|
||||
|
|
@ -160,25 +153,30 @@ std::vector<uint8_t> AuthSessionPacket::computeAuthHash(
|
|||
}
|
||||
|
||||
bool AuthChallengeParser::parse(network::Packet& packet, AuthChallengeData& data) {
|
||||
// SMSG_AUTH_CHALLENGE format (WoW 3.3.5a):
|
||||
// uint32 unknown1 (always 1?)
|
||||
// uint32 serverSeed
|
||||
// SMSG_AUTH_CHALLENGE format varies by expansion:
|
||||
// TBC 2.4.3: uint32 serverSeed (4 bytes)
|
||||
// WotLK 3.3.5a: uint32 one + uint32 serverSeed + seeds (40 bytes)
|
||||
|
||||
if (packet.getSize() < 8) {
|
||||
if (packet.getSize() < 4) {
|
||||
LOG_ERROR("SMSG_AUTH_CHALLENGE packet too small: ", packet.getSize(), " bytes");
|
||||
return false;
|
||||
}
|
||||
|
||||
data.unknown1 = packet.readUInt32();
|
||||
data.serverSeed = packet.readUInt32();
|
||||
if (packet.getSize() < 8) {
|
||||
// TBC format: just the server seed (4 bytes)
|
||||
data.unknown1 = 0;
|
||||
data.serverSeed = packet.readUInt32();
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (TBC format):");
|
||||
} else {
|
||||
// WotLK format: unknown1 + serverSeed + encryption seeds
|
||||
data.unknown1 = packet.readUInt32();
|
||||
data.serverSeed = packet.readUInt32();
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (WotLK format):");
|
||||
LOG_INFO(" Unknown1: 0x", std::hex, data.unknown1, std::dec);
|
||||
}
|
||||
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE:");
|
||||
LOG_INFO(" Unknown1: 0x", std::hex, data.unknown1, std::dec);
|
||||
LOG_INFO(" Server seed: 0x", std::hex, data.serverSeed, std::dec);
|
||||
|
||||
// Note: 3.3.5a has additional data after this (seed2, etc.)
|
||||
// but we only need the first seed for authentication
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +255,7 @@ const char* getAuthResultString(AuthResult result) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet CharCreatePacket::build(const CharCreateData& data) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CHAR_CREATE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_CREATE));
|
||||
|
||||
// Convert nonbinary gender to server-compatible value (servers only support male/female)
|
||||
Gender serverGender = toServerGender(data.gender);
|
||||
|
|
@ -305,7 +303,7 @@ bool CharCreateResponseParser::parse(network::Packet& packet, CharCreateResponse
|
|||
|
||||
network::Packet CharEnumPacket::build() {
|
||||
// CMSG_CHAR_ENUM has no body - just the opcode
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CHAR_ENUM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_ENUM));
|
||||
|
||||
LOG_DEBUG("Built CMSG_CHAR_ENUM packet (no body)");
|
||||
|
||||
|
|
@ -399,7 +397,7 @@ bool CharEnumParser::parse(network::Packet& packet, CharEnumResponse& response)
|
|||
}
|
||||
|
||||
network::Packet PlayerLoginPacket::build(uint64_t characterGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_PLAYER_LOGIN));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_PLAYER_LOGIN));
|
||||
|
||||
// Write character GUID (8 bytes, little-endian)
|
||||
packet.writeUInt64(characterGuid);
|
||||
|
|
@ -491,7 +489,7 @@ bool MotdParser::parse(network::Packet& packet, MotdData& data) {
|
|||
}
|
||||
|
||||
network::Packet PingPacket::build(uint32_t sequence, uint32_t latency) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_PING));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_PING));
|
||||
|
||||
// Write sequence number (uint32, little-endian)
|
||||
packet.writeUInt32(sequence);
|
||||
|
|
@ -618,7 +616,7 @@ void MovementPacket::writeMovementPayload(network::Packet& packet, const Movemen
|
|||
}
|
||||
|
||||
network::Packet MovementPacket::build(Opcode opcode, const MovementInfo& info, uint64_t playerGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(opcode));
|
||||
network::Packet packet(wireOpcode(opcode));
|
||||
|
||||
// Movement packet format (WoW 3.3.5a):
|
||||
// packed GUID + movement payload
|
||||
|
|
@ -634,7 +632,7 @@ network::Packet MovementPacket::build(Opcode opcode, const MovementInfo& info, u
|
|||
char b[4]; snprintf(b, sizeof(b), "%02x ", raw[i]);
|
||||
hex += b;
|
||||
}
|
||||
LOG_INFO("MOVEPKT opcode=0x", std::hex, static_cast<uint16_t>(opcode), std::dec,
|
||||
LOG_INFO("MOVEPKT opcode=0x", std::hex, wireOpcode(opcode), std::dec,
|
||||
" guid=0x", std::hex, playerGuid, std::dec,
|
||||
" payload=", raw.size(), " bytes",
|
||||
" flags=0x", std::hex, info.flags, std::dec,
|
||||
|
|
@ -1096,7 +1094,7 @@ network::Packet MessageChatPacket::build(ChatType type,
|
|||
ChatLanguage language,
|
||||
const std::string& message,
|
||||
const std::string& target) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_MESSAGECHAT));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_MESSAGECHAT));
|
||||
|
||||
// Write chat type
|
||||
packet.writeUInt32(static_cast<uint32_t>(type));
|
||||
|
|
@ -1267,21 +1265,21 @@ const char* getChatTypeString(ChatType type) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet SetSelectionPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SET_SELECTION));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SET_SELECTION));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_SET_SELECTION: target=0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SetActiveMoverPacket::build(uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SET_ACTIVE_MOVER));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SET_ACTIVE_MOVER));
|
||||
packet.writeUInt64(guid);
|
||||
LOG_DEBUG("Built CMSG_SET_ACTIVE_MOVER: guid=0x", std::hex, guid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet InspectPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_INSPECT));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_INSPECT));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_INSPECT: target=0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
|
|
@ -1292,7 +1290,7 @@ network::Packet InspectPacket::build(uint64_t targetGuid) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet QueryTimePacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUERY_TIME));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUERY_TIME));
|
||||
LOG_DEBUG("Built CMSG_QUERY_TIME");
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -1305,7 +1303,7 @@ bool QueryTimeResponseParser::parse(network::Packet& packet, QueryTimeResponseDa
|
|||
}
|
||||
|
||||
network::Packet RequestPlayedTimePacket::build(bool sendToChat) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REQUEST_PLAYED_TIME));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_REQUEST_PLAYED_TIME));
|
||||
packet.writeUInt8(sendToChat ? 1 : 0);
|
||||
LOG_DEBUG("Built CMSG_REQUEST_PLAYED_TIME: sendToChat=", sendToChat);
|
||||
return packet;
|
||||
|
|
@ -1324,7 +1322,7 @@ network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
|||
const std::string& guildName,
|
||||
uint32_t raceMask, uint32_t classMask,
|
||||
uint32_t zones) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_WHO));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_WHO));
|
||||
packet.writeUInt32(minLevel);
|
||||
packet.writeUInt32(maxLevel);
|
||||
packet.writeString(playerName);
|
||||
|
|
@ -1341,7 +1339,7 @@ network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
|||
// ============================================================
|
||||
|
||||
network::Packet AddFriendPacket::build(const std::string& playerName, const std::string& note) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ADD_FRIEND));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ADD_FRIEND));
|
||||
packet.writeString(playerName);
|
||||
packet.writeString(note);
|
||||
LOG_DEBUG("Built CMSG_ADD_FRIEND: player=", playerName);
|
||||
|
|
@ -1349,14 +1347,14 @@ network::Packet AddFriendPacket::build(const std::string& playerName, const std:
|
|||
}
|
||||
|
||||
network::Packet DelFriendPacket::build(uint64_t friendGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DEL_FRIEND));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DEL_FRIEND));
|
||||
packet.writeUInt64(friendGuid);
|
||||
LOG_DEBUG("Built CMSG_DEL_FRIEND: guid=0x", std::hex, friendGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SetContactNotesPacket::build(uint64_t friendGuid, const std::string& note) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SET_CONTACT_NOTES));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SET_CONTACT_NOTES));
|
||||
packet.writeUInt64(friendGuid);
|
||||
packet.writeString(note);
|
||||
LOG_DEBUG("Built CMSG_SET_CONTACT_NOTES: guid=0x", std::hex, friendGuid, std::dec);
|
||||
|
|
@ -1375,14 +1373,14 @@ bool FriendStatusParser::parse(network::Packet& packet, FriendStatusData& data)
|
|||
}
|
||||
|
||||
network::Packet AddIgnorePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ADD_IGNORE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ADD_IGNORE));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_ADD_IGNORE: player=", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet DelIgnorePacket::build(uint64_t ignoreGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DEL_IGNORE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DEL_IGNORE));
|
||||
packet.writeUInt64(ignoreGuid);
|
||||
LOG_DEBUG("Built CMSG_DEL_IGNORE: guid=0x", std::hex, ignoreGuid, std::dec);
|
||||
return packet;
|
||||
|
|
@ -1393,13 +1391,13 @@ network::Packet DelIgnorePacket::build(uint64_t ignoreGuid) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet LogoutRequestPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOGOUT_REQUEST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOGOUT_REQUEST));
|
||||
LOG_DEBUG("Built CMSG_LOGOUT_REQUEST");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet LogoutCancelPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOGOUT_CANCEL));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOGOUT_CANCEL));
|
||||
LOG_DEBUG("Built CMSG_LOGOUT_CANCEL");
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -1416,7 +1414,7 @@ bool LogoutResponseParser::parse(network::Packet& packet, LogoutResponseData& da
|
|||
// ============================================================
|
||||
|
||||
network::Packet StandStateChangePacket::build(uint8_t state) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_STAND_STATE_CHANGE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_STAND_STATE_CHANGE));
|
||||
packet.writeUInt32(state);
|
||||
LOG_DEBUG("Built CMSG_STAND_STATE_CHANGE: state=", (int)state);
|
||||
return packet;
|
||||
|
|
@ -1427,14 +1425,14 @@ network::Packet StandStateChangePacket::build(uint8_t state) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet ShowingHelmPacket::build(bool show) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SHOWING_HELM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SHOWING_HELM));
|
||||
packet.writeUInt8(show ? 1 : 0);
|
||||
LOG_DEBUG("Built CMSG_SHOWING_HELM: show=", show);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet ShowingCloakPacket::build(bool show) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SHOWING_CLOAK));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SHOWING_CLOAK));
|
||||
packet.writeUInt8(show ? 1 : 0);
|
||||
LOG_DEBUG("Built CMSG_SHOWING_CLOAK: show=", show);
|
||||
return packet;
|
||||
|
|
@ -1445,7 +1443,7 @@ network::Packet ShowingCloakPacket::build(bool show) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet TogglePvpPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_TOGGLE_PVP));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_TOGGLE_PVP));
|
||||
LOG_DEBUG("Built CMSG_TOGGLE_PVP");
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -1455,46 +1453,46 @@ network::Packet TogglePvpPacket::build() {
|
|||
// ============================================================
|
||||
|
||||
network::Packet GuildInfoPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_INFO));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_INFO));
|
||||
LOG_DEBUG("Built CMSG_GUILD_INFO");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildRosterPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_GET_ROSTER));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_GET_ROSTER));
|
||||
LOG_DEBUG("Built CMSG_GUILD_GET_ROSTER");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildMotdPacket::build(const std::string& motd) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_MOTD));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_MOTD));
|
||||
packet.writeString(motd);
|
||||
LOG_DEBUG("Built CMSG_GUILD_MOTD: ", motd);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildPromotePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_PROMOTE_MEMBER));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_PROMOTE_MEMBER));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_GUILD_PROMOTE_MEMBER: ", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildDemotePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_DEMOTE_MEMBER));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_DEMOTE_MEMBER));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_GUILD_DEMOTE_MEMBER: ", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildLeavePacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_LEAVE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_LEAVE));
|
||||
LOG_DEBUG("Built CMSG_GUILD_LEAVE");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GuildInvitePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GUILD_INVITE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GUILD_INVITE));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_GUILD_INVITE: ", playerName);
|
||||
return packet;
|
||||
|
|
@ -1505,13 +1503,13 @@ network::Packet GuildInvitePacket::build(const std::string& playerName) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet ReadyCheckPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RAID_READY_CHECK));
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_RAID_READY_CHECK));
|
||||
LOG_DEBUG("Built MSG_RAID_READY_CHECK");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RAID_READY_CHECK_CONFIRM));
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_RAID_READY_CHECK_CONFIRM));
|
||||
packet.writeUInt8(ready ? 1 : 0);
|
||||
LOG_DEBUG("Built MSG_RAID_READY_CHECK_CONFIRM: ready=", ready);
|
||||
return packet;
|
||||
|
|
@ -1522,7 +1520,7 @@ network::Packet ReadyCheckConfirmPacket::build(bool ready) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet DuelCancelPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DUEL_CANCELLED));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_CANCELLED));
|
||||
LOG_DEBUG("Built CMSG_DUEL_CANCELLED");
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -1532,20 +1530,20 @@ network::Packet DuelCancelPacket::build() {
|
|||
// ============================================================
|
||||
|
||||
network::Packet GroupUninvitePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_UNINVITE_GUID));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_UNINVITE_GUID));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_GROUP_UNINVITE_GUID for player: ", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GroupDisbandPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_DISBAND));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_DISBAND));
|
||||
LOG_DEBUG("Built CMSG_GROUP_DISBAND");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet RaidTargetUpdatePacket::build(uint8_t targetIndex, uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RAID_TARGET_UPDATE));
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_RAID_TARGET_UPDATE));
|
||||
packet.writeUInt8(targetIndex);
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built MSG_RAID_TARGET_UPDATE, index: ", (uint32_t)targetIndex, ", guid: 0x", std::hex, targetGuid, std::dec);
|
||||
|
|
@ -1553,7 +1551,7 @@ network::Packet RaidTargetUpdatePacket::build(uint8_t targetIndex, uint64_t targ
|
|||
}
|
||||
|
||||
network::Packet RequestRaidInfoPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REQUEST_RAID_INFO));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_REQUEST_RAID_INFO));
|
||||
LOG_DEBUG("Built CMSG_REQUEST_RAID_INFO");
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -1563,34 +1561,34 @@ network::Packet RequestRaidInfoPacket::build() {
|
|||
// ============================================================
|
||||
|
||||
network::Packet DuelProposedPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DUEL_PROPOSED));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_DUEL_PROPOSED));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_DUEL_PROPOSED for target: 0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet InitiateTradePacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_INITIATE_TRADE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_INITIATE_TRADE));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_INITIATE_TRADE for target: 0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet AttackSwingPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ATTACKSWING));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ATTACKSWING));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_ATTACKSWING for target: 0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet AttackStopPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ATTACKSTOP));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ATTACKSTOP));
|
||||
LOG_DEBUG("Built CMSG_ATTACKSTOP");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet CancelCastPacket::build(uint32_t spellId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CANCEL_CAST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_CAST));
|
||||
packet.writeUInt32(0); // cast count/sequence
|
||||
packet.writeUInt32(spellId);
|
||||
LOG_DEBUG("Built CMSG_CANCEL_CAST for spell: ", spellId);
|
||||
|
|
@ -1602,7 +1600,7 @@ network::Packet CancelCastPacket::build(uint32_t spellId) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet RandomRollPacket::build(uint32_t minRoll, uint32_t maxRoll) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RANDOM_ROLL));
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_RANDOM_ROLL));
|
||||
packet.writeUInt32(minRoll);
|
||||
packet.writeUInt32(maxRoll);
|
||||
LOG_DEBUG("Built MSG_RANDOM_ROLL: ", minRoll, "-", maxRoll);
|
||||
|
|
@ -1621,7 +1619,7 @@ bool RandomRollParser::parse(network::Packet& packet, RandomRollData& data) {
|
|||
}
|
||||
|
||||
network::Packet NameQueryPacket::build(uint64_t playerGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_NAME_QUERY));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_NAME_QUERY));
|
||||
packet.writeUInt64(playerGuid);
|
||||
LOG_DEBUG("Built CMSG_NAME_QUERY: guid=0x", std::hex, playerGuid, std::dec);
|
||||
return packet;
|
||||
|
|
@ -1650,7 +1648,7 @@ bool NameQueryResponseParser::parse(network::Packet& packet, NameQueryResponseDa
|
|||
}
|
||||
|
||||
network::Packet CreatureQueryPacket::build(uint32_t entry, uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CREATURE_QUERY));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CREATURE_QUERY));
|
||||
packet.writeUInt32(entry);
|
||||
packet.writeUInt64(guid);
|
||||
LOG_DEBUG("Built CMSG_CREATURE_QUERY: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
||||
|
|
@ -1691,7 +1689,7 @@ bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryRe
|
|||
// ---- GameObject Query ----
|
||||
|
||||
network::Packet GameObjectQueryPacket::build(uint32_t entry, uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GAMEOBJECT_QUERY));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GAMEOBJECT_QUERY));
|
||||
packet.writeUInt32(entry);
|
||||
packet.writeUInt64(guid);
|
||||
LOG_DEBUG("Built CMSG_GAMEOBJECT_QUERY: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
||||
|
|
@ -1725,7 +1723,7 @@ bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQue
|
|||
// ---- Item Query ----
|
||||
|
||||
network::Packet ItemQueryPacket::build(uint32_t entry, uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ITEM_QUERY_SINGLE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ITEM_QUERY_SINGLE));
|
||||
packet.writeUInt32(entry);
|
||||
packet.writeUInt64(guid);
|
||||
LOG_DEBUG("Built CMSG_ITEM_QUERY_SINGLE: entry=", entry, " guid=0x", std::hex, guid, std::dec);
|
||||
|
|
@ -2116,7 +2114,7 @@ bool InitialSpellsParser::parse(network::Packet& packet, InitialSpellsData& data
|
|||
}
|
||||
|
||||
network::Packet CastSpellPacket::build(uint32_t spellId, uint64_t targetGuid, uint8_t castCount) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CAST_SPELL));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CAST_SPELL));
|
||||
packet.writeUInt8(castCount);
|
||||
packet.writeUInt32(spellId);
|
||||
packet.writeUInt8(0x00); // castFlags = 0 for normal cast
|
||||
|
|
@ -2152,7 +2150,7 @@ network::Packet CastSpellPacket::build(uint32_t spellId, uint64_t targetGuid, ui
|
|||
}
|
||||
|
||||
network::Packet CancelAuraPacket::build(uint32_t spellId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_CANCEL_AURA));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_CANCEL_AURA));
|
||||
packet.writeUInt32(spellId);
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -2273,7 +2271,7 @@ bool SpellCooldownParser::parse(network::Packet& packet, SpellCooldownData& data
|
|||
// ============================================================
|
||||
|
||||
network::Packet GroupInvitePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_INVITE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_INVITE));
|
||||
packet.writeString(playerName);
|
||||
packet.writeUInt32(0); // unused
|
||||
LOG_DEBUG("Built CMSG_GROUP_INVITE: ", playerName);
|
||||
|
|
@ -2288,13 +2286,13 @@ bool GroupInviteResponseParser::parse(network::Packet& packet, GroupInviteRespon
|
|||
}
|
||||
|
||||
network::Packet GroupAcceptPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_ACCEPT));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_ACCEPT));
|
||||
packet.writeUInt32(0); // unused in 3.3.5a
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GroupDeclinePacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_DECLINE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GROUP_DECLINE));
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
@ -2365,20 +2363,20 @@ bool GroupDeclineResponseParser::parse(network::Packet& packet, GroupDeclineData
|
|||
// ============================================================
|
||||
|
||||
network::Packet LootPacket::build(uint64_t targetGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOOT));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT));
|
||||
packet.writeUInt64(targetGuid);
|
||||
LOG_DEBUG("Built CMSG_LOOT: target=0x", std::hex, targetGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet AutostoreLootItemPacket::build(uint8_t slotIndex) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_AUTOSTORE_LOOT_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_AUTOSTORE_LOOT_ITEM));
|
||||
packet.writeUInt8(slotIndex);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet UseItemPacket::build(uint8_t bagIndex, uint8_t slotIndex, uint64_t itemGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_USE_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_USE_ITEM));
|
||||
packet.writeUInt8(bagIndex);
|
||||
packet.writeUInt8(slotIndex);
|
||||
packet.writeUInt8(0); // cast count
|
||||
|
|
@ -2392,14 +2390,14 @@ network::Packet UseItemPacket::build(uint8_t bagIndex, uint8_t slotIndex, uint64
|
|||
}
|
||||
|
||||
network::Packet AutoEquipItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_AUTOEQUIP_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_AUTOEQUIP_ITEM));
|
||||
packet.writeUInt8(srcBag);
|
||||
packet.writeUInt8(srcSlot);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SwapItemPacket::build(uint8_t dstBag, uint8_t dstSlot, uint8_t srcBag, uint8_t srcSlot) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SWAP_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_ITEM));
|
||||
packet.writeUInt8(dstBag);
|
||||
packet.writeUInt8(dstSlot);
|
||||
packet.writeUInt8(srcBag);
|
||||
|
|
@ -2408,19 +2406,19 @@ network::Packet SwapItemPacket::build(uint8_t dstBag, uint8_t dstSlot, uint8_t s
|
|||
}
|
||||
|
||||
network::Packet SwapInvItemPacket::build(uint8_t srcSlot, uint8_t dstSlot) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SWAP_INV_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SWAP_INV_ITEM));
|
||||
packet.writeUInt8(srcSlot);
|
||||
packet.writeUInt8(dstSlot);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet LootMoneyPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOOT_MONEY));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT_MONEY));
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet LootReleasePacket::build(uint64_t lootGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOOT_RELEASE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LOOT_RELEASE));
|
||||
packet.writeUInt64(lootGuid);
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -2453,19 +2451,19 @@ bool LootResponseParser::parse(network::Packet& packet, LootResponseData& data)
|
|||
// ============================================================
|
||||
|
||||
network::Packet GossipHelloPacket::build(uint64_t npcGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GOSSIP_HELLO));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GOSSIP_HELLO));
|
||||
packet.writeUInt64(npcGuid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet QuestgiverHelloPacket::build(uint64_t npcGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_HELLO));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_HELLO));
|
||||
packet.writeUInt64(npcGuid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet GossipSelectOptionPacket::build(uint64_t npcGuid, uint32_t menuId, uint32_t optionId, const std::string& code) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GOSSIP_SELECT_OPTION));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GOSSIP_SELECT_OPTION));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(menuId);
|
||||
packet.writeUInt32(optionId);
|
||||
|
|
@ -2476,7 +2474,7 @@ network::Packet GossipSelectOptionPacket::build(uint64_t npcGuid, uint32_t menuI
|
|||
}
|
||||
|
||||
network::Packet QuestgiverQueryQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_QUERY_QUEST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_QUERY_QUEST));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
packet.writeUInt8(1); // isDialogContinued = 1 (from gossip)
|
||||
|
|
@ -2484,7 +2482,7 @@ network::Packet QuestgiverQueryQuestPacket::build(uint64_t npcGuid, uint32_t que
|
|||
}
|
||||
|
||||
network::Packet QuestgiverAcceptQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_ACCEPT_QUEST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_ACCEPT_QUEST));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
packet.writeUInt32(0); // unused
|
||||
|
|
@ -2584,7 +2582,7 @@ bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data
|
|||
// ============================================================
|
||||
|
||||
network::Packet BinderActivatePacket::build(uint64_t npcGuid) {
|
||||
network::Packet pkt(static_cast<uint16_t>(Opcode::CMSG_BINDER_ACTIVATE));
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_BINDER_ACTIVATE));
|
||||
pkt.writeUInt64(npcGuid);
|
||||
return pkt;
|
||||
}
|
||||
|
|
@ -2703,14 +2701,14 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
}
|
||||
|
||||
network::Packet QuestgiverCompleteQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_COMPLETE_QUEST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_COMPLETE_QUEST));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet QuestgiverChooseRewardPacket::build(uint64_t npcGuid, uint32_t questId, uint32_t rewardIndex) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_CHOOSE_REWARD));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_QUESTGIVER_CHOOSE_REWARD));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
packet.writeUInt32(rewardIndex);
|
||||
|
|
@ -2722,13 +2720,13 @@ network::Packet QuestgiverChooseRewardPacket::build(uint64_t npcGuid, uint32_t q
|
|||
// ============================================================
|
||||
|
||||
network::Packet ListInventoryPacket::build(uint64_t npcGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LIST_INVENTORY));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LIST_INVENTORY));
|
||||
packet.writeUInt64(npcGuid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_BUY_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_BUY_ITEM));
|
||||
packet.writeUInt64(vendorGuid);
|
||||
packet.writeUInt32(itemId);
|
||||
packet.writeUInt32(slot);
|
||||
|
|
@ -2738,7 +2736,7 @@ network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint3
|
|||
}
|
||||
|
||||
network::Packet SellItemPacket::build(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SELL_ITEM));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SELL_ITEM));
|
||||
packet.writeUInt64(vendorGuid);
|
||||
packet.writeUInt64(itemGuid);
|
||||
packet.writeUInt32(count);
|
||||
|
|
@ -2812,7 +2810,7 @@ bool TrainerListParser::parse(network::Packet& packet, TrainerListData& data) {
|
|||
}
|
||||
|
||||
network::Packet TrainerBuySpellPacket::build(uint64_t trainerGuid, uint32_t spellId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_TRAINER_BUY_SPELL));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_TRAINER_BUY_SPELL));
|
||||
packet.writeUInt64(trainerGuid);
|
||||
packet.writeUInt32(spellId);
|
||||
return packet;
|
||||
|
|
@ -2919,14 +2917,14 @@ bool TalentsInfoParser::parse(network::Packet& packet, TalentsInfoData& data) {
|
|||
}
|
||||
|
||||
network::Packet LearnTalentPacket::build(uint32_t talentId, uint32_t requestedRank) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LEARN_TALENT));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_LEARN_TALENT));
|
||||
packet.writeUInt32(talentId);
|
||||
packet.writeUInt32(requestedRank);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet TalentWipeConfirmPacket::build(bool accept) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_TALENT_WIPE_CONFIRM));
|
||||
network::Packet packet(wireOpcode(Opcode::MSG_TALENT_WIPE_CONFIRM));
|
||||
packet.writeUInt32(accept ? 1 : 0);
|
||||
return packet;
|
||||
}
|
||||
|
|
@ -2936,19 +2934,19 @@ network::Packet TalentWipeConfirmPacket::build(bool accept) {
|
|||
// ============================================================
|
||||
|
||||
network::Packet RepopRequestPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REPOP_REQUEST));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_REPOP_REQUEST));
|
||||
packet.writeUInt8(1); // request release (1 = manual)
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SpiritHealerActivatePacket::build(uint64_t npcGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SPIRIT_HEALER_ACTIVATE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_SPIRIT_HEALER_ACTIVATE));
|
||||
packet.writeUInt64(npcGuid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet ResurrectResponsePacket::build(uint64_t casterGuid, bool accept) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_RESURRECT_RESPONSE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_RESURRECT_RESPONSE));
|
||||
packet.writeUInt64(casterGuid);
|
||||
packet.writeUInt8(accept ? 1 : 0);
|
||||
return packet;
|
||||
|
|
@ -2989,7 +2987,7 @@ bool ActivateTaxiReplyParser::parse(network::Packet& packet, ActivateTaxiReplyDa
|
|||
}
|
||||
|
||||
network::Packet ActivateTaxiExpressPacket::build(uint64_t npcGuid, uint32_t totalCost, const std::vector<uint32_t>& pathNodes) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ACTIVATETAXIEXPRESS));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ACTIVATETAXIEXPRESS));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(totalCost);
|
||||
packet.writeUInt32(static_cast<uint32_t>(pathNodes.size()));
|
||||
|
|
@ -3002,7 +3000,7 @@ network::Packet ActivateTaxiExpressPacket::build(uint64_t npcGuid, uint32_t tota
|
|||
}
|
||||
|
||||
network::Packet ActivateTaxiPacket::build(uint64_t npcGuid, uint32_t srcNode, uint32_t destNode) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ACTIVATETAXI));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_ACTIVATETAXI));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(srcNode);
|
||||
packet.writeUInt32(destNode);
|
||||
|
|
@ -3010,7 +3008,7 @@ network::Packet ActivateTaxiPacket::build(uint64_t npcGuid, uint32_t srcNode, ui
|
|||
}
|
||||
|
||||
network::Packet GameObjectUsePacket::build(uint64_t guid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GAMEOBJECT_USE));
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_GAMEOBJECT_USE));
|
||||
packet.writeUInt64(guid);
|
||||
return packet;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue