mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: implement spell queue window (400ms pre-cast)
When castSpell() is called while a timed cast is in progress and castTimeRemaining <= 0.4s, store the spell in queuedSpellId_ instead of silently dropping it. handleSpellGo() fires the queued spell immediately after clearing the cast state, matching the ~400ms spell queue window in Blizzlike WoW clients. Queue is cleared on all cancel/interrupt paths: cancelCast(), handleCastFailed(), SMSG_CAST_RESULT failure, SMSG_SPELL_FAILED, world-teardown, and worldport ACK. Channeled casts never queue (cancelling a channel should remain explicit).
This commit is contained in:
parent
0f8852d290
commit
4907f4124b
2 changed files with 38 additions and 3 deletions
|
|
@ -2725,6 +2725,9 @@ private:
|
||||||
// Repeat-craft queue: re-cast the same profession spell N more times after current cast finishes
|
// Repeat-craft queue: re-cast the same profession spell N more times after current cast finishes
|
||||||
uint32_t craftQueueSpellId_ = 0;
|
uint32_t craftQueueSpellId_ = 0;
|
||||||
int craftQueueRemaining_ = 0;
|
int craftQueueRemaining_ = 0;
|
||||||
|
// Spell queue: next spell to cast within the 400ms window before current cast ends
|
||||||
|
uint32_t queuedSpellId_ = 0;
|
||||||
|
uint64_t queuedSpellTarget_ = 0;
|
||||||
// Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START)
|
// Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START)
|
||||||
std::unordered_map<uint64_t, UnitCastState> unitCastStates_;
|
std::unordered_map<uint64_t, UnitCastState> unitCastStates_;
|
||||||
uint64_t pendingGameObjectInteractGuid_ = 0;
|
uint64_t pendingGameObjectInteractGuid_ = 0;
|
||||||
|
|
|
||||||
|
|
@ -2252,9 +2252,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
currentCastSpellId = 0;
|
currentCastSpellId = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
lastInteractedGoGuid_ = 0;
|
lastInteractedGoGuid_ = 0;
|
||||||
// Cancel craft queue on cast failure
|
// Cancel craft queue and spell queue on cast failure
|
||||||
craftQueueSpellId_ = 0;
|
craftQueueSpellId_ = 0;
|
||||||
craftQueueRemaining_ = 0;
|
craftQueueRemaining_ = 0;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
// Pass player's power type so result 85 says "Not enough rage/energy/etc."
|
// Pass player's power type so result 85 says "Not enough rage/energy/etc."
|
||||||
int playerPowerType = -1;
|
int playerPowerType = -1;
|
||||||
if (auto pe = entityManager.getEntity(playerGuid)) {
|
if (auto pe = entityManager.getEntity(playerGuid)) {
|
||||||
|
|
@ -3353,6 +3355,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
castIsChannel = false;
|
castIsChannel = false;
|
||||||
currentCastSpellId = 0;
|
currentCastSpellId = 0;
|
||||||
lastInteractedGoGuid_ = 0;
|
lastInteractedGoGuid_ = 0;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||||
ssm->stopPrecast();
|
ssm->stopPrecast();
|
||||||
|
|
@ -9090,6 +9094,8 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
|
||||||
lastInteractedGoGuid_ = 0;
|
lastInteractedGoGuid_ = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
castTimeTotal = 0.0f;
|
castTimeTotal = 0.0f;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
playerDead_ = false;
|
playerDead_ = false;
|
||||||
releasedSpirit_ = false;
|
releasedSpirit_ = false;
|
||||||
corpseGuid_ = 0;
|
corpseGuid_ = 0;
|
||||||
|
|
@ -17933,7 +17939,17 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (casting) return; // Already casting
|
if (casting) {
|
||||||
|
// Spell queue: if we're within 400ms of the cast completing (and not channeling),
|
||||||
|
// store the spell so it fires automatically when the cast finishes.
|
||||||
|
if (!castIsChannel && castTimeRemaining > 0.0f && castTimeRemaining <= 0.4f) {
|
||||||
|
queuedSpellId_ = spellId;
|
||||||
|
queuedSpellTarget_ = targetGuid != 0 ? targetGuid : this->targetGuid;
|
||||||
|
LOG_INFO("Spell queue: queued spellId=", spellId, " (", castTimeRemaining * 1000.0f,
|
||||||
|
"ms remaining)");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Hearthstone: cast spell directly (server checks item in inventory)
|
// Hearthstone: cast spell directly (server checks item in inventory)
|
||||||
// Using CMSG_CAST_SPELL is more reliable than CMSG_USE_ITEM which
|
// Using CMSG_CAST_SPELL is more reliable than CMSG_USE_ITEM which
|
||||||
|
|
@ -18035,9 +18051,11 @@ void GameHandler::cancelCast() {
|
||||||
castIsChannel = false;
|
castIsChannel = false;
|
||||||
currentCastSpellId = 0;
|
currentCastSpellId = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
// Cancel craft queue when player manually cancels cast
|
// Cancel craft queue and spell queue when player manually cancels cast
|
||||||
craftQueueSpellId_ = 0;
|
craftQueueSpellId_ = 0;
|
||||||
craftQueueRemaining_ = 0;
|
craftQueueRemaining_ = 0;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::startCraftQueue(uint32_t spellId, int count) {
|
void GameHandler::startCraftQueue(uint32_t spellId, int count) {
|
||||||
|
|
@ -18311,6 +18329,8 @@ void GameHandler::handleCastFailed(network::Packet& packet) {
|
||||||
currentCastSpellId = 0;
|
currentCastSpellId = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
lastInteractedGoGuid_ = 0;
|
lastInteractedGoGuid_ = 0;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
|
|
||||||
// Stop precast sound — spell failed before completing
|
// Stop precast sound — spell failed before completing
|
||||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||||
|
|
@ -18483,6 +18503,16 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
|
||||||
if (spellCastAnimCallback_) {
|
if (spellCastAnimCallback_) {
|
||||||
spellCastAnimCallback_(playerGuid, false, false);
|
spellCastAnimCallback_(playerGuid, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spell queue: fire the next queued spell now that casting has ended
|
||||||
|
if (queuedSpellId_ != 0) {
|
||||||
|
uint32_t nextSpell = queuedSpellId_;
|
||||||
|
uint64_t nextTarget = queuedSpellTarget_;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
|
LOG_INFO("Spell queue: firing queued spellId=", nextSpell);
|
||||||
|
castSpell(nextSpell, nextTarget);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (spellCastAnimCallback_) {
|
if (spellCastAnimCallback_) {
|
||||||
// End cast animation on other unit
|
// End cast animation on other unit
|
||||||
|
|
@ -22063,6 +22093,8 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
|
||||||
pendingGameObjectInteractGuid_ = 0;
|
pendingGameObjectInteractGuid_ = 0;
|
||||||
lastInteractedGoGuid_ = 0;
|
lastInteractedGoGuid_ = 0;
|
||||||
castTimeRemaining = 0.0f;
|
castTimeRemaining = 0.0f;
|
||||||
|
queuedSpellId_ = 0;
|
||||||
|
queuedSpellTarget_ = 0;
|
||||||
|
|
||||||
// Send MSG_MOVE_WORLDPORT_ACK to tell the server we're ready
|
// Send MSG_MOVE_WORLDPORT_ACK to tell the server we're ready
|
||||||
if (socket) {
|
if (socket) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue