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
|
||||
uint32_t craftQueueSpellId_ = 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)
|
||||
std::unordered_map<uint64_t, UnitCastState> unitCastStates_;
|
||||
uint64_t pendingGameObjectInteractGuid_ = 0;
|
||||
|
|
|
|||
|
|
@ -2252,9 +2252,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
currentCastSpellId = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
lastInteractedGoGuid_ = 0;
|
||||
// Cancel craft queue on cast failure
|
||||
// Cancel craft queue and spell queue on cast failure
|
||||
craftQueueSpellId_ = 0;
|
||||
craftQueueRemaining_ = 0;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
// Pass player's power type so result 85 says "Not enough rage/energy/etc."
|
||||
int playerPowerType = -1;
|
||||
if (auto pe = entityManager.getEntity(playerGuid)) {
|
||||
|
|
@ -3353,6 +3355,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
castIsChannel = false;
|
||||
currentCastSpellId = 0;
|
||||
lastInteractedGoGuid_ = 0;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||
if (auto* ssm = renderer->getSpellSoundManager()) {
|
||||
ssm->stopPrecast();
|
||||
|
|
@ -9090,6 +9094,8 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
|
|||
lastInteractedGoGuid_ = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
castTimeTotal = 0.0f;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
playerDead_ = false;
|
||||
releasedSpirit_ = false;
|
||||
corpseGuid_ = 0;
|
||||
|
|
@ -17933,7 +17939,17 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) {
|
|||
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)
|
||||
// Using CMSG_CAST_SPELL is more reliable than CMSG_USE_ITEM which
|
||||
|
|
@ -18035,9 +18051,11 @@ void GameHandler::cancelCast() {
|
|||
castIsChannel = false;
|
||||
currentCastSpellId = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
// Cancel craft queue when player manually cancels cast
|
||||
// Cancel craft queue and spell queue when player manually cancels cast
|
||||
craftQueueSpellId_ = 0;
|
||||
craftQueueRemaining_ = 0;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
}
|
||||
|
||||
void GameHandler::startCraftQueue(uint32_t spellId, int count) {
|
||||
|
|
@ -18311,6 +18329,8 @@ void GameHandler::handleCastFailed(network::Packet& packet) {
|
|||
currentCastSpellId = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
lastInteractedGoGuid_ = 0;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
|
||||
// Stop precast sound — spell failed before completing
|
||||
if (auto* renderer = core::Application::getInstance().getRenderer()) {
|
||||
|
|
@ -18483,6 +18503,16 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
|
|||
if (spellCastAnimCallback_) {
|
||||
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 {
|
||||
if (spellCastAnimCallback_) {
|
||||
// End cast animation on other unit
|
||||
|
|
@ -22063,6 +22093,8 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
|
|||
pendingGameObjectInteractGuid_ = 0;
|
||||
lastInteractedGoGuid_ = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
queuedSpellId_ = 0;
|
||||
queuedSpellTarget_ = 0;
|
||||
|
||||
// Send MSG_MOVE_WORLDPORT_ACK to tell the server we're ready
|
||||
if (socket) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue