game/ui: add target cast bar to target frame (SMSG_SPELL_START tracking)

SMSG_SPELL_START fires for all units, not just the player. Previously only
the player's own cast was tracked; now we also track when the current
target is casting, enabling interrupt decisions.

- GameHandler: track targetCasting_/targetCastSpellId_/targetCastTimeTotal_
  /targetCastTimeRemaining_ — updated by SMSG_SPELL_START for the current
  target and ticked down in the update loop each frame
- Target cast cleared when: target changes (setTarget), target's spell
  lands (SMSG_SPELL_GO), or cast timer expires naturally
- game_screen: renderTargetFrame shows a red cast progress bar between
  the power bar and distance line when the target is casting, with
  spell name + remaining seconds
- Public accessors: isTargetCasting(), getTargetCastSpellId(),
  getTargetCastProgress(), getTargetCastTimeRemaining()
This commit is contained in:
Kelsi 2026-03-09 23:06:40 -07:00
parent 6951b7803d
commit 4d39736d29
3 changed files with 61 additions and 0 deletions

View file

@ -759,6 +759,16 @@ void GameHandler::update(float deltaTime) {
}
}
// Tick down target cast bar
if (targetCasting_ && targetCastTimeRemaining_ > 0.0f) {
targetCastTimeRemaining_ -= deltaTime;
if (targetCastTimeRemaining_ <= 0.0f) {
targetCasting_ = false;
targetCastSpellId_ = 0;
targetCastTimeRemaining_ = 0.0f;
}
}
// Update spell cooldowns (Phase 3)
for (auto it = spellCooldowns.begin(); it != spellCooldowns.end(); ) {
it->second -= deltaTime;
@ -8546,6 +8556,11 @@ void GameHandler::setTarget(uint64_t guid) {
targetGuid = guid;
// Clear target cast bar when target changes
targetCasting_ = false;
targetCastSpellId_ = 0;
targetCastTimeRemaining_ = 0.0f;
// Inform server of target selection (Phase 1)
if (state == WorldState::IN_WORLD && socket) {
auto packet = SetSelectionPacket::build(guid);
@ -12641,6 +12656,14 @@ void GameHandler::handleSpellStart(network::Packet& packet) {
SpellStartData data;
if (!packetParsers_->parseSpellStart(packet, data)) return;
// Track cast bar for the current target (for interrupt awareness)
if (data.casterUnit == targetGuid && data.castTime > 0) {
targetCasting_ = true;
targetCastSpellId_ = data.spellId;
targetCastTimeTotal_ = data.castTime / 1000.0f;
targetCastTimeRemaining_ = targetCastTimeTotal_;
}
// If this is the player's own cast, start cast bar
if (data.casterUnit == playerGuid && data.castTime > 0) {
casting = true;
@ -12713,6 +12736,13 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
castTimeRemaining = 0.0f;
}
// Clear target cast bar when the target's spell lands
if (data.casterUnit == targetGuid) {
targetCasting_ = false;
targetCastSpellId_ = 0;
targetCastTimeRemaining_ = 0.0f;
}
// Show miss/dodge/parry/etc combat text when player's spells miss targets
if (data.casterUnit == playerGuid && !data.missTargets.empty()) {
static const CombatTextEntry::Type missTypes[] = {