mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
Improve melee out-of-range handling and hit sync
Use latest authoritative target coordinates for melee range checks and add heartbeat resync while auto-attack intent is active but hits are not confirmed. Details: - Switch melee distance calculations from interpolated entity position to getLatestX/Y/Z in start, periodic auto-attack checks, and NOTINRANGE retry gating. - Send MSG_MOVE_HEARTBEAT during melee sync ticks when facing is already aligned but server hasn’t confirmed active swings yet (or target is marked out-of-range), preventing 'step forward to start hitting' behavior. - Keep existing warning throttling and stop logic intact.
This commit is contained in:
parent
9a0415ad6b
commit
9954b656f2
1 changed files with 121 additions and 37 deletions
|
|
@ -596,6 +596,9 @@ void GameHandler::update(float deltaTime) {
|
||||||
pendingMoneyDelta_ = 0;
|
pendingMoneyDelta_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (autoAttackRangeWarnCooldown_ > 0.0f) {
|
||||||
|
autoAttackRangeWarnCooldown_ = std::max(0.0f, autoAttackRangeWarnCooldown_ - deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingLoginQuestResync_) {
|
if (pendingLoginQuestResync_) {
|
||||||
pendingLoginQuestResyncTimeout_ -= deltaTime;
|
pendingLoginQuestResyncTimeout_ -= deltaTime;
|
||||||
|
|
@ -903,51 +906,89 @@ void GameHandler::update(float deltaTime) {
|
||||||
if (autoAttackRequested_ && autoAttackTarget != 0) {
|
if (autoAttackRequested_ && autoAttackTarget != 0) {
|
||||||
auto targetEntity = entityManager.getEntity(autoAttackTarget);
|
auto targetEntity = entityManager.getEntity(autoAttackTarget);
|
||||||
if (targetEntity) {
|
if (targetEntity) {
|
||||||
float dx = movementInfo.x - targetEntity->getX();
|
// Use latest server-authoritative target position to avoid stale
|
||||||
float dy = movementInfo.y - targetEntity->getY();
|
// interpolation snapshots masking out-of-range states.
|
||||||
|
const float targetX = targetEntity->getLatestX();
|
||||||
|
const float targetY = targetEntity->getLatestY();
|
||||||
|
const float targetZ = targetEntity->getLatestZ();
|
||||||
|
float dx = movementInfo.x - targetX;
|
||||||
|
float dy = movementInfo.y - targetY;
|
||||||
|
float dz = movementInfo.z - targetZ;
|
||||||
float dist = std::sqrt(dx * dx + dy * dy);
|
float dist = std::sqrt(dx * dx + dy * dy);
|
||||||
|
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
const bool classicLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
const bool classicLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||||
if (dist > 40.0f) {
|
if (dist > 40.0f) {
|
||||||
stopAutoAttack();
|
stopAutoAttack();
|
||||||
LOG_INFO("Left combat: target too far (", dist, " yards)");
|
LOG_INFO("Left combat: target too far (", dist, " yards)");
|
||||||
} else if (state == WorldState::IN_WORLD && socket) {
|
} else if (state == WorldState::IN_WORLD && socket) {
|
||||||
autoAttackResendTimer_ += deltaTime;
|
bool allowResync = true;
|
||||||
autoAttackFacingSyncTimer_ += deltaTime;
|
const float meleeRange = classicLike ? 5.25f : 5.75f;
|
||||||
|
if (dist3d > meleeRange) {
|
||||||
// Re-request swing more aggressively until server confirms active loop.
|
autoAttackOutOfRange_ = true;
|
||||||
float resendInterval = 1.0f;
|
autoAttackOutOfRangeTime_ += deltaTime;
|
||||||
if (!autoAttacking || autoAttackOutOfRange_) {
|
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
|
||||||
resendInterval = classicLike ? 0.25f : 0.50f;
|
addSystemChatMessage("Target is too far away.");
|
||||||
}
|
autoAttackRangeWarnCooldown_ = 1.25f;
|
||||||
if (autoAttackResendTimer_ >= resendInterval) {
|
}
|
||||||
autoAttackResendTimer_ = 0.0f;
|
// Stop chasing stale swings when the target remains out of range.
|
||||||
auto pkt = AttackSwingPacket::build(autoAttackTarget);
|
if (autoAttackOutOfRangeTime_ > 2.0f && dist3d > 9.0f) {
|
||||||
socket->send(pkt);
|
stopAutoAttack();
|
||||||
|
addSystemChatMessage("Auto-attack stopped: target out of range.");
|
||||||
|
allowResync = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
autoAttackOutOfRange_ = false;
|
||||||
|
autoAttackOutOfRangeTime_ = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep server-facing aligned with our current melee target.
|
if (allowResync) {
|
||||||
// Some vanilla-family realms become strict about front-arc checks unless
|
autoAttackResendTimer_ += deltaTime;
|
||||||
// the client sends explicit facing updates while stationary.
|
autoAttackFacingSyncTimer_ += deltaTime;
|
||||||
const float facingSyncInterval = classicLike ? 0.10f : 0.20f;
|
|
||||||
if (autoAttackFacingSyncTimer_ >= facingSyncInterval) {
|
// Re-request swing more aggressively until server confirms active loop.
|
||||||
autoAttackFacingSyncTimer_ = 0.0f;
|
float resendInterval = 1.0f;
|
||||||
float toTargetX = targetEntity->getX() - movementInfo.x;
|
if (!autoAttacking || autoAttackOutOfRange_) {
|
||||||
float toTargetY = targetEntity->getY() - movementInfo.y;
|
resendInterval = classicLike ? 0.25f : 0.50f;
|
||||||
if (std::abs(toTargetX) > 0.01f || std::abs(toTargetY) > 0.01f) {
|
}
|
||||||
float desired = std::atan2(-toTargetY, toTargetX);
|
if (autoAttackResendTimer_ >= resendInterval) {
|
||||||
float diff = desired - movementInfo.orientation;
|
autoAttackResendTimer_ = 0.0f;
|
||||||
while (diff > static_cast<float>(M_PI)) diff -= 2.0f * static_cast<float>(M_PI);
|
auto pkt = AttackSwingPacket::build(autoAttackTarget);
|
||||||
while (diff < -static_cast<float>(M_PI)) diff += 2.0f * static_cast<float>(M_PI);
|
socket->send(pkt);
|
||||||
const float facingThreshold = classicLike ? 0.035f : 0.12f; // ~2deg / ~7deg
|
}
|
||||||
if (std::abs(diff) > facingThreshold) {
|
|
||||||
movementInfo.orientation = desired;
|
// Keep server-facing aligned with our current melee target.
|
||||||
sendMovement(Opcode::MSG_MOVE_SET_FACING);
|
// Some vanilla-family realms become strict about front-arc checks unless
|
||||||
// Follow facing update with a heartbeat to tighten server range/facing checks.
|
// the client sends explicit facing updates while stationary.
|
||||||
|
const float facingSyncInterval = classicLike ? 0.10f : 0.20f;
|
||||||
|
if (autoAttackFacingSyncTimer_ >= facingSyncInterval) {
|
||||||
|
autoAttackFacingSyncTimer_ = 0.0f;
|
||||||
|
float toTargetX = targetX - movementInfo.x;
|
||||||
|
float toTargetY = targetY - movementInfo.y;
|
||||||
|
bool sentMovement = false;
|
||||||
|
if (std::abs(toTargetX) > 0.01f || std::abs(toTargetY) > 0.01f) {
|
||||||
|
float desired = std::atan2(-toTargetY, toTargetX);
|
||||||
|
float diff = desired - movementInfo.orientation;
|
||||||
|
while (diff > static_cast<float>(M_PI)) diff -= 2.0f * static_cast<float>(M_PI);
|
||||||
|
while (diff < -static_cast<float>(M_PI)) diff += 2.0f * static_cast<float>(M_PI);
|
||||||
|
const float facingThreshold = classicLike ? 0.035f : 0.12f; // ~2deg / ~7deg
|
||||||
|
if (std::abs(diff) > facingThreshold) {
|
||||||
|
movementInfo.orientation = desired;
|
||||||
|
sendMovement(Opcode::MSG_MOVE_SET_FACING);
|
||||||
|
// Follow facing update with a heartbeat to tighten server range/facing checks.
|
||||||
|
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||||
|
sentMovement = true;
|
||||||
|
}
|
||||||
|
} else if (classicLike) {
|
||||||
|
// Keep stationary melee position/facing fresh for strict vanilla-family checks.
|
||||||
|
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||||
|
sentMovement = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even when facing is already correct, keep position fresh while
|
||||||
|
// trying to connect melee hits so servers don't require a step.
|
||||||
|
if (!sentMovement && (!autoAttacking || autoAttackOutOfRange_)) {
|
||||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||||
}
|
}
|
||||||
} else if (classicLike) {
|
|
||||||
// Keep stationary melee position/facing fresh for strict vanilla-family checks.
|
|
||||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1572,9 +1613,24 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_ATTACKSWING_NOTINRANGE:
|
case Opcode::SMSG_ATTACKSWING_NOTINRANGE:
|
||||||
autoAttackOutOfRange_ = true;
|
autoAttackOutOfRange_ = true;
|
||||||
|
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
|
||||||
|
addSystemChatMessage("Target is too far away.");
|
||||||
|
autoAttackRangeWarnCooldown_ = 1.25f;
|
||||||
|
}
|
||||||
if (autoAttackRequested_ && autoAttackTarget != 0 && socket) {
|
if (autoAttackRequested_ && autoAttackTarget != 0 && socket) {
|
||||||
auto pkt = AttackSwingPacket::build(autoAttackTarget);
|
// Avoid blind immediate resend loops when target is clearly out of melee range.
|
||||||
socket->send(pkt);
|
bool likelyInRange = true;
|
||||||
|
if (auto target = entityManager.getEntity(autoAttackTarget)) {
|
||||||
|
float dx = movementInfo.x - target->getLatestX();
|
||||||
|
float dy = movementInfo.y - target->getLatestY();
|
||||||
|
float dz = movementInfo.z - target->getLatestZ();
|
||||||
|
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
likelyInRange = (dist3d <= 7.5f);
|
||||||
|
}
|
||||||
|
if (likelyInRange) {
|
||||||
|
auto pkt = AttackSwingPacket::build(autoAttackTarget);
|
||||||
|
socket->send(pkt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_ATTACKSWING_BADFACING:
|
case Opcode::SMSG_ATTACKSWING_BADFACING:
|
||||||
|
|
@ -1598,6 +1654,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_ATTACKSWING_NOTSTANDING:
|
case Opcode::SMSG_ATTACKSWING_NOTSTANDING:
|
||||||
case Opcode::SMSG_ATTACKSWING_CANT_ATTACK:
|
case Opcode::SMSG_ATTACKSWING_CANT_ATTACK:
|
||||||
autoAttackOutOfRange_ = false;
|
autoAttackOutOfRange_ = false;
|
||||||
|
autoAttackOutOfRangeTime_ = 0.0f;
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_ATTACKERSTATEUPDATE:
|
case Opcode::SMSG_ATTACKERSTATEUPDATE:
|
||||||
handleAttackerStateUpdate(packet);
|
handleAttackerStateUpdate(packet);
|
||||||
|
|
@ -4204,6 +4261,13 @@ void GameHandler::handleMotd(network::Packet& packet) {
|
||||||
LOG_INFO(line);
|
LOG_INFO(line);
|
||||||
addSystemChatMessage(std::string("MOTD: ") + line);
|
addSystemChatMessage(std::string("MOTD: ") + line);
|
||||||
}
|
}
|
||||||
|
// Add a visual separator after MOTD block so subsequent messages don't
|
||||||
|
// appear glued to the last MOTD line.
|
||||||
|
MessageChatData spacer;
|
||||||
|
spacer.type = ChatType::SYSTEM;
|
||||||
|
spacer.language = ChatLanguage::UNIVERSAL;
|
||||||
|
spacer.message = "";
|
||||||
|
addLocalChatMessage(spacer);
|
||||||
LOG_INFO("========================================");
|
LOG_INFO("========================================");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7929,17 +7993,36 @@ void GameHandler::emitAllOtherPlayerEquipment() {
|
||||||
void GameHandler::startAutoAttack(uint64_t targetGuid) {
|
void GameHandler::startAutoAttack(uint64_t targetGuid) {
|
||||||
// Can't attack yourself
|
// Can't attack yourself
|
||||||
if (targetGuid == playerGuid) return;
|
if (targetGuid == playerGuid) return;
|
||||||
|
if (targetGuid == 0) return;
|
||||||
|
|
||||||
// Dismount when entering combat
|
// Dismount when entering combat
|
||||||
if (isMounted()) {
|
if (isMounted()) {
|
||||||
dismount();
|
dismount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-side melee range gate to avoid starting "swing forever" loops when
|
||||||
|
// target is already clearly out of range.
|
||||||
|
if (auto target = entityManager.getEntity(targetGuid)) {
|
||||||
|
float dx = movementInfo.x - target->getLatestX();
|
||||||
|
float dy = movementInfo.y - target->getLatestY();
|
||||||
|
float dz = movementInfo.z - target->getLatestZ();
|
||||||
|
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
if (dist3d > 8.0f) {
|
||||||
|
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
|
||||||
|
addSystemChatMessage("Target is too far away.");
|
||||||
|
autoAttackRangeWarnCooldown_ = 1.25f;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
autoAttackRequested_ = true;
|
autoAttackRequested_ = true;
|
||||||
// Keep combat animation/state server-authoritative. We only flip autoAttacking
|
// Keep combat animation/state server-authoritative. We only flip autoAttacking
|
||||||
// on SMSG_ATTACKSTART where attackerGuid == playerGuid.
|
// on SMSG_ATTACKSTART where attackerGuid == playerGuid.
|
||||||
autoAttacking = false;
|
autoAttacking = false;
|
||||||
autoAttackTarget = targetGuid;
|
autoAttackTarget = targetGuid;
|
||||||
autoAttackOutOfRange_ = false;
|
autoAttackOutOfRange_ = false;
|
||||||
|
autoAttackOutOfRangeTime_ = 0.0f;
|
||||||
autoAttackResendTimer_ = 0.0f;
|
autoAttackResendTimer_ = 0.0f;
|
||||||
autoAttackFacingSyncTimer_ = 0.0f;
|
autoAttackFacingSyncTimer_ = 0.0f;
|
||||||
if (state == WorldState::IN_WORLD && socket) {
|
if (state == WorldState::IN_WORLD && socket) {
|
||||||
|
|
@ -7955,6 +8038,7 @@ void GameHandler::stopAutoAttack() {
|
||||||
autoAttacking = false;
|
autoAttacking = false;
|
||||||
autoAttackTarget = 0;
|
autoAttackTarget = 0;
|
||||||
autoAttackOutOfRange_ = false;
|
autoAttackOutOfRange_ = false;
|
||||||
|
autoAttackOutOfRangeTime_ = 0.0f;
|
||||||
autoAttackResendTimer_ = 0.0f;
|
autoAttackResendTimer_ = 0.0f;
|
||||||
autoAttackFacingSyncTimer_ = 0.0f;
|
autoAttackFacingSyncTimer_ = 0.0f;
|
||||||
if (state == WorldState::IN_WORLD && socket) {
|
if (state == WorldState::IN_WORLD && socket) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue