diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index 03e76baa..5b02127b 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -282,6 +282,7 @@ bool ClassicPacketParsers::parseMovementBlock(network::Packet& packet, UpdateBlo /*uint32_t splineId =*/ packet.readUInt32(); uint32_t pointCount = packet.readUInt32(); + // Cap waypoints to prevent DoS from malformed packets allocating huge arrays if (pointCount > 256) return false; // points + endPoint (no splineMode in Classic) @@ -362,7 +363,9 @@ void ClassicPacketParsers::writeMovementPayload(network::Packet& packet, const M // Transport data (Classic ONTRANSPORT = 0x02000000, no timestamp) if (wireFlags & ClassicMoveFlags::ONTRANSPORT) { - // Packed transport GUID + // Packed GUID compression: only transmit non-zero bytes of the 8-byte GUID. + // The mask byte indicates which positions are present (bit N = byte N included). + // This is the standard WoW packed GUID wire format across all expansions. uint8_t transMask = 0; uint8_t transGuidBytes[8]; int transGuidByteCount = 0; diff --git a/src/rendering/charge_effect.cpp b/src/rendering/charge_effect.cpp index f6da288f..60cc5ae7 100644 --- a/src/rendering/charge_effect.cpp +++ b/src/rendering/charge_effect.cpp @@ -474,11 +474,13 @@ void ChargeEffect::emit(const glm::vec3& position, const glm::vec3& direction) { // Spawn dust puffs at feet glm::vec3 horizDir = glm::vec3(direction.x, direction.y, 0.0f); float horizLenSq = glm::dot(horizDir, horizDir); + // Skip dust when character is nearly stationary — prevents NaN from inversesqrt(0) if (horizLenSq < 1e-6f) return; float invHorizLen = glm::inversesqrt(horizLenSq); glm::vec3 backDir = -horizDir * invHorizLen; glm::vec3 sideDir = glm::vec3(-backDir.y, backDir.x, 0.0f); + // Accumulate ~0.48 per frame at 60fps (30 particles/sec * 16ms); emit when >= 1.0 dustAccum_ += 30.0f * 0.016f; while (dustAccum_ >= 1.0f && dustPuffs_.size() < MAX_DUST) { dustAccum_ -= 1.0f; diff --git a/src/rendering/swim_effects.cpp b/src/rendering/swim_effects.cpp index e964f736..5e7f4599 100644 --- a/src/rendering/swim_effects.cpp +++ b/src/rendering/swim_effects.cpp @@ -175,6 +175,8 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou return false; } + // Depth test disabled — insects are screen-space sprites that must always + // render above the water surface regardless of scene geometry. insectPipeline = PipelineBuilder() .setShaders(vertStage, fragStage) .setVertexInput({binding}, attrs) diff --git a/src/ui/auth_screen.cpp b/src/ui/auth_screen.cpp index 4c4b36ba..88264930 100644 --- a/src/ui/auth_screen.cpp +++ b/src/ui/auth_screen.cpp @@ -216,7 +216,9 @@ void AuthScreen::render(auth::AuthHandler& authHandler) { if (music) { if (!loginMusicVolumeAdjusted_) { savedMusicVolume_ = music->getVolume(); - int loginVolume = (savedMusicVolume_ * 80) / 100; // reduce auth music by 20% + // Reduce music to 80% during login so UI button clicks and error sounds + // remain audible over the background track + int loginVolume = (savedMusicVolume_ * 80) / 100; if (loginVolume < 0) loginVolume = 0; if (loginVolume > 100) loginVolume = 100; music->setVolume(loginVolume); diff --git a/src/ui/talent_screen.cpp b/src/ui/talent_screen.cpp index 752762d7..3afc6fd9 100644 --- a/src/ui/talent_screen.cpp +++ b/src/ui/talent_screen.cpp @@ -76,7 +76,9 @@ void TalentScreen::renderTalentTrees(game::GameHandler& gameHandler) { return; } - // Get talent tabs for this class, sorted by orderIndex + // Get talent tabs for this class, sorted by orderIndex. + // WoW class IDs are 1-indexed (Warrior=1..Druid=11); convert to bitmask for + // TalentTab.classMask matching (Warrior=0x1, Paladin=0x2, Hunter=0x4, etc.) uint32_t classMask = 1u << (playerClass - 1); std::vector classTabs; for (const auto& [tabId, tab] : gameHandler.getAllTalentTabs()) {