mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix(gameplay): keep timeout animation stable on repeated presses and harden M2 elevator sync
This commit is contained in:
parent
f7a996ab26
commit
38210ec186
3 changed files with 111 additions and 34 deletions
|
|
@ -504,16 +504,6 @@ void Application::run() {
|
||||||
LOG_INFO("Shadows: ", enabled ? "ON" : "OFF");
|
LOG_INFO("Shadows: ", enabled ? "ON" : "OFF");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// F7: Test level-up effect (ignore key repeat)
|
|
||||||
else if (event.key.keysym.scancode == SDL_SCANCODE_F7 && event.key.repeat == 0) {
|
|
||||||
if (renderer) {
|
|
||||||
renderer->triggerLevelUpEffect(renderer->getCharacterPosition());
|
|
||||||
LOG_INFO("Triggered test level-up effect");
|
|
||||||
}
|
|
||||||
if (uiManager) {
|
|
||||||
uiManager->getGameScreen().triggerDing(99);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// F8: Debug WMO floor at current position
|
// F8: Debug WMO floor at current position
|
||||||
else if (event.key.keysym.scancode == SDL_SCANCODE_F8 && event.key.repeat == 0) {
|
else if (event.key.keysym.scancode == SDL_SCANCODE_F8 && event.key.repeat == 0) {
|
||||||
if (renderer && renderer->getWMORenderer()) {
|
if (renderer && renderer->getWMORenderer()) {
|
||||||
|
|
@ -1376,6 +1366,24 @@ void Application::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
lastTransportCanonical = tr->position;
|
lastTransportCanonical = tr->position;
|
||||||
lastTransportGuid = gameHandler->getPlayerTransportGuid();
|
lastTransportGuid = gameHandler->getPlayerTransportGuid();
|
||||||
|
|
||||||
|
// Keep passenger locked to elevator vertical motion while grounded.
|
||||||
|
// Without this, floor clamping can hold world-Z static unless the
|
||||||
|
// player is jumping, which makes lifts appear to not move vertically.
|
||||||
|
glm::vec3 tentativeCanonical = core::coords::renderToCanonical(renderPos);
|
||||||
|
glm::vec3 localOffset = gameHandler->getPlayerTransportOffset();
|
||||||
|
localOffset.x = tentativeCanonical.x - tr->position.x;
|
||||||
|
localOffset.y = tentativeCanonical.y - tr->position.y;
|
||||||
|
if (renderer->getCameraController() &&
|
||||||
|
!renderer->getCameraController()->isGrounded()) {
|
||||||
|
// While airborne (jump/fall), allow local Z offset to change.
|
||||||
|
localOffset.z = tentativeCanonical.z - tr->position.z;
|
||||||
|
}
|
||||||
|
gameHandler->setPlayerTransportOffset(localOffset);
|
||||||
|
|
||||||
|
glm::vec3 lockedCanonical = tr->position + localOffset;
|
||||||
|
renderPos = core::coords::canonicalToRender(lockedCanonical);
|
||||||
|
renderer->getCharacterPosition() = renderPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1420,9 +1428,11 @@ void Application::update(float deltaTime) {
|
||||||
glm::vec3 playerCanonical = core::coords::renderToCanonical(renderPos);
|
glm::vec3 playerCanonical = core::coords::renderToCanonical(renderPos);
|
||||||
constexpr float kM2BoardHorizDistSq = 12.0f * 12.0f;
|
constexpr float kM2BoardHorizDistSq = 12.0f * 12.0f;
|
||||||
constexpr float kM2BoardVertDist = 15.0f;
|
constexpr float kM2BoardVertDist = 15.0f;
|
||||||
constexpr float kTbLiftBoardHorizDistSq = 42.0f * 42.0f;
|
constexpr float kTbLiftBoardHorizDistSq = 95.0f * 95.0f;
|
||||||
constexpr float kTbLiftBoardVertDist = 80.0f;
|
constexpr float kTbLiftBoardVertDist = 80.0f;
|
||||||
|
|
||||||
|
uint64_t bestGuid = 0;
|
||||||
|
float bestScore = 1e30f;
|
||||||
for (auto& [guid, transport] : tm->getTransports()) {
|
for (auto& [guid, transport] : tm->getTransports()) {
|
||||||
if (!transport.isM2) continue;
|
if (!transport.isM2) continue;
|
||||||
const bool isThunderBluffLift =
|
const bool isThunderBluffLift =
|
||||||
|
|
@ -1437,9 +1447,18 @@ void Application::update(float deltaTime) {
|
||||||
float horizDistSq = diff.x * diff.x + diff.y * diff.y;
|
float horizDistSq = diff.x * diff.x + diff.y * diff.y;
|
||||||
float vertDist = std::abs(diff.z);
|
float vertDist = std::abs(diff.z);
|
||||||
if (horizDistSq < maxHorizDistSq && vertDist < maxVertDist) {
|
if (horizDistSq < maxHorizDistSq && vertDist < maxVertDist) {
|
||||||
gameHandler->setPlayerOnTransport(guid, playerCanonical - transport.position);
|
float score = horizDistSq + vertDist * vertDist;
|
||||||
LOG_DEBUG("M2 transport boarding: guid=0x", std::hex, guid, std::dec);
|
if (score < bestScore) {
|
||||||
break;
|
bestScore = score;
|
||||||
|
bestGuid = guid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestGuid != 0) {
|
||||||
|
auto* tr = tm->getTransport(bestGuid);
|
||||||
|
if (tr) {
|
||||||
|
gameHandler->setPlayerOnTransport(bestGuid, playerCanonical - tr->position);
|
||||||
|
LOG_DEBUG("M2 transport boarding: guid=0x", std::hex, bestGuid, std::dec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1455,7 +1474,7 @@ void Application::update(float deltaTime) {
|
||||||
const bool isThunderBluffLift =
|
const bool isThunderBluffLift =
|
||||||
(tr->entry >= 20649u && tr->entry <= 20657u);
|
(tr->entry >= 20649u && tr->entry <= 20657u);
|
||||||
constexpr float kM2DisembarkHorizDistSq = 15.0f * 15.0f;
|
constexpr float kM2DisembarkHorizDistSq = 15.0f * 15.0f;
|
||||||
constexpr float kTbLiftDisembarkHorizDistSq = 52.0f * 52.0f;
|
constexpr float kTbLiftDisembarkHorizDistSq = 120.0f * 120.0f;
|
||||||
const float disembarkHorizDistSq = isThunderBluffLift
|
const float disembarkHorizDistSq = isThunderBluffLift
|
||||||
? kTbLiftDisembarkHorizDistSq
|
? kTbLiftDisembarkHorizDistSq
|
||||||
: kM2DisembarkHorizDistSq;
|
: kM2DisembarkHorizDistSq;
|
||||||
|
|
@ -2902,6 +2921,12 @@ void Application::setupUICallbacks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
transportManager->registerTransport(guid, wmoInstanceId, pathId, canonicalSpawnPos, entry);
|
transportManager->registerTransport(guid, wmoInstanceId, pathId, canonicalSpawnPos, entry);
|
||||||
|
// Keep type in sync with the spawned instance; needed for M2 lift boarding/motion.
|
||||||
|
if (!it->second.isWmo) {
|
||||||
|
if (auto* tr = transportManager->getTransport(guid)) {
|
||||||
|
tr->isM2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pendingTransportMoves_[guid] = PendingTransportMove{x, y, z, orientation};
|
pendingTransportMoves_[guid] = PendingTransportMove{x, y, z, orientation};
|
||||||
LOG_DEBUG("Cannot auto-spawn transport 0x", std::hex, guid, std::dec,
|
LOG_DEBUG("Cannot auto-spawn transport 0x", std::hex, guid, std::dec,
|
||||||
|
|
|
||||||
|
|
@ -309,6 +309,16 @@ bool isPlaceholderQuestTitle(const std::string& s) {
|
||||||
return s.rfind("Quest #", 0) == 0;
|
return s.rfind("Quest #", 0) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float mergeCooldownSeconds(float current, float incoming) {
|
||||||
|
constexpr float kEpsilon = 0.05f;
|
||||||
|
if (incoming <= 0.0f) return 0.0f;
|
||||||
|
if (current <= 0.0f) return incoming;
|
||||||
|
// Cooldowns should normally tick down. If a duplicate/late packet reports a
|
||||||
|
// larger value, keep the local remaining time to avoid visible timer resets.
|
||||||
|
if (incoming > current + kEpsilon) return current;
|
||||||
|
return incoming;
|
||||||
|
}
|
||||||
|
|
||||||
bool looksLikeQuestDescriptionText(const std::string& s) {
|
bool looksLikeQuestDescriptionText(const std::string& s) {
|
||||||
int spaces = 0;
|
int spaces = 0;
|
||||||
int commas = 0;
|
int commas = 0;
|
||||||
|
|
@ -3208,7 +3218,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
uint32_t cdMs = packet.readUInt32();
|
uint32_t cdMs = packet.readUInt32();
|
||||||
float cdSec = cdMs / 1000.0f;
|
float cdSec = cdMs / 1000.0f;
|
||||||
if (cdSec > 0.0f) {
|
if (cdSec > 0.0f) {
|
||||||
if (spellId != 0) spellCooldowns[spellId] = cdSec;
|
if (spellId != 0) {
|
||||||
|
auto it = spellCooldowns.find(spellId);
|
||||||
|
if (it == spellCooldowns.end()) {
|
||||||
|
spellCooldowns[spellId] = cdSec;
|
||||||
|
} else {
|
||||||
|
it->second = mergeCooldownSeconds(it->second, cdSec);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Resolve itemId from the GUID so item-type slots are also updated
|
// Resolve itemId from the GUID so item-type slots are also updated
|
||||||
uint32_t itemId = 0;
|
uint32_t itemId = 0;
|
||||||
auto iit = onlineItems_.find(itemGuid);
|
auto iit = onlineItems_.find(itemGuid);
|
||||||
|
|
@ -3217,8 +3234,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
bool match = (spellId != 0 && slot.type == ActionBarSlot::SPELL && slot.id == spellId)
|
bool match = (spellId != 0 && slot.type == ActionBarSlot::SPELL && slot.id == spellId)
|
||||||
|| (itemId != 0 && slot.type == ActionBarSlot::ITEM && slot.id == itemId);
|
|| (itemId != 0 && slot.type == ActionBarSlot::ITEM && slot.id == itemId);
|
||||||
if (match) {
|
if (match) {
|
||||||
slot.cooldownTotal = cdSec;
|
float prevRemaining = slot.cooldownRemaining;
|
||||||
slot.cooldownRemaining = cdSec;
|
float merged = mergeCooldownSeconds(slot.cooldownRemaining, cdSec);
|
||||||
|
slot.cooldownRemaining = merged;
|
||||||
|
if (slot.cooldownTotal <= 0.0f || prevRemaining <= 0.0f) {
|
||||||
|
slot.cooldownTotal = cdSec;
|
||||||
|
} else {
|
||||||
|
slot.cooldownTotal = std::max(slot.cooldownTotal, merged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG("SMSG_ITEM_COOLDOWN: itemGuid=0x", std::hex, itemGuid, std::dec,
|
LOG_DEBUG("SMSG_ITEM_COOLDOWN: itemGuid=0x", std::hex, itemGuid, std::dec,
|
||||||
|
|
@ -9849,8 +9872,17 @@ void GameHandler::sendMovement(Opcode opcode) {
|
||||||
sanitizeMovementForTaxi();
|
sanitizeMovementForTaxi();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transport data if player is on a transport
|
bool includeTransportInWire = isOnTransport();
|
||||||
if (isOnTransport()) {
|
if (includeTransportInWire && transportManager_) {
|
||||||
|
if (auto* tr = transportManager_->getTransport(playerTransportGuid_); tr && tr->isM2) {
|
||||||
|
// Client-detected M2 elevators/trams are not always server-recognized transports.
|
||||||
|
// Sending ONTRANSPORT for these can trigger bad fall-state corrections server-side.
|
||||||
|
includeTransportInWire = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add transport data if player is on a server-recognized transport
|
||||||
|
if (includeTransportInWire) {
|
||||||
// Keep authoritative world position synchronized to parent transport transform
|
// Keep authoritative world position synchronized to parent transport transform
|
||||||
// so heartbeats/corrections don't drag the passenger through geometry.
|
// so heartbeats/corrections don't drag the passenger through geometry.
|
||||||
if (transportManager_) {
|
if (transportManager_) {
|
||||||
|
|
@ -9892,7 +9924,7 @@ void GameHandler::sendMovement(Opcode opcode) {
|
||||||
|
|
||||||
LOG_DEBUG("Sending movement packet: opcode=0x", std::hex,
|
LOG_DEBUG("Sending movement packet: opcode=0x", std::hex,
|
||||||
wireOpcode(opcode), std::dec,
|
wireOpcode(opcode), std::dec,
|
||||||
(isOnTransport() ? " ONTRANSPORT" : ""));
|
(includeTransportInWire ? " ONTRANSPORT" : ""));
|
||||||
|
|
||||||
// Convert canonical → server coordinates for the wire
|
// Convert canonical → server coordinates for the wire
|
||||||
MovementInfo wireInfo = movementInfo;
|
MovementInfo wireInfo = movementInfo;
|
||||||
|
|
@ -9905,7 +9937,7 @@ void GameHandler::sendMovement(Opcode opcode) {
|
||||||
wireInfo.orientation = core::coords::canonicalToServerYaw(wireInfo.orientation);
|
wireInfo.orientation = core::coords::canonicalToServerYaw(wireInfo.orientation);
|
||||||
|
|
||||||
// Also convert transport local position to server coordinates if on transport
|
// Also convert transport local position to server coordinates if on transport
|
||||||
if (isOnTransport()) {
|
if (includeTransportInWire) {
|
||||||
glm::vec3 serverTransportPos = core::coords::canonicalToServer(
|
glm::vec3 serverTransportPos = core::coords::canonicalToServer(
|
||||||
glm::vec3(wireInfo.transportX, wireInfo.transportY, wireInfo.transportZ));
|
glm::vec3(wireInfo.transportX, wireInfo.transportY, wireInfo.transportZ));
|
||||||
wireInfo.transportX = serverTransportPos.x;
|
wireInfo.transportX = serverTransportPos.x;
|
||||||
|
|
@ -16744,9 +16776,12 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) {
|
||||||
socket->send(packet);
|
socket->send(packet);
|
||||||
LOG_INFO("Casting spell: ", spellId, " on 0x", std::hex, target, std::dec);
|
LOG_INFO("Casting spell: ", spellId, " on 0x", std::hex, target, std::dec);
|
||||||
|
|
||||||
// Optimistically start GCD immediately on cast — server will confirm or override
|
// Optimistically start GCD immediately on cast, but do not restart it while
|
||||||
gcdTotal_ = 1.5f;
|
// already active (prevents timeout animation reset on repeated key presses).
|
||||||
gcdStartedAt_ = std::chrono::steady_clock::now();
|
if (!isGCDActive()) {
|
||||||
|
gcdTotal_ = 1.5f;
|
||||||
|
gcdStartedAt_ = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::cancelCast() {
|
void GameHandler::cancelCast() {
|
||||||
|
|
@ -17261,13 +17296,24 @@ void GameHandler::handleSpellCooldown(network::Packet& packet) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
spellCooldowns[spellId] = seconds;
|
auto it = spellCooldowns.find(spellId);
|
||||||
|
if (it == spellCooldowns.end()) {
|
||||||
|
spellCooldowns[spellId] = seconds;
|
||||||
|
} else {
|
||||||
|
it->second = mergeCooldownSeconds(it->second, seconds);
|
||||||
|
}
|
||||||
for (auto& slot : actionBar) {
|
for (auto& slot : actionBar) {
|
||||||
bool match = (slot.type == ActionBarSlot::SPELL && slot.id == spellId)
|
bool match = (slot.type == ActionBarSlot::SPELL && slot.id == spellId)
|
||||||
|| (cdItemId != 0 && slot.type == ActionBarSlot::ITEM && slot.id == cdItemId);
|
|| (cdItemId != 0 && slot.type == ActionBarSlot::ITEM && slot.id == cdItemId);
|
||||||
if (match) {
|
if (match) {
|
||||||
slot.cooldownTotal = seconds;
|
float prevRemaining = slot.cooldownRemaining;
|
||||||
slot.cooldownRemaining = seconds;
|
float merged = mergeCooldownSeconds(slot.cooldownRemaining, seconds);
|
||||||
|
slot.cooldownRemaining = merged;
|
||||||
|
if (slot.cooldownTotal <= 0.0f || prevRemaining <= 0.0f) {
|
||||||
|
slot.cooldownTotal = seconds;
|
||||||
|
} else {
|
||||||
|
slot.cooldownTotal = std::max(slot.cooldownTotal, merged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,8 +251,10 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
|
||||||
if (path.durationMs == 0) {
|
if (path.durationMs == 0) {
|
||||||
// Just update transform (position already set)
|
// Just update transform (position already set)
|
||||||
updateTransformMatrices(transport);
|
updateTransformMatrices(transport);
|
||||||
if (wmoRenderer_) {
|
if (transport.isM2) {
|
||||||
wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||||
|
} else {
|
||||||
|
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -287,8 +289,10 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
|
||||||
} else {
|
} else {
|
||||||
// Strict server-authoritative mode: do not guess movement between server snapshots.
|
// Strict server-authoritative mode: do not guess movement between server snapshots.
|
||||||
updateTransformMatrices(transport);
|
updateTransformMatrices(transport);
|
||||||
if (wmoRenderer_) {
|
if (transport.isM2) {
|
||||||
wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||||
|
} else {
|
||||||
|
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -777,8 +781,10 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTransformMatrices(*transport);
|
updateTransformMatrices(*transport);
|
||||||
if (wmoRenderer_) {
|
if (transport->isM2) {
|
||||||
wmoRenderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
||||||
|
} else {
|
||||||
|
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue