Described method will help you to filter out spells which can’t be used due to the lack of mana (or holy power), wrong distance to target or other reasons. This will help to implement good combat rotations for your bot.
The main trick is in getting information from this function API IsUsableAction - WoWWiki - Your guide to the World of Warcraft
It seems that all prerequisites are calculated somewhere else and this function just checks two flags stored in memory. So we can easily do the same in our application.
This piece of code will show the example usage:
Code:
of_enabled_action_btns = $9EDC58;
of_no_mana_for_action_btns = $9EDA18;
//i - number of Action button.
is_available = mr.readUInt( mem_baseaddr + of_enabled_action_btns + i*4);
not_enough_mana = mr.readUInt( mem_baseaddr + of_no_mana_for_action_btns + i*4);
So here is the brief example code for building combat loop. First of all we need to prepare our action bar – rearrange spells in priority order. The spell with highest priority goes first. Here is example for retribution paladin:
Now the example code in Delphi (ya, really which will give you the idea of how to put it all together.
Credits for cooldown tracking goes to someone from mmowned.
//4.0.1
Code:
SKILLS_BAR_FROM = 60;
SKILLS_BAR_TO = 72;
of_action_toolbar_start = $9EE0D4;
of_spell_cooldowns = $980968;
of_enabled_action_btns = $9EDC58;
of_no_mana_for_action_btns = $9EDA18;
{*******************************************}
{**** SKILLS *******************************}
procedure s_Combat.use_spell();
var
i : word;
is_available, not_enough_mana : dword;
x : byte;
spid : dword;
s : AnsiString;
begin
update_cooldowns();
if (on_gcd) then begin
if (gcd_left > 200) then sleep( gcd_left - 200 );
exit;
end;
x := 0;
for i := SKILLS_BAR_FROM to SKILLS_BAR_TO do begin
inc(x);
is_available := mr.readUInt( mem_baseaddr + of_enabled_action_btns + i*4);
not_enough_mana := mr.readUInt( mem_baseaddr + of_no_mana_for_action_btns + i*4);
if (is_available = 1) and ( not_enough_mana <> 1 ) then begin
//get spell ID from btn
spid := mr.readUInt(mem_baseaddr + of_action_toolbar_start + (i+1)*4);
if not( on_cooldown(spid) ) then begin
log(' btn '+inttostr(x)+' available ('+inttostr(spid)+')');
s := inttostr(x);
SendKey( wow_hwnd, s[1] );
break;
end;
end;
end;
end;
procedure s_Combat.update_cooldowns();
var
perfCount, frequency, currentTime: Int64;
startTime, cooldown1, cooldown2, cooldownLength, globalLength, left: integer;
curItem,spellId : dword;
begin
n_cooldowns := 0;
on_gcd := false;
QueryPerformanceFrequency(frequency);
QueryPerformanceCounter(perfCount);
currentTime := round( (perfCount * 1000) / frequency );
curItem := mr.readUInt(mem_baseaddr + of_spell_cooldowns + $8);
while ((curItem <> 0) and ((curItem and 1) = 0)) do begin
spellId := mr.readUInt(curItem + $8);
startTime := mr.readInt(curItem + $10);
cooldown1 := mr.readInt(curItem + $14);
cooldown2 := mr.readInt(curItem + $20);
globalLength := mr.readInt( curItem + $2C );
if ( (startTime + globalLength) > currentTime) then begin
on_gcd := true;
gcd_left := (startTime + globalLength) - currentTime;
exit;
end;
cooldownLength := max(cooldown1, cooldown2);
left := (startTime + cooldownLength) - currentTime ;
if left > 0 then begin
//log( Format(' +sp CD: %x %d', [spellId, left]) );
inc(n_cooldowns);
spells_on_cooldown[ n_cooldowns ] := spellId;
end;
curItem := mr.readUInt(curItem + 4);
end;
end;
function s_Combat.on_cooldown(spid:dword) : boolean;
var
i : word;
begin
result := false;
for i := 1 to n_cooldowns do begin
if spells_on_cooldown[ i ] = spid then begin
result := true;
exit;
end;
end;
end;