There is some good information about spell cooldowns on these forums but I was never able to find decent documentation about how the global cooldown works or how shared cooldowns work so I researched this topic myself. Here is the code I used for checking what spells are on cooldown:
Code:
public class SpellHistory {
private static final int SPELL_COOLDOWN = 0xACD714; // 4.3.4.15595
private static final int TIMESTAMP_OFFSET = 0x9C0C7C; // 4.3.4.15595
private static SpellHistory instance;
private static class Spell {
private int spellId; // ID of the spell
private int itemId; // 0 for most spells, for trinket spells or engineer enchants, contains the item ID of the item that casted it
private int startTime; // time the cooldown started, sometimes zero for profession cooldowns that end at midnight (in which case shared CD is used)
private int spellCD; // length of the spell cooldown, can be zero or negative if the spell has or a shared/global CD only
private int sharedId; // ID of the shared cooldown, a unique number for most spells, but many spells do actually have overlapping shared cooldowns
private int otherTime; // the start time of the shared cooldown, always nonzero
private int sharedCD; // length of the shared cooldown
private int globalCD; // length of the global cooldown, 0 for spells with no globel cooldown
}
private final ArrayList<Spell> spells;
private final MemoryReader r;
private int now;
private SpellHistory(MemoryReader r) {
this.r = r;
spells = new ArrayList<Spell>();
}
public static void createSpellHistory(MemoryReader r) {
instance = new SpellHistory(r);
}
public static SpellHistory getSpellHistory() {
return instance;
}
public void update() {
spells.clear();
int current = r.readInt(r.getBaseAddress() + SPELL_COOLDOWN + 0x08);
int[] fields = new int[12];
while (0 != current && 0 == (current & 3)) {
r.readArray(current, fields);
Spell spell = new Spell();
int prev = fields[0];
int next = fields[1];
spell.spellId = fields[2];
spell.itemId = fields[3];
spell.startTime = fields[4];
spell.spellCD = fields[5];
spell.sharedId = fields[6];
spell.otherTime = fields[7];
spell.sharedCD = fields[8];
int unk1 = fields[9]; // always zero
int unk2 = fields[10]; // 0x85 when GCD, otherwise 0
spell.globalCD = fields[11];
spells.add(spell);
current = next;
}
now = r.readInt(r.getBaseAddress() + TIMESTAMP_OFFSET);
}
/*
* This method returns the number of milliseconds until the spell is off
* cooldown, measured from time time SpellHistory.update() was called.
* Returns 0 if the spell is off cooldown.
* You must specify the spell ID, and set gcd to true if the spell waits
* on the global cooldown. If the spell shares a cooldown with any other
* spell, you need to specify the shared cooldown ID, otherwise, it is
* okay to set it to zero.
*/
public int getSpellCooldown(int spellId, int sharedId, boolean gcd) {
int cooldown = 0;
for (Spell spell : spells) {
int spellTimeLeft = spell.spellCD + spell.startTime - now;
int sharedTimeLeft = spell.sharedCD + spell.otherTime - now;
int gcdTimeLeft = spell.startTime + spell.globalCD - now;
if (0 != spellId && spellId == spell.spellId) {
if (0 != spell.startTime && spellTimeLeft > cooldown) {
cooldown = spellTimeLeft;
}
/*
* This clause not necessary if you always supply the correct shared ID
* when calling the method, but putting it here gives correct behaviour
* even when you don't specify a shared ID, as long as the spell isn't
* on CD due to a shared CD with another spell.
*/
if (0 == sharedId && sharedTimeLeft > cooldown) {
cooldown = sharedTimeLeft;
}
}
if (0 != sharedId && sharedId == spell.sharedId && sharedTimeLeft > cooldown) {
cooldown = sharedTimeLeft;
}
if (gcd && 0 < spell.globalCD && gcdTimeLeft > cooldown) {
cooldown = gcdTimeLeft;
}
}
return cooldown;
}
public boolean isSpellOnCooldown(int spellId, int sharedId, boolean gcd) {
return 0 < getSpellCooldown(spellId, sharedId, gcd);
}
}