mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: R key resets camera angles only; consume all SpellCastTargets bytes
- CameraController::resetAngles(): new method that only resets yaw/pitch without teleporting the player. R key now calls resetAngles() instead of reset() so pressing R no longer moves the character to spawn. The full reset() (position + angles) is still used on world-entry and respawn via application.cpp. - packet_parsers_classic: parseSpellStart now calls skipClassicSpellCastTargets() to consume all target payload bytes (UNIT, ITEM, SOURCE_LOCATION, DEST_LOCATION, etc.) instead of only handling UNIT/OBJECT. Prevents packet-read corruption for ground- targeted AoE spells. - packet_parsers_tbc: added skipTbcSpellCastTargets() static helper (uint32 targetFlags, full payload coverage including TRADE_ITEM and STRING targets). parseSpellStart now uses it.
This commit is contained in:
parent
a731223e47
commit
32497552d1
4 changed files with 95 additions and 33 deletions
|
|
@ -44,6 +44,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
void resetAngles();
|
||||||
void teleportTo(const glm::vec3& pos);
|
void teleportTo(const glm::vec3& pos);
|
||||||
void setOnlineMode(bool online) { onlineMode = online; }
|
void setOnlineMode(bool online) { onlineMode = online; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -520,23 +520,20 @@ bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartDa
|
||||||
data.castFlags = packet.readUInt16(); // uint16 in Vanilla (uint32 in TBC/WotLK)
|
data.castFlags = packet.readUInt16(); // uint16 in Vanilla (uint32 in TBC/WotLK)
|
||||||
data.castTime = packet.readUInt32();
|
data.castTime = packet.readUInt32();
|
||||||
|
|
||||||
// SpellCastTargets: uint16 targetFlags in Vanilla (uint32 in TBC/WotLK)
|
// SpellCastTargets: consume ALL target payload types so subsequent reads stay aligned.
|
||||||
if (rem() < 2) {
|
// Previously only UNIT(0x02)/OBJECT(0x800) were handled; DEST_LOCATION(0x40),
|
||||||
LOG_WARNING("[Classic] Spell start: missing targetFlags");
|
// SOURCE_LOCATION(0x20), and ITEM(0x10) bytes were silently skipped, corrupting
|
||||||
packet.setReadPos(startPos);
|
// castFlags/castTime for every AOE/ground-targeted spell (Rain of Fire, Blizzard, etc.).
|
||||||
return false;
|
{
|
||||||
}
|
uint64_t targetGuid = 0;
|
||||||
uint16_t targetFlags = packet.readUInt16();
|
// skipClassicSpellCastTargets reads uint16 targetFlags and all payloads.
|
||||||
// TARGET_FLAG_UNIT (0x02) or TARGET_FLAG_OBJECT (0x800) carry a packed GUID
|
// Non-fatal on truncation: self-cast spells have zero-byte targets.
|
||||||
if ((targetFlags & 0x02) || (targetFlags & 0x800)) {
|
skipClassicSpellCastTargets(packet, &targetGuid);
|
||||||
if (!hasFullPackedGuid(packet)) {
|
data.targetGuid = targetGuid;
|
||||||
packet.setReadPos(startPos);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("[Classic] Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms");
|
LOG_DEBUG("[Classic] Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms",
|
||||||
|
" targetGuid=0x", std::hex, data.targetGuid, std::dec);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1232,6 +1232,66 @@ bool TbcPacketParsers::parseMailList(network::Packet& packet, std::vector<MailMe
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// skipTbcSpellCastTargets — consume all SpellCastTargets payload bytes for TBC.
|
||||||
|
//
|
||||||
|
// TBC uses uint32 targetFlags (Classic: uint16). Unit/item/object/corpse targets
|
||||||
|
// are PackedGuid (same as Classic). Source/dest location is 3 floats (12 bytes)
|
||||||
|
// with no transport guid (Classic: same; WotLK adds a transport PackedGuid).
|
||||||
|
//
|
||||||
|
// This helper is used by parseSpellStart to ensure the read position advances
|
||||||
|
// past ALL target payload fields so subsequent fields (e.g. those parsed by the
|
||||||
|
// caller after spell targets) are not corrupted.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static bool skipTbcSpellCastTargets(network::Packet& packet, uint64_t* primaryTargetGuid = nullptr) {
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 4) return false;
|
||||||
|
|
||||||
|
const uint32_t targetFlags = packet.readUInt32();
|
||||||
|
|
||||||
|
// Returns false if the packed guid can't be read, otherwise reads and optionally captures it.
|
||||||
|
auto readPackedGuidCond = [&](uint32_t flag, bool capture) -> bool {
|
||||||
|
if (!(targetFlags & flag)) return true;
|
||||||
|
// Packed GUID: 1-byte mask + up to 8 data bytes
|
||||||
|
if (packet.getReadPos() >= packet.getSize()) return false;
|
||||||
|
uint8_t mask = packet.getData()[packet.getReadPos()];
|
||||||
|
size_t needed = 1;
|
||||||
|
for (int b = 0; b < 8; ++b) if (mask & (1u << b)) ++needed;
|
||||||
|
if (packet.getSize() - packet.getReadPos() < needed) return false;
|
||||||
|
uint64_t g = UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
if (capture && primaryTargetGuid && *primaryTargetGuid == 0) *primaryTargetGuid = g;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto skipFloats3 = [&](uint32_t flag) -> bool {
|
||||||
|
if (!(targetFlags & flag)) return true;
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 12) return false;
|
||||||
|
(void)packet.readFloat(); (void)packet.readFloat(); (void)packet.readFloat();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process in wire order matching cmangos-tbc SpellCastTargets::write()
|
||||||
|
if (!readPackedGuidCond(0x0002, true)) return false; // UNIT
|
||||||
|
if (!readPackedGuidCond(0x0004, false)) return false; // UNIT_MINIPET
|
||||||
|
if (!readPackedGuidCond(0x0010, false)) return false; // ITEM
|
||||||
|
if (!skipFloats3(0x0020)) return false; // SOURCE_LOCATION
|
||||||
|
if (!skipFloats3(0x0040)) return false; // DEST_LOCATION
|
||||||
|
|
||||||
|
if (targetFlags & 0x1000) { // TRADE_ITEM: uint8
|
||||||
|
if (packet.getReadPos() >= packet.getSize()) return false;
|
||||||
|
(void)packet.readUInt8();
|
||||||
|
}
|
||||||
|
if (targetFlags & 0x2000) { // STRING: null-terminated
|
||||||
|
const auto& raw = packet.getData();
|
||||||
|
size_t pos = packet.getReadPos();
|
||||||
|
while (pos < raw.size() && raw[pos] != 0) ++pos;
|
||||||
|
if (pos >= raw.size()) return false;
|
||||||
|
packet.setReadPos(pos + 1);
|
||||||
|
}
|
||||||
|
if (!readPackedGuidCond(0x8200, false)) return false; // CORPSE / PVP_CORPSE
|
||||||
|
if (!readPackedGuidCond(0x0800, true)) return false; // OBJECT
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// TbcPacketParsers::parseSpellStart — TBC 2.4.3 SMSG_SPELL_START
|
// TbcPacketParsers::parseSpellStart — TBC 2.4.3 SMSG_SPELL_START
|
||||||
//
|
//
|
||||||
// TBC uses full uint64 GUIDs for casterGuid and casterUnit.
|
// TBC uses full uint64 GUIDs for casterGuid and casterUnit.
|
||||||
|
|
@ -1243,7 +1303,6 @@ bool TbcPacketParsers::parseMailList(network::Packet& packet, std::vector<MailMe
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData& data) {
|
bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData& data) {
|
||||||
data = SpellStartData{};
|
data = SpellStartData{};
|
||||||
const size_t startPos = packet.getReadPos();
|
|
||||||
if (packet.getSize() - packet.getReadPos() < 22) return false;
|
if (packet.getSize() - packet.getReadPos() < 22) return false;
|
||||||
|
|
||||||
data.casterGuid = packet.readUInt64(); // full GUID (object)
|
data.casterGuid = packet.readUInt64(); // full GUID (object)
|
||||||
|
|
@ -1253,23 +1312,19 @@ bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData&
|
||||||
data.castFlags = packet.readUInt32();
|
data.castFlags = packet.readUInt32();
|
||||||
data.castTime = packet.readUInt32();
|
data.castTime = packet.readUInt32();
|
||||||
|
|
||||||
if (packet.getReadPos() + 4 > packet.getSize()) {
|
// SpellCastTargets: consume ALL target payload types to keep the read position
|
||||||
LOG_WARNING("[TBC] Spell start: missing targetFlags");
|
// aligned for any bytes the caller may parse after this (ammo, etc.).
|
||||||
packet.setReadPos(startPos);
|
// The previous code only read UNIT(0x02)/OBJECT(0x800) target GUIDs and left
|
||||||
return false;
|
// DEST_LOCATION(0x40)/SOURCE_LOCATION(0x20)/ITEM(0x10) bytes unconsumed,
|
||||||
|
// corrupting subsequent reads for every AOE/ground-targeted spell cast.
|
||||||
|
{
|
||||||
|
uint64_t targetGuid = 0;
|
||||||
|
skipTbcSpellCastTargets(packet, &targetGuid); // non-fatal on truncation
|
||||||
|
data.targetGuid = targetGuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t targetFlags = packet.readUInt32();
|
LOG_DEBUG("[TBC] Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms",
|
||||||
const bool needsTargetGuid = (targetFlags & 0x02) || (targetFlags & 0x800); // UNIT/OBJECT
|
" targetGuid=0x", std::hex, data.targetGuid, std::dec);
|
||||||
if (needsTargetGuid) {
|
|
||||||
if (packet.getReadPos() + 8 > packet.getSize()) {
|
|
||||||
packet.setReadPos(startPos);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("[TBC] Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -388,10 +388,11 @@ void CameraController::update(float deltaTime) {
|
||||||
if (mounted_) sitting = false;
|
if (mounted_) sitting = false;
|
||||||
xKeyWasDown = xDown;
|
xKeyWasDown = xDown;
|
||||||
|
|
||||||
// Reset camera with R key (edge-triggered) — only when UI doesn't want keyboard
|
// Reset camera angles with R key (edge-triggered) — only when UI doesn't want keyboard
|
||||||
|
// Does NOT move the player; full reset() is reserved for world-entry/respawn.
|
||||||
bool rDown = !uiWantsKeyboard && input.isKeyPressed(SDL_SCANCODE_R);
|
bool rDown = !uiWantsKeyboard && input.isKeyPressed(SDL_SCANCODE_R);
|
||||||
if (rDown && !rKeyWasDown) {
|
if (rDown && !rKeyWasDown) {
|
||||||
reset();
|
resetAngles();
|
||||||
}
|
}
|
||||||
rKeyWasDown = rDown;
|
rKeyWasDown = rDown;
|
||||||
|
|
||||||
|
|
@ -1941,6 +1942,14 @@ void CameraController::processMouseButton(const SDL_MouseButtonEvent& event) {
|
||||||
mouseButtonDown = anyDown;
|
mouseButtonDown = anyDown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CameraController::resetAngles() {
|
||||||
|
if (!camera) return;
|
||||||
|
yaw = defaultYaw;
|
||||||
|
facingYaw = defaultYaw;
|
||||||
|
pitch = defaultPitch;
|
||||||
|
camera->setRotation(yaw, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
void CameraController::reset() {
|
void CameraController::reset() {
|
||||||
if (!camera) {
|
if (!camera) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue