Update opcode data and movement integration docs/code

This commit is contained in:
Kelsi 2026-02-18 03:15:25 -08:00
parent 86127f0ddf
commit bd0ce17794
11 changed files with 41 additions and 15 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: Kelsidavis

View file

@ -94,6 +94,7 @@
"CMSG_BINDER_ACTIVATE": "0x1B5", "CMSG_BINDER_ACTIVATE": "0x1B5",
"SMSG_LOG_XPGAIN": "0x1D0", "SMSG_LOG_XPGAIN": "0x1D0",
"SMSG_MONSTER_MOVE": "0x0DD", "SMSG_MONSTER_MOVE": "0x0DD",
"SMSG_COMPRESSED_MOVES": "0x06B",
"CMSG_ATTACKSWING": "0x141", "CMSG_ATTACKSWING": "0x141",
"CMSG_ATTACKSTOP": "0x142", "CMSG_ATTACKSTOP": "0x142",
"SMSG_ATTACKSTART": "0x143", "SMSG_ATTACKSTART": "0x143",

View file

@ -93,7 +93,8 @@
"CMSG_SET_ACTIVE_MOVER": "0x26A", "CMSG_SET_ACTIVE_MOVER": "0x26A",
"CMSG_BINDER_ACTIVATE": "0x1B5", "CMSG_BINDER_ACTIVATE": "0x1B5",
"SMSG_LOG_XPGAIN": "0x1D0", "SMSG_LOG_XPGAIN": "0x1D0",
"SMSG_MONSTER_MOVE": "0x0DD", "SMSG_MONSTER_MOVE": "0x2FB",
"SMSG_COMPRESSED_MOVES": "0x06B",
"CMSG_ATTACKSWING": "0x141", "CMSG_ATTACKSWING": "0x141",
"CMSG_ATTACKSTOP": "0x142", "CMSG_ATTACKSTOP": "0x142",
"SMSG_ATTACKSTART": "0x143", "SMSG_ATTACKSTART": "0x143",

View file

@ -6,6 +6,8 @@
A native C++ World of Warcraft client with a custom OpenGL renderer. A native C++ World of Warcraft client with a custom OpenGL renderer.
[![Sponsor](https://img.shields.io/github/sponsors/Kelsidavis?label=Sponsor&logo=GitHub)](https://github.com/sponsors/Kelsidavis)
[![Watch the video](https://img.youtube.com/vi/Pd9JuYYxu0o/maxresdefault.jpg)](https://youtu.be/Pd9JuYYxu0o) [![Watch the video](https://img.youtube.com/vi/Pd9JuYYxu0o/maxresdefault.jpg)](https://youtu.be/Pd9JuYYxu0o)
[![Watch the video](https://img.youtube.com/vi/J4NXegzqWSQ/maxresdefault.jpg)](https://youtu.be/J4NXegzqWSQ) [![Watch the video](https://img.youtube.com/vi/J4NXegzqWSQ/maxresdefault.jpg)](https://youtu.be/J4NXegzqWSQ)

View file

@ -105,6 +105,13 @@ public:
bool isEntityMoving() const { return isMoving_; } bool isEntityMoving() const { return isMoving_; }
// Returns the latest server-authoritative position: destination if moving, current if not.
// Unlike getX/Y/Z (which only update via updateMovement), this always reflects the
// last known server position regardless of distance culling.
float getLatestX() const { return isMoving_ ? moveEndX_ : x; }
float getLatestY() const { return isMoving_ ? moveEndY_ : y; }
float getLatestZ() const { return isMoving_ ? moveEndZ_ : z; }
// Object type // Object type
ObjectType getType() const { return type; } ObjectType getType() const { return type; }
void setType(ObjectType t) { type = t; } void setType(ObjectType t) { type = t; }

View file

@ -1049,6 +1049,7 @@ private:
// ---- Creature movement handler ---- // ---- Creature movement handler ----
void handleMonsterMove(network::Packet& packet); void handleMonsterMove(network::Packet& packet);
void handleCompressedMoves(network::Packet& packet);
void handleMonsterMoveTransport(network::Packet& packet); void handleMonsterMoveTransport(network::Packet& packet);
// ---- Other player movement (MSG_MOVE_* from server) ---- // ---- Other player movement (MSG_MOVE_* from server) ----

View file

@ -157,6 +157,7 @@ enum class LogicalOpcode : uint16_t {
// ---- Creature Movement ---- // ---- Creature Movement ----
SMSG_MONSTER_MOVE, SMSG_MONSTER_MOVE,
SMSG_COMPRESSED_MOVES, // Vanilla/Classic batch movement packet (0x6B)
// ---- Phase 2: Combat Core ---- // ---- Phase 2: Combat Core ----
CMSG_ATTACKSWING, CMSG_ATTACKSWING,

View file

@ -1450,7 +1450,11 @@ struct MonsterMoveData {
class MonsterMoveParser { class MonsterMoveParser {
public: public:
// WotLK 3.3.5a format: PackedGUID + uint8 unk + float[3] + uint32 splineId + uint8 moveType + ...
static bool parse(network::Packet& packet, MonsterMoveData& data); static bool parse(network::Packet& packet, MonsterMoveData& data);
// Vanilla 1.12 format: PackedGUID + float[3] + uint32 timeInMs + uint8 moveType + ...
// Used for Classic/TBC/Turtle WoW servers (no splineId, timeInMs before moveType)
static bool parseVanilla(network::Packet& packet, MonsterMoveData& data);
}; };
/** SMSG_ATTACKSTART data */ /** SMSG_ATTACKSTART data */

View file

@ -3419,6 +3419,20 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
} }
} }
// Use the entity's latest server-authoritative position rather than the stale spawn
// position. Movement packets (SMSG_MONSTER_MOVE) can arrive while a creature is still
// queued in pendingCreatureSpawns_ and get silently dropped. getLatestX/Y/Z returns
// the movement destination if the entity is mid-move, which is always up-to-date
// regardless of distance culling (unlike getX/Y/Z which requires updateMovement).
if (gameHandler) {
if (auto entity = gameHandler->getEntityManager().getEntity(guid)) {
x = entity->getLatestX();
y = entity->getLatestY();
z = entity->getLatestZ();
orientation = entity->getOrientation();
}
}
// Convert canonical → render coordinates // Convert canonical → render coordinates
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z)); glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));

View file

@ -126,6 +126,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
{"CMSG_BINDER_ACTIVATE", LogicalOpcode::CMSG_BINDER_ACTIVATE}, {"CMSG_BINDER_ACTIVATE", LogicalOpcode::CMSG_BINDER_ACTIVATE},
{"SMSG_LOG_XPGAIN", LogicalOpcode::SMSG_LOG_XPGAIN}, {"SMSG_LOG_XPGAIN", LogicalOpcode::SMSG_LOG_XPGAIN},
{"SMSG_MONSTER_MOVE", LogicalOpcode::SMSG_MONSTER_MOVE}, {"SMSG_MONSTER_MOVE", LogicalOpcode::SMSG_MONSTER_MOVE},
{"SMSG_COMPRESSED_MOVES", LogicalOpcode::SMSG_COMPRESSED_MOVES},
{"CMSG_ATTACKSWING", LogicalOpcode::CMSG_ATTACKSWING}, {"CMSG_ATTACKSWING", LogicalOpcode::CMSG_ATTACKSWING},
{"CMSG_ATTACKSTOP", LogicalOpcode::CMSG_ATTACKSTOP}, {"CMSG_ATTACKSTOP", LogicalOpcode::CMSG_ATTACKSTOP},
{"SMSG_ATTACKSTART", LogicalOpcode::SMSG_ATTACKSTART}, {"SMSG_ATTACKSTART", LogicalOpcode::SMSG_ATTACKSTART},

View file

@ -1175,22 +1175,15 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
} }
bgpu.texture = tex; bgpu.texture = tex;
bgpu.hasAlpha = (tex != 0 && tex != whiteTexture); bgpu.hasAlpha = (tex != 0 && tex != whiteTexture);
bgpu.textureUnit = static_cast<uint8_t>(batch.textureUnit & 0x1); // textureCoordIndex is an index into a texture coord combo table, not directly
// a UV set selector. Most batches have index=0 (UV set 0). We always use UV set 0
// since we don't have the full combo table — dual-UV effects are rare edge cases.
bgpu.textureUnit = 0;
// Resolve opacity: texture weight track × color animation alpha // Batch is hidden only when its named texture failed to load (avoids white shell artifacts).
// Batches whose texture failed to load are hidden (avoid white shell artifacts) // Do NOT bake transparency/color animation tracks here — they animate over time and
// baking the first keyframe value causes legitimate meshes to become invisible.
bgpu.batchOpacity = texFailed ? 0.0f : 1.0f; bgpu.batchOpacity = texFailed ? 0.0f : 1.0f;
// Texture weight track (via transparency lookup)
if (batch.transparencyIndex < model.textureTransformLookup.size()) {
uint16_t trackIdx = model.textureTransformLookup[batch.transparencyIndex];
if (trackIdx < model.textureWeights.size()) {
bgpu.batchOpacity *= model.textureWeights[trackIdx];
}
}
// Color animation alpha (M2Color.alpha, indexed directly by colorIndex)
if (batch.colorIndex < model.colorAlphas.size()) {
bgpu.batchOpacity *= model.colorAlphas[batch.colorIndex];
}
// Compute batch center and radius for glow sprite positioning // Compute batch center and radius for glow sprite positioning
if (bgpu.blendMode >= 3 && batch.indexCount > 0) { if (bgpu.blendMode >= 3 && batch.indexCount > 0) {