a796c32 - Add CD tracker
authorAleksi Blinnikka <aleksi.blinnikka@gmail.com>
Sun, 18 Mar 2018 14:16:20 +0000
committerAleksi Blinnikka <aleksi.blinnikka@gmail.com>
Sun, 18 Mar 2018 14:16:20 +0000
Some problems with inspecting talents etc.

OmaCD/Bres.lua [new file with mode: 0644]
OmaCD/Cooldowns.lua [new file with mode: 0644]
OmaCD/OmaCD.toc [new file with mode: 0644]

diff --git a/OmaCD/Bres.lua b/OmaCD/Bres.lua
new file mode 100644 (file)
index 0000000..0bdde11
--- /dev/null
@@ -0,0 +1,49 @@
+-- Bres.lua
+local _;
+local min, ceil = math.min, math.ceil;
+local GetSpellCharges = GetSpellCharges;
+local GetTime = GetTime;
+local CTimerAfter = C_Timer.After;
+
+local frame = CreateFrame("Frame", "OmaBres", UIParent);
+
+local function tick()
+    local charges, maxCharges, start, duration = GetSpellCharges(20484); -- Rebirth
+    if charges then
+        frame:Show();
+        local remain = duration - (GetTime() - start);
+        frame.charge:SetText(charges);
+        frame.cd:SetCooldown(start, duration);
+        CTimerAfter(min(ceil(remain), 20), tick);
+    else
+        frame:Hide();
+        CTimerAfter(5, tick);
+    end
+end
+
+local function bres()
+    frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 580, 0);
+    frame:SetWidth(48);
+    frame:SetHeight(48);
+    frame.base = frame:CreateTexture(nil, "BACKGROUND");
+    frame.base:SetAllPoints();
+    frame.base:SetColorTexture(0, 0, 0, 0.5);
+    frame.icon = frame:CreateTexture(nil, "BORDER");
+    frame.icon:SetPoint("TOPLEFT", frame.base, "TOPLEFT", 1, -1);
+    frame.icon:SetPoint("BOTTOMRIGHT", frame.base, "BOTTOMRIGHT", -1, 1);
+    frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
+    frame.icon:SetTexture(136080); -- Rebirth
+    frame.charge = frame:CreateFontString(nil, "OVERLAY", "NumberFontNormalLarge");
+    frame.charge:SetPoint("BOTTOMRIGHT");
+    frame.cd = CreateFrame("Cooldown", "OmaBresCD", frame, "CooldownFrameTemplate");
+    frame.cd:SetAllPoints();
+    frame:UnregisterAllEvents();
+    tick();
+end
+
+frame:RegisterEvent("PLAYER_LOGIN");
+frame:SetScript("OnEvent", function(self, event)
+    if event == "PLAYER_LOGIN" then
+        return bres();
+    end
+end);
diff --git a/OmaCD/Cooldowns.lua b/OmaCD/Cooldowns.lua
new file mode 100644 (file)
index 0000000..916a5a0
--- /dev/null
@@ -0,0 +1,495 @@
+-- Cooldowns.lua
+local _;
+local pairs, ipairs, unpack = pairs, ipairs, unpack;
+local tremove, format, ssub = table.remove, string.format, string.sub;
+local floor, strsplit = math.floor, strsplit;
+local GetTime, UnitGUID, UnitClass = GetTime, UnitGUID, UnitClass;
+local UnitName, GetSpellTexture = UnitName, GetSpellTexture;
+local IsInGroup, IsInRaid = IsInGroup, IsInRaid;
+local CreateFrame, CTimerAfter = CreateFrame, C_Timer.After;
+local UnitIsUnit, IsEncounterInProgress = UnitIsUnit, IsEncounterInProgress;
+local UnitExists, UnitGroupRolesAssigned = UnitExists, UnitGroupRolesAssigned;
+local UnitIsDeadOrGhost, UnitIsConnected = UnitIsDeadOrGhost, UnitIsConnected;
+local GetTalentInfo, GetInspectSpecialization = GetTalentInfo, GetInspectSpecialization;
+local GetSpecializationInfo, GetSpecialization = GetSpecializationInfo, GetSpecialization;
+local GetInventoryItemLink, GetInventoryItemID = GetInventoryItemLink, GetInventoryItemID;
+local CanInspect, NotifyInspect, ClearInspectPlayer = CanInspect, NotifyInspect, ClearInspectPlayer;
+local INVSLOT_MAINHAND, INVSLOT_WRIST = INVSLOT_MAINHAND, INVSLOT_WRIST;
+
+local upColor = {0, 0.7, 0.2, 0.7};
+local downColor = {1, 0, 0, 0.5};
+
+local cdframe = CreateFrame("Frame", "OmaCD", UIParent);
+local frames = {};
+local unused = {};
+local shown = {}; -- simple array of frames in shown order
+
+local specs = {
+    ["PALADIN"] = 65,
+    ["PRIEST"] = false, -- priest has 2 healing specs
+    ["SHAMAN"] = 264,
+    ["MONK"] = 270,
+    ["DRUID"] = 105,
+};
+local trackedcds = {
+    [65] = { -- Holy Paladin
+        [31821] = 180, -- Aura Mastery
+    },
+    [105] = { -- Resto Druid
+        [740] = 180, -- Tranquility
+    },
+    [256] = { -- Discipline Priest
+        [62618] = 180, -- Power Word: Barrier
+    },
+    [257] = { -- Holy Priest
+        [64843] = 180, -- Divine Hymn
+    },
+    [264] = { -- Resto Shaman
+        [108280] = 180, -- Healing Tide Totem
+        [98008] = 180, -- Spirit Link Totem
+    },
+    [270] = { -- Mistweaver Monk
+        [115310] = (180-40), -- Revival, already lowered by Artifact when lvl 75
+    },
+};
+local monkRelics = { -- Tendrils of Revival
+    ["151012"] = -10,
+    ["151010"] = -10,
+    ["147112"] = -10,
+    ["152291"] = -10,
+};
+
+local cds = {}; -- CD durations currently available modified by passive cdfixes
+-- CD fixes active e.g. cdfixes[guid] = -60, no need for spellid as
+-- only classes with one CD have fixes
+local cdfixes = {};
+local runningcds = {}; -- CDs currently active
+
+local idToGuid = {};
+local guidToId = {};
+local guidToSpecid = {};
+local monksWithLegendary = {};
+local inspectSent = {};
+local dead = {};
+local disconnected = {};
+
+local function CdUp(guid, spellid)
+    local frame = frames[guid][spellid];
+    if runningcds[guid] then runningcds[guid][spellid] = nil end
+    frame.base:SetVertexColor(unpack(upColor));
+    frame.text:SetText(frame.name);
+end
+
+local function CdDown(guid, spellid, start)
+    local frame = frames[guid][spellid];
+    if not runningcds[guid] then runningcds[guid] = {} end
+    runningcds[guid][spellid] = start;
+    frame.base:SetVertexColor(unpack(downColor));
+    if start then frame.text:SetText(cds[guid][spellid]) end
+end
+
+local function sendInspect(guid, id, func)
+    if not inspectSent[guid] and CanInspect(id) then
+        inspectSent[guid] = true;
+        local _, _, _, _, _, name = GetPlayerInfoByGUID(guid);
+        print(guid, name, "send inspect");
+        NotifyInspect(id);
+        CTimerAfter(20, func); -- precaution for never receiving INSPECT_READY
+    end
+end
+
+local encounter = nil;
+local function tick()
+    if not encounter and IsEncounterInProgress() then
+        encounter = true;
+    elseif encounter and not IsEncounterInProgress() then
+        encounter = nil;
+        -- for some reason can't hook to ENCOUNTER_END event so have to track with timer
+        -- TODO support Argus, encounterID 1866
+        for guid, t in pairs(runningcds) do
+            for spellid, _ in pairs(t) do
+                CdUp(guid, spellid);
+            end
+        end
+    end
+    local current = GetTime();
+    for guid, t in pairs(runningcds) do
+        for spellid, start in pairs(t) do
+            local remain = cds[guid][spellid] - (current - start);
+            if remain <= 0 then -- CD back up
+                CdUp(guid, spellid);
+            else -- still on CD
+                if remain >= 60 then
+                    frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
+                else
+                    frames[guid][spellid].text:SetText(floor(remain));
+                end
+            end
+        end
+    end
+    CTimerAfter(0.5, tick);
+end
+
+local function refreshPositions()
+    for i, frame in ipairs(shown) do
+        local prev = shown[i-1] or cdframe;
+        frame:ClearAllPoints();
+        frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
+    end
+end
+
+local function removeFrame(frame)
+    unused[#unused+1] = frame;
+    frames[frame.guid][frame.spellid] = nil;
+    cds[frame.guid][frame.spellid] = nil;
+    if runningcds[frame.guid] then runningcds[frame.guid][frame.spellid] = nil end
+    tremove(shown, frame.pos);
+    for i, frame in ipairs(shown) do
+        frame.pos = i;
+    end
+    frame:ClearAllPoints();
+    frame:Hide();
+    refreshPositions();
+end
+
+local unique = 1;
+local function getFrame()
+    local frame = tremove(unused);
+    if not frame then
+        frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
+        unique = unique + 1;
+        frame:SetWidth(75);
+        frame:SetHeight(16);
+        frame.base = frame:CreateTexture(nil, "BACKGROUND");
+        frame.base:SetAllPoints();
+        frame.base:SetColorTexture(1, 1, 1);
+        frame.icon = frame:CreateTexture(nil, "ARTWORK");
+        frame.icon:SetPoint("TOPLEFT");
+        frame.icon:SetPoint("BOTTOMLEFT");
+        frame.icon:SetWidth(16);
+        frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
+        frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
+        frame.text:SetPoint("LEFT", frame, "LEFT", 20, 0);
+        frame.text:SetJustifyH("LEFT");
+    end
+    return frame;
+end
+local function updateCD(guid, specid, spellid)
+    -- update cd value
+    local cd = trackedcds[specid][spellid];
+    if cdfixes[guid] then cd = cd + cdfixes[guid] end
+    if not cds[guid] then cds[guid] = {} end
+    cds[guid][spellid] = cd;
+    if frames[guid] and frames[guid][spellid] then return end
+
+    -- no cd frame yet, create one
+    local frame = getFrame();
+    frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
+    frame.guid = guid;
+    frame.spellid = spellid;
+    frame.base:SetVertexColor(unpack(upColor));
+    frame.icon:SetTexture(GetSpellTexture(spellid));
+    frame.text:SetText(frame.name);
+    frame:Show();
+    if not frames[guid] then frames[guid] = {} end
+    frames[guid][spellid] = frame;
+    frame.pos = #shown+1;
+    local prev = shown[#shown] or cdframe;
+    shown[#shown+1] = frame;
+    frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
+    return frame;
+end
+
+local function updateUnitCD(guid)
+    local specid = guidToSpecid[guid];
+    if specid then
+        for spellid, _ in pairs(trackedcds[specid]) do
+            updateCD(guid, specid, spellid);
+        end
+    end
+end
+
+local function updateMonk(guid)
+    local id = guidToId[guid];
+    if not id then return end -- called because another addon inspected
+    local weapon = GetInventoryItemLink(id, INVSLOT_MAINHAND);
+    if not weapon then
+        -- try again directly
+        if CanInspect(id) then
+            print(guid, "send inspect");NotifyInspect(id);
+            CTimerAfter(20, updateMonk);
+        end
+        return;
+    end
+    if inspectSent[guid] then inspectSent[guid] = nil end
+    print("Got monk", guid);
+    local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon);
+    local cdfix = 0;
+    if monkRelics[relic1] then cdfix = cdfix + monkRelics[relic1] end
+    if monkRelics[relic2] then cdfix = cdfix + monkRelics[relic2] end
+    if monkRelics[relic3] then cdfix = cdfix + monkRelics[relic3] end
+    if cdfix ~= 0 then cdfixes[guid] = cdfix end
+
+    local wrist = GetInventoryItemID(guidToId[guid], INVSLOT_WRIST);
+    if wrist == 137096 then -- Petrichor Lagniappe
+        monksWithLegendary[guid] = true;
+    else
+        monksWithLegendary[guid] = nil;
+    end
+    updateUnitCD(guid);
+end
+
+local function updateDruid(guid, player)
+    local id = guidToId[guid];
+    if id then
+        -- Inner Peace talent
+        local selected;
+        if player then
+            _, _, _, selected = GetTalentInfo(6, 2, 1);
+        else
+            _, _, _, selected = GetTalentInfo(6, 2, 1, true, id);
+        end
+        if selected then
+            cdfixes[guid] = -60;
+        else
+            cdfixes[guid] = nil;
+        end
+        updateUnitCD(guid);
+    end
+end
+
+local function updatePriest(guid)
+    local id = guidToId[guid];
+    if id then
+        local specid = GetInspectSpecialization(id);
+        if specid == 0 then
+            -- try again directly
+            if CanInspect(id) then
+                print(guid, "send inspect");NotifyInspect(id);
+                CTimerAfter(20, updatePriest);
+            end
+        end
+        if inspectSent[guid] then inspectSent[guid] = nil end
+        print("Got priest", guid);
+        guidToSpecid[guid] = specid;
+        updateUnitCD(guid);
+    end
+end
+
+local function updatePlayer()
+    local specid = GetSpecializationInfo(GetSpecialization() or 1);
+    local guid = UnitGUID("player");
+    if trackedcds[specid] then
+        guidToId[guid] = "player";
+        guidToSpecid[guid] = specid;
+        if specid == 270 then -- Monk
+            updateMonk(guid);
+        elseif specid == 105 then -- Druid
+            updateDruid(guid, true);
+        else
+            updateUnitCD(guid);
+        end
+        idToGuid["player"] = guid;
+    else
+        idToGuid["player"] = nil;
+        for _, frame in pairs(frames[guid]) do
+            removeFrame(frame);
+        end
+    end
+end
+
+local function updateUnitid(id)
+    local guid = UnitGUID(id);
+    if UnitIsUnit(id, "player") then -- player is special case
+        return updatePlayer();
+    elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then
+        local _, class = UnitClass(id);
+        if specs[class] ~= nil then
+            guidToId[guid] = id;
+            if specs[class] == false then
+                -- Priest, have to inspect to get spec, try immediately, if not available, inspect
+                local specid = GetInspectSpecialization(id);
+                if specid == 0 then
+                    sendInspect(guid, id, updatePriest);
+                elseif specid == 256 or specid == 257 then
+                    guidToSpecid[guid] = specid; -- only Holy and Discipline
+                    updateUnitCD(guid);
+                end
+            elseif specs[class] == 270 then
+                -- Monk, have to check inventory for CD modifications
+                guidToSpecid[guid] = specs[class];
+                -- updateUnitCD (without having fixes yet)
+                updateUnitCD(guid);
+                sendInspect(guid, id, updateMonk);
+            elseif specs[class] == 105 then
+                -- Druid, have to inspect to get talents
+                guidToSpecid[guid] = specs[class];
+                -- updateUnitCD (without having fixes yet)
+                updateUnitCD(guid);
+                sendInspect(guid, id, updateDruid);
+            else
+                guidToSpecid[guid] = specs[class];
+                updateUnitCD(guid);
+            end
+            idToGuid[id] = guid;
+        end
+    else
+        idToGuid[id] = nil;
+        if frames[guid] then
+            for _, frame in pairs(frames[guid]) do
+                removeFrame(frame);
+            end
+        end
+    end
+end
+
+local updateQueued = nil;
+local function updateUnitids()
+    local size = 0;
+    local prefix;
+    local newIdToGuid = {};
+    local prevIdToGuid = {};
+    guidToId = {};
+    updateQueued = nil;
+
+    for id, guid in pairs(idToGuid) do
+        prevIdToGuid[id] = guid;
+    end
+
+    if IsInGroup() then
+        if IsInRaid() then
+            size = 40;
+            prefix = "raid";
+        else
+            size = 4;
+            prefix = "party";
+        end
+    end
+
+    updatePlayer();
+    if prevIdToGuid["player"] then prevIdToGuid["player"] = nil end
+    for i = 1,size do
+        local id = format("%s%i", prefix, i);
+        if not UnitIsUnit(id, "player") then
+            updateUnitid(id);
+        end
+        if prevIdToGuid[id] then prevIdToGuid[id] = nil end
+    end
+
+    -- clean up players leaving
+    for id, guid in pairs(prevIdToGuid) do
+        if frames[guid] then
+            for _, frame in pairs(frames[guid]) do
+                removeFrame(frame);
+            end
+        end
+    end
+end
+
+local events = {
+    ["UNIT_HEALTH"] = function(id)
+        local guid = idToGuid[id];
+        if guid and cds[guid] and UnitIsDeadOrGhost(id) then
+            dead[guid] = true;
+            for spellid, _ in pairs(cds[guid]) do
+                CdDown(guid, spellid, nil);
+            end
+        elseif guid and dead[guid] and cds[guid] then
+            dead[guid] = nil;
+            for spellid, _ in pairs(cds[guid]) do
+                CdUp(guid, spellid);
+            end
+        end
+    end,
+    ["UNIT_CONNECTION"] = function(id)
+        local guid = idToGuid[id];
+        if guid and cds[guid] and not UnitIsConnected(id) then
+            disconnected[guid] = true;
+            for spellid, _ in pairs(cds[guid]) do
+                CdDown(guid, spellid, nil);
+            end
+        elseif guid and disconnected[guid] and cds[guid] then
+            disconnected[guid] = nil;
+            for spellid, _ in pairs(cds[guid]) do
+                CdUp(guid, spellid);
+            end
+        end
+    end,
+    ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, _, _, spellid)
+        local guid = idToGuid[id];
+        if guid and frames[guid] then
+            local frame = frames[guid][spellid];
+            if frame then
+                CdDown(guid, spellid, GetTime());
+            elseif monksWithLegendary[guid] and spellid == 115151 then
+                -- Renewing Mist with legendary affects Revival
+                if runningcds[guid] and runningcds[guid][115310] then
+                    runningcds[guid][115310] = runningcds[guid][115310] - 2;
+                end
+            end
+        end
+    end,
+    ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
+        updateUnitid(id);
+    end,
+    ["UNIT_INVENTORY_CHANGED"] = function(id)
+        local guid = idToGuid[id];
+        if guid and guidToSpecid[guid] == 270 then
+            print("OmaCD Inventory: Monk inventory update");
+            updateMonk(guid);
+        end
+    end,
+    ["GROUP_ROSTER_UPDATE"] = function()
+        if not updateQueued then
+            updateQueued = true
+            CTimerAfter(2, updateUnitids);
+        end
+    end,
+    ["INSPECT_READY"] = function(guid)
+        local specid = guidToSpecid[guid];
+        if inspectSent[guid] then inspectSent[guid] = nil end
+        if specid == 105 then -- Druid
+            print("OmaCD Inspect: druid,", guid);
+            updateDruid(guid);
+            ClearInspectPlayer();
+        elseif specid == 270 then -- Monk
+            print("OmaCD Inspect: monk,", guid);
+            -- don't clear inspect player, have to track inventory changes
+        elseif specid == false or specid == 256 or specid == 257 then -- Priest
+            print("OmaCD Inspect: priest,", guid);
+            updatePriest(guid);
+            ClearInspectPlayer();
+        end
+    end,
+};
+events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
+
+local function cdtracker()
+    cdframe:SetFrameStrata("LOW");
+    cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 200, -200);
+    cdframe:SetWidth(1);
+    cdframe:SetHeight(1);
+
+    cdframe:UnregisterAllEvents();
+    cdframe:SetScript("OnEvent", function(self, event, ...)
+        events[event](...);
+    end);
+    cdframe:RegisterEvent("UNIT_HEALTH");
+    cdframe:RegisterEvent("UNIT_CONNECTION");
+    cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
+    cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
+    cdframe:RegisterEvent("UNIT_INVENTORY_CHANGED");
+    cdframe:RegisterEvent("GROUP_ROSTER_UPDATE");
+    cdframe:RegisterEvent("INSPECT_READY");
+    cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED");
+    -- initial update
+    events["GROUP_ROSTER_UPDATE"]();
+    CTimerAfter(0.5, tick);
+end
+
+cdframe:RegisterEvent("PLAYER_LOGIN");
+cdframe:SetScript("OnEvent", function(self, event)
+    if event == "PLAYER_LOGIN" then
+        return cdtracker();
+    end
+end);
diff --git a/OmaCD/OmaCD.toc b/OmaCD/OmaCD.toc
new file mode 100644 (file)
index 0000000..6e1a15e
--- /dev/null
@@ -0,0 +1,8 @@
+## Interface: 70300
+## Title: Oma Cooldown Tracker
+## Version: 1.0
+## Author: schyrio
+## Notes: My CD tracker
+
+Bres.lua
+Cooldowns.lua