WoW:Detecting an instant cast spell: Difference between revisions

From AddOn Studio
Jump to navigation Jump to search
(demoted headings)
Line 1: Line 1:
= Explanation of SPELLCAST_STOP =
== Explanation of SPELLCAST_STOP ==


Unfortunately until more information is exposed to us through events or the API, there isn't currently a very reliable manner to catch instant cast spells being cast by the player, for interaction with the UI.  For a spell with a casting time, we get SPELLCAST_START with the spell name and the length of the cast time.  When the spell is finished (successfully) casting, we get a SPELLCAST_STOP.  In between we could get SPELLCAST_FAILED, SPELLCAST_INTERRUPTED, etc. which help us keep our states straight.
Unfortunately until more information is exposed to us through events or the API, there isn't currently a very reliable manner to catch instant cast spells being cast by the player, for interaction with the UI.  For a spell with a casting time, we get SPELLCAST_START with the spell name and the length of the cast time.  When the spell is finished (successfully) casting, we get a SPELLCAST_STOP.  In between we could get SPELLCAST_FAILED, SPELLCAST_INTERRUPTED, etc. which help us keep our states straight.
Line 5: Line 5:
With an instant cast spell, we get one single SPELLCAST_STOP message, with no information (no spell name, no target, nothing).  As a result any mod that tries to track something involving these instant cast spells has to guess and play around.  I finished writing a Heal Tracking addon that me asures your healing efficiency, and for this purpose-- I needed those instant cast spells.  This is the method I used to get them reliably.
With an instant cast spell, we get one single SPELLCAST_STOP message, with no information (no spell name, no target, nothing).  As a result any mod that tries to track something involving these instant cast spells has to guess and play around.  I finished writing a Heal Tracking addon that me asures your healing efficiency, and for this purpose-- I needed those instant cast spells.  This is the method I used to get them reliably.


== Spellcasting functions and timeline ==
=== Spellcasting functions and timeline ===


We have the following functions involved in the casting/targeting process:
We have the following functions involved in the casting/targeting process:
Line 20: Line 20:
Keep in mind when looking at this world, there are plenty of combinations that need to be considered when trying to detect instant cast spells.  I try to take these into consideration with my hooks.
Keep in mind when looking at this world, there are plenty of combinations that need to be considered when trying to detect instant cast spells.  I try to take these into consideration with my hooks.


= Implementing this system =
== Implementing this system ==


== Caveats ==
=== Caveats ===


This code will grab every spell that is being cast as well as every action that is being used on the client side.  Due to global cooldowns and other timing the overhead we incur here is very small, and I've done everything I can to make the code efficient and to ensure that the original functions are called as quickly as possible.
This code will grab every spell that is being cast as well as every action that is being used on the client side.  Due to global cooldowns and other timing the overhead we incur here is very small, and I've done everything I can to make the code efficient and to ensure that the original functions are called as quickly as possible.
Line 28: Line 28:
If you are interested in grabbing just a subset of instant cast functions, you can put them in a localized table and use that lookup to determine whether or not you change the MyMod_Spell variable.
If you are interested in grabbing just a subset of instant cast functions, you can put them in a localized table and use that lookup to determine whether or not you change the MyMod_Spell variable.


== Provide a custom tooltip ==
=== Provide a custom tooltip ===


As much as I wish it wasn't necessary, we need some way to glean the spell information.  This step is somewhat optional if you have your own tooltip to look this up, but I like my functions to be somewhat complete in providing accurate information.  Let's create a tooltip in our XML file so we can use it later.
As much as I wish it wasn't necessary, we need some way to glean the spell information.  This step is somewhat optional if you have your own tooltip to look this up, but I like my functions to be somewhat complete in providing accurate information.  Let's create a tooltip in our XML file so we can use it later.
Line 43: Line 43:
  </GameTooltip>
  </GameTooltip>


== Variables ==
=== Variables ===


We could use some variables here to store information as we progress, so we'll define the following in the LUA body:
We could use some variables here to store information as we progress, so we'll define the following in the LUA body:
Line 54: Line 54:
  MyMod_Target = nil
  MyMod_Target = nil


== Hooking the casting functions ==
=== Hooking the casting functions ===


=== CastSpell ===
==== CastSpell ====


  MyMod_oldCastSpell = CastSpell;
  MyMod_oldCastSpell = CastSpell;
Line 80: Line 80:
  CastSpell = MyMod_newCastSpell
  CastSpell = MyMod_newCastSpell


=== CastSpellByName ===
==== CastSpellByName ====
Corrected syntax for 1.10 patch.
Corrected syntax for 1.10 patch.


Line 107: Line 107:
  CastSpellByName = MyMod_newCastSpellByName
  CastSpellByName = MyMod_newCastSpellByName


=== UseAction ===
==== UseAction ====
  MyMod_oldUseAction = UseAction
  MyMod_oldUseAction = UseAction
  function MyMod_newUseAction(a1, a2, a3)
  function MyMod_newUseAction(a1, a2, a3)
Line 130: Line 130:
  UseAction = MyMod_newUseAction
  UseAction = MyMod_newUseAction


== Hooking the targeting functions ==
=== Hooking the targeting functions ===


=== SpellTargetUnit ===
==== SpellTargetUnit ====


This one is nice, and provides us with a good framwork because of its limitations.  Since SpellTargetUnit can ONLY be called after a spellcast has been issued, we can always be sure of who we're casting the spell on.  Even if ClearTarget is used, it just triggers the need to target after casting.
This one is nice, and provides us with a good framwork because of its limitations.  Since SpellTargetUnit can ONLY be called after a spellcast has been issued, we can always be sure of who we're casting the spell on.  Even if ClearTarget is used, it just triggers the need to target after casting.
Line 155: Line 155:
  SpellTargetUnit = MyMod_newSpellTargetUnit
  SpellTargetUnit = MyMod_newSpellTargetUnit


=== TargetUnit ===
==== TargetUnit ====


This is the same code from SpellTargetUnit, with the names changed to protect the innocent.
This is the same code from SpellTargetUnit, with the names changed to protect the innocent.
Line 178: Line 178:
  TargetUnit = MyMod_newTargetUnit
  TargetUnit = MyMod_newTargetUnit
   
   
=== SpellStopTargeting ===
==== SpellStopTargeting ====


All we need to do here is clear MyMod_Spell to ensure we keep our state clean.
All we need to do here is clear MyMod_Spell to ensure we keep our state clean.
Line 194: Line 194:
  SpellStopTargeting = MyMod_newSpellStopTargeting
  SpellStopTargeting = MyMod_newSpellStopTargeting


=== CameraOrSelectOrMoveStart ===
==== CameraOrSelectOrMoveStart ====


After the 1.10 patch, CameraOrSelectOrMoveStart may no longer be hooked by custom UI mods.  The below code is preserved for historical purposes, but will no longer work.
After the 1.10 patch, CameraOrSelectOrMoveStart may no longer be hooked by custom UI mods.  The below code is preserved for historical purposes, but will no longer work.
Line 222: Line 222:
  CameraOrSelectOrMoveStart = MyMod_newCameraOrSelectOrMoveStart
  CameraOrSelectOrMoveStart = MyMod_newCameraOrSelectOrMoveStart


== Hooking the mouse click ==
=== Hooking the mouse click ===
In the 1.10 patch, CameraOrSelectOrMoveStart can no longer be hooked by the user interface.  Attempting to call the hooked function will cause your addon to be reported as blocked by the game.  The solution to this problem is to hook the mouse click itself.  Since the user clicks on the screen to target a spell, all we have to do is add a call to our custom script when this occurs.
In the 1.10 patch, CameraOrSelectOrMoveStart can no longer be hooked by the user interface.  Attempting to call the hooked function will cause your addon to be reported as blocked by the game.  The solution to this problem is to hook the mouse click itself.  Since the user clicks on the screen to target a spell, all we have to do is add a call to our custom script when this occurs.


=== OnMouseDown ===
==== OnMouseDown ====
   
   
   function myMouseDown()
   function myMouseDown()
Line 250: Line 250:
  end
  end


== Watching for SPELLCAST events ==
=== Watching for SPELLCAST events ===


In your OnLoad handler, register the following events:
In your OnLoad handler, register the following events:

Revision as of 17:49, 7 January 2007

Explanation of SPELLCAST_STOP

Unfortunately until more information is exposed to us through events or the API, there isn't currently a very reliable manner to catch instant cast spells being cast by the player, for interaction with the UI. For a spell with a casting time, we get SPELLCAST_START with the spell name and the length of the cast time. When the spell is finished (successfully) casting, we get a SPELLCAST_STOP. In between we could get SPELLCAST_FAILED, SPELLCAST_INTERRUPTED, etc. which help us keep our states straight.

With an instant cast spell, we get one single SPELLCAST_STOP message, with no information (no spell name, no target, nothing). As a result any mod that tries to track something involving these instant cast spells has to guess and play around. I finished writing a Heal Tracking addon that me asures your healing efficiency, and for this purpose-- I needed those instant cast spells. This is the method I used to get them reliably.

Spellcasting functions and timeline

We have the following functions involved in the casting/targeting process:

Keep in mind when looking at this world, there are plenty of combinations that need to be considered when trying to detect instant cast spells. I try to take these into consideration with my hooks.

Implementing this system

Caveats

This code will grab every spell that is being cast as well as every action that is being used on the client side. Due to global cooldowns and other timing the overhead we incur here is very small, and I've done everything I can to make the code efficient and to ensure that the original functions are called as quickly as possible.

If you are interested in grabbing just a subset of instant cast functions, you can put them in a localized table and use that lookup to determine whether or not you change the MyMod_Spell variable.

Provide a custom tooltip

As much as I wish it wasn't necessary, we need some way to glean the spell information. This step is somewhat optional if you have your own tooltip to look this up, but I like my functions to be somewhat complete in providing accurate information. Let's create a tooltip in our XML file so we can use it later.

After the 1.10 patch, it is necessary to provide an anchor for all tooltips; otherwise they will not be filled in by the UI. See UIOBJECT_GameTooltip for additional information.

<GameTooltip name="MyMod_Tooltip" frameStrata="TOOLTIP" hidden="true"
inherits="GameTooltipTemplate" parent="UIParent">
    <Scripts>
        <OnLoad>
            this:SetOwner(UIParent, "ANCHOR_NONE");
        </OnLoad>
    </Scripts>
</GameTooltip>

Variables

We could use some variables here to store information as we progress, so we'll define the following in the LUA body:

-- This will contain the spell that is waiting to be targeted
MyMod_Spell = nil
-- This will contain the spell that has been cast and targeted and is awaiting a SPELLCAST_STOP
MyMod_EndCast = nil
-- This contains the target of the current spell being casting
MyMod_Target = nil

Hooking the casting functions

CastSpell

MyMod_oldCastSpell = CastSpell;
function MyMod_newCastSpell(spellId, spellbookTabNum)
   -- Call the original function so there's no delay while we process
   MyMod_oldCastSpell(spellId, spellbookTabNum)
       
   -- Load the tooltip with the spell information
   MyMod_Tooltip:SetSpell(spellId, spellbookTabNum)
   
   local spellName = MyMod_TooltipTextLeft1:GetText()
       
   if SpellIsTargeting() then 
       -- Spell is waiting for a target
       MyMod_Spell = spellName
   else
       -- Spell is being cast on the current target.  
       -- If ClearTarget() had been called, we'd be waiting target
       MyMod_EndCast = spellName
       MyMod_Target = UnitName("target")
   end
end
CastSpell = MyMod_newCastSpell

CastSpellByName

Corrected syntax for 1.10 patch.

MyMod_oldCastSpellByName = CastSpellByName;
function MyMod_newCastSpellByName(spellName, onSelf)
   -- Call the original function
   MyMod_oldCastSpellByName(spellName, onSelf)
   
   -- This will give us the full spellname, including Rank.  
   -- This can be filtered out quite easily
   local spellName = spellName
   
   if SpellIsTargeting() then
       -- Spell is waiting for a target
       MyMod_Spell = spellName
   else
       -- Spell is being cast on the current target
       MyMod_EndCast = spellName
       if onSelf then
           MyMod_Target = UnitName("player")
       else
           MyMod_Target = UnitName("target")
       end
   end
end
CastSpellByName = MyMod_newCastSpellByName

UseAction

MyMod_oldUseAction = UseAction
function MyMod_newUseAction(a1, a2, a3)
   -- Call the original function
   MyMod_oldUseAction(a1, a2, a3)
   
   -- Test to see if this is a macro
   if GetActionText(a1) then return end
   
   MyMod_Tooltip:SetAction(a1)
   local spellName = MyMod_TooltipTextLeft1:GetText()
   
   if SpellIsTargeting() then
       -- Spell is waiting for a target
       MyMod_Spell = spellName
   else
       -- Spell is being cast on the current target
       MyMod_EndCast = spellName
       MyMod_Target = UnitName("target")
   end
end
UseAction = MyMod_newUseAction

Hooking the targeting functions

SpellTargetUnit

This one is nice, and provides us with a good framwork because of its limitations. Since SpellTargetUnit can ONLY be called after a spellcast has been issued, we can always be sure of who we're casting the spell on. Even if ClearTarget is used, it just triggers the need to target after casting.

MyMod_oldSpellTargetUnit = SpellTargetUnit
function MyMod_newSpellTargetUnit(unit)
   -- Call the original function
   MyMod_oldSpellTargetUnit(unit)
   
   -- Look to see if we're currently waiting for a target internally
   -- If we are, then well glean the target info here.
   
   if MyMod_Spell then
       -- Currently casting.. lets grab the target
       MyMod_EndCast = MyMod_Spell
       MyMod_Target = UnitName(unit)
       
       -- Clear MyMod_Spell so we can wait for SPELLCAST_STOP
       MyMod_Spell = nil
   end
end
SpellTargetUnit = MyMod_newSpellTargetUnit

TargetUnit

This is the same code from SpellTargetUnit, with the names changed to protect the innocent.

MyMod_oldTargetUnit = TargetUnit
function MyMod_newTargetUnit(unit)
   -- Call the original function
   MyMod_oldTargetUnit(unit)
   
   -- Look to see if we're currently waiting for a target internally
   -- If we are, then well glean the target info here.
   
   if MyMod_Spell then
       -- Currently casting.. lets grab the target
       MyMod_EndCast = MyMod_Spell
       MyMod_Target = UnitName(unit)
       
       -- Clear MyMod_Spell so we can wait for SPELLCAST_STOP
       MyMod_Spell = nil
   end
end
TargetUnit = MyMod_newTargetUnit

SpellStopTargeting

All we need to do here is clear MyMod_Spell to ensure we keep our state clean.

MyMod_oldSpellStopTargeting = SpellStopTargeting
function MyMod_newSpellStopTargeting()
   MyMod_oldSpellStopTargeting()
   
   if MyMod_Spell then
       MyMod_Spell = nil
       MyMod_EndCast = nil
       MyMod_Target = nil
   end
end
SpellStopTargeting = MyMod_newSpellStopTargeting

CameraOrSelectOrMoveStart

After the 1.10 patch, CameraOrSelectOrMoveStart may no longer be hooked by custom UI mods. The below code is preserved for historical purposes, but will no longer work.


This one is a little trickier.. we need to grab the information before we call the original function, so lets be quick about it:

MyMod_oldCameraOrSelectOrMoveStart = CameraOrSelectOrMoveStart
function MyMod_newCameraOrSelectOrMoveStart()
   -- If we're waiting to target
   
   local targetName = nil
   
   if MyMod_Spell and UnitName("mouseover") then
       targetName = UnitName("mouseover")
   end
   
   MyMod_oldCameraOrSelectOrMoveStart();
   
   if MyMod_Spell then
       MyMod_EndCast = MyMod_Spell
       MyMod_Target = targetName
       MyMod_Spell = nil
   end
end
CameraOrSelectOrMoveStart = MyMod_newCameraOrSelectOrMoveStart

Hooking the mouse click

In the 1.10 patch, CameraOrSelectOrMoveStart can no longer be hooked by the user interface. Attempting to call the hooked function will cause your addon to be reported as blocked by the game. The solution to this problem is to hook the mouse click itself. Since the user clicks on the screen to target a spell, all we have to do is add a call to our custom script when this occurs.

OnMouseDown

 function myMouseDown()
   if arg1 == "LeftButton" then
       local targetName;
       
       if MyMod_Spell and UnitExists("mouseover") then
           targetName = UnitName("mouseover")
       end

       if MyMod_Spell and targetName then
           MyMod_EndCast = MyMod_Spell
           MyMod_Target = targetName
           MyMod_Spell = nil
       end
   end
end

local oldFunc = WorldFrame:GetScript("OnMouseDown");
if ( oldFunc ) then --hook the old function if one already exists
   WorldFrame:SetScript("OnMouseDown", function() oldFunc(); myMouseDown(); end );
else
   WorldFrame:SetScript("OnMouseDown", myMouseDown);
end

Watching for SPELLCAST events

In your OnLoad handler, register the following events:

this:RegisterEvent("SPELLCAST_FAILED");
this:RegisterEvent("SPELLCAST_STOP");
this:RegisterEvent("SPELLCAST_INTERRUPTED");

In your OnEvent handler, add the following:

if event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then

   if MyMod_EndCast then 
       MyMod_EndCast = nil
       MyMod_Target = nil
       MyMod_Spell = nil
   end

   -- This fires when a spell is completed casting, or you double escape a targeting spell
   elseif event == "SPELLCAST_STOP" then	
	
   if MyMod_EndCast then
       -- This is where you implement your handling, and pull the SpellName and Target information
       MyMod_EndCast = nil
       MyMod_Target = nil
       MyMod_Spell = nil
   end