vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
SA_LIST_BOXES = {};

-- TODO: incombat interval ja outofcombat interval! performance++ =)

SA_REFRESHRATE = 1.5;

SA_BASERATE = 0.6;
SA_INCREASE = 0.1; -- how much to decrease refresh rate per mob
SA_LASTREFRESH = SA_BASERATE;

SA_SEMIALERT = "|cffffa303";
SA_ALERTCOL = "|cffff0000";
SA_TANKCOL = "|cff00ff00";

-- title modes

MODE_OOC = 1;
MODE_NORMAL = 2;
MODE_FILTERED = 3;
MODE_PAUSED = 4;
MODE_NEAREST = 5;
MODE_OVERLOADED = 10;

-- contains all data from last update

SA_PREV = {
        ["aggro_count"] = 0,
        ["mob_count"] = 0,
        ["wb_count"] = 0,
        ["title_mode"] = -1,
        ["dirty"] = false
};

-- class icon texture coordinates

SA_TEXTCOORDS={};
SA_TEXTCOORDS["PRIEST"]  = { left=0.50, right=0.75, top=0.25, bottom=0.50 };
SA_TEXTCOORDS["MAGE"]    = { left=0.25, right=0.50, top=0.00, bottom=0.25 };
SA_TEXTCOORDS["WARLOCK"] = { left=0.75, right=1.00, top=0.25, bottom=0.50 };
SA_TEXTCOORDS["DRUID"]   = { left=0.75, right=1.00, top=0.00, bottom=0.25 };
SA_TEXTCOORDS["HUNTER"]  = { left=0.00, right=0.25, top=0.25, bottom=0.50 };
SA_TEXTCOORDS["ROGUE"]   = { left=0.50, right=0.75, top=0.00, bottom=0.25 };
SA_TEXTCOORDS["WARRIOR"] = { left=0.00, right=0.25, top=0.00, bottom=0.25 };
SA_TEXTCOORDS["SHAMAN"]  = { left=0.25, right=0.50, top=0.25, bottom=0.50 };
SA_TEXTCOORDS["PALADIN"] = { left=0.00, right=0.25, top=0.50, bottom=0.75 };

-- predefined icon locations

SA_ICONPOS={n=8};
SA_ICONPOS[1] = { name="Disabled", point="TOPLEFT", relativePoint="", x=-26, y=-28, width=25, height=25 };
SA_ICONPOS[2] = { name="Left bottom", point="TOPLEFT", relativePoint="", x=-26, y=-28, width=25, height=25 };
SA_ICONPOS[3] = { name="Left top", point="TOPLEFT", relativePoint="", x=-26, y=-3, width=25, height=25};
SA_ICONPOS[4] = { name="Inside box", point="BOTTOMRIGHT", relativePoint="", x=0, y=0, width=25, height=25};
SA_ICONPOS[5] = { name="Right bottom", point="TOPRIGHT", relativePoint="", x=26, y=-28, width=25, height=25 };
SA_ICONPOS[6] = { name="Right top", point="TOPRIGHT", relativePoint="", x=26, y=-3, width=25, height=25};
SA_ICONPOS[7] = { name="Left big", point="TOPLEFT", relativePoint="", x=-51, y=-3, width=50, height=50};
SA_ICONPOS[8] = { name="Right big", point="TOPRIGHT", relativePoint="", x=51, y=-3, width=50, height=50};


------------------------------------------------------------------------------

function SA_InitBar(bar, text)
        bar:SetMinMaxValues(0, 100);
        bar:SetValue(100);
        text:SetHeight(12);
        text:SetPoint("CENTER", bar:GetName(), "CENTER", 0, 0);
        SetTextStatusBarText(bar, text);
        ShowTextStatusBarText(bar);
end

function SA_List_OnLoad()
        -- initialize boxes
        for i=1, 10 do
                local box = {};
                box["frame"] = getglobal("Target"..i);
                box["mobText"] = getglobal("Target"..i.."MobText");
                box["targetText"] = getglobal("Target"..i.."TargetText");
                box["targetOf"] = getglobal("Target"..i.."TargetOf");
                box["mobBar"] = getglobal("Target"..i.."MobBar");
                box["targetBar"] = getglobal("Target"..i.."TargetBar");
                box["classIcon"] = getglobal("Target"..i.."ClassIcon");
                box["targetIcon"] = getglobal("Target"..i.."TargetIcon");
                box["huntersMarkIcon"] = getglobal("Target"..i.."HuntersMarkIcon");
                SA_LIST_BOXES[i] = box;
        end
end

function SA_List_OnShow()
        -- TODO: this should be done only if options altered from xml defaults
        SA_List_UpdateAppearance();
        
        if (SA_OPTIONS.ListScale ~= 1.0) then
                SAListFrameScaler:SetScale(SA_OPTIONS.ListScale);
        end
        
        -- set title button to correct mode
        SA_List_SetTitleButton(MODE_OOC);
end

------------------------------------------------------------------------------
-- initialize list dropdown menu
------------------------------------------------------------------------------

function SA_List_FrameDropDown_OnLoad()
        UIDropDownMenu_Initialize(this, SA_List_FrameDropDown_Initialize, "MENU");
end

------------------------------------------------------------------------------
-- note: this is called always when dropdown is shown!
------------------------------------------------------------------------------

function SA_List_FrameDropDown_Initialize()
        if (not SA_OPTIONS) then 
                -- options not available yet (onload event) -> abort
                return;
        end

        item = {};
        item.text = "Filter targets";
        if (SA_OPTIONS.Filter) then
                item.checked = 1;
        end
        item.func = SA_List_Menu_Filter;
        UIDropDownMenu_AddButton(item);

        item = {};
        item.text = "Assist with pet";
        if (SA_OPTIONS.AutoPetAttack) then
                item.checked = 1;
        end
        item.keepShownOnClick = 1;
        item.func = SA_List_Menu_AssistWithPet;
        UIDropDownMenu_AddButton(item);
        
        item = {};
        item.text = "Assist only players nearby (28 yards)";
        if (SA_OPTIONS.AssistOnlyNearest) then
                item.checked = 1;
        end
        item.func = SA_List_Menu_AssistOnlyNearest;
        UIDropDownMenu_AddButton(item);

        item = {};
        item.text = "Display out of combat targets";
        item.checked = SA_OPTIONS.OutOfCombat;
        item.func = SA_List_Menu_ShowOOC;
        UIDropDownMenu_AddButton(item);

        item = {};
        item.text = "Lock list position";
        item.func = SA_List_Menu_Lock;
        item.checked = SA_OPTIONS.LockList;
        UIDropDownMenu_AddButton(item);
        
        item = {};
        item.text = "SmartAssist options";
        item.notCheckable = 1;
        item.func = SA_ShowOptions;
        UIDropDownMenu_AddButton(item);
end

function SA_List_Menu_Lock()
        SA_ToggleOption("LockList");
end

function SA_List_Menu_AssistWithPet()
        SA_ToggleOption("AutoPetAttack");
end

function SA_List_Menu_AssistOnlyNearest()
        SA_ToggleOption("AssistOnlyNearest");
        if (SA_OPTIONS.AssistOnlyNearest) then
                printInfo("Assisting only players nearby");
        end
end

function SA_List_Menu_ShowOOC()
        SA_ToggleOption("OutOfCombat");
end

function SA_List_Menu_Filter()
        if (SA_OPTIONS.Filter == nil) then
                SAFilterFrame:Show();
        else
                SA_OPTIONS.Filter = nil;
                printInfo("Filtering disabled");
                SA_List_SetTitleButton(MODE_OOC);
        end;
end

function SA_List_FilterButtonOK_OnClick()
        local text = SAFilterEditBox:GetText();
        if (string.len(text) > 0) then
                SA_OPTIONS.Filter = string.lower(text);
                printInfo("SmartAssist will now ignore targets which name doesn't contain text '"..text.."' until you disable filtering. Note that filtering applies only assisting, it doesn't include fallback to nearest.");
                SA_List_SetTitleButton(MODE_FILTERED);
        else
                SA_OPTIONS.Filter = nil;
                printInfo("Filtering disabled");
                SA_List_SetTitleButton(MODE_OOC);
        end
        SAFilterFrame:Hide();
end

------------------------------------------------------------------------------
-- Title button clicked (show menu) or drag
-- kudos goes for DamageMeters
------------------------------------------------------------------------------

function SA_List_TitleButton_OnClick()
        local button = arg1;
        if ( button  == "LeftButton" and not SA_OPTIONS.LockList) then
                -- drag frame
                if ( this:GetButtonState() == "PUSHED" ) then
                        SAListFrame:StopMovingOrSizing();
                else
                        SAListFrame:StartMoving();
                end
        elseif ( button == "RightButton" ) then
                -- show menu
                ToggleDropDownMenu(1, nil, SAListFrameDropDown, "SAListFrameDropDown", -33, 25);
        end
end

------------------------------------------------------------------------------
-- change outlook for the list depending options
------------------------------------------------------------------------------

function SA_List_HasSideIcons()
        if (SA_OPTIONS.ClassIconMode==2 or SA_OPTIONS.ClassIconMode==3 or
                SA_OPTIONS.TargetIconMode==2 or SA_OPTIONS.TargetIconMode==3 or
                SA_OPTIONS.HuntersMarkIconMode==2 or SA_OPTIONS.HuntersMarkIconMode==3) 
        then
                return true;
        end
end

function SA_List_UpdateAppearance()
        local width = SA_OPTIONS.ListWidth;
        
        local x = 0;
        local y = 0;
        if (SA_OPTIONS.ListHorizontal) then
                -- horizontal
                local gap = 10;
                if (SA_List_HasSideIcons()) then
                        gap = gap - 30;
                end
                if (SA_OPTIONS.ListSpacing > 0) then
                        x = width - gap + SA_OPTIONS.ListSpacing;
                else
                        x = - (width - gap - SA_OPTIONS.ListSpacing);
                end
        else
                -- vertical
                local height = ceil(Target1:GetHeight()) - 8;
                if (SA_OPTIONS.ListSpacing > 0) then
                        y = height + SA_OPTIONS.ListSpacing;
                else
                        y = - (height-SA_OPTIONS.ListSpacing);
                end
        end
        
        SAListFrame:SetWidth(width);
        SAListTitleButton:SetWidth(width);
        
        local anchor="TOPLEFT";
        for i,box in SA_LIST_BOXES do
                if (i==1) then
                        -- first box is ancored to title
                        if (y<=0) then
                                box.frame:SetPoint(anchor, "SAListFrame", anchor, 0, -17);
                        else
                                box.frame:SetPoint(anchor, "SAListFrame", anchor, 0, 54);
                        end
                elseif (i==6 and SA_OPTIONS.ListTwoRow) then
                        -- two rows
                        if (SA_OPTIONS.ListHorizontal) then
                                local gap = SA_OPTIONS.ListSpacing;
                                if (gap>0) then
                                        gap = -gap;
                                end
                                gap = gap + 10;
                                box.frame:SetPoint(anchor, "Target1", "BOTTOMLEFT", 0, gap);
                        else
                                local gap = SA_OPTIONS.ListSpacing;
                                if (gap<0) then
                                        gap = -gap;
                                end
                                gap = gap - 8;
                                -- if icons visible next to box
                                if (SA_List_HasSideIcons()) then
                                        gap = gap + 30;
                                end
                                box.frame:ClearAllPoints();
                                box.frame:SetPoint(anchor, "Target1", "TOPRIGHT", gap, 0);
                        end
                else
                        -- rest are anchored to previous box
                        box.frame:ClearAllPoints();
                        box.frame:SetPoint(anchor, "Target"..i-1, anchor, x, y);
                end
                
                box.frame:SetWidth(width);
                -- scale inside elements width
                box.mobBar:SetWidth(width-20);
                box.targetBar:SetWidth(width-20);
                box.mobText:SetWidth(width-30);
                box.targetText:SetWidth(width-30);

                -- update display targeted by stuff
                box.targetOf:SetWidth(width-20);
                
                -- set texts alpha
                box.mobText:SetAlpha(SA_OPTIONS.TextAlpha);
                box.targetText:SetAlpha(SA_OPTIONS.TextAlpha);
                
                -- set icon positions
                if (SA_OPTIONS.ClassIconMode>1) then
                        local icon = SA_ICONPOS[SA_OPTIONS.ClassIconMode];
                        SA_List_UpdateIconAppearance(box.classIcon, i, icon);
                else
                        box.classIcon:Hide();
                end
                
                if (SA_OPTIONS.TargetIconMode>1) then
                        local icon = SA_ICONPOS[SA_OPTIONS.TargetIconMode];
                        SA_List_UpdateIconAppearance(box.targetIcon, i, icon);
                else
                        box.targetIcon:Hide();
                end
                
                if (SA_OPTIONS.HuntersMarkIconMode>1) then
                        local icon = SA_ICONPOS[SA_OPTIONS.HuntersMarkIconMode];
                        SA_List_UpdateIconAppearance(box.huntersMarkIcon, i, icon);
                else
                        box.huntersMarkIcon:Hide();
                end
                
        end
end

-- helper function, updates icon appearance
function SA_List_UpdateIconAppearance(frame, i, icon)
        frame:ClearAllPoints();
        frame:SetPoint(icon.point, "Target"..i, icon.point, icon.x, icon.y);
        frame:SetWidth(icon.width);
        frame:SetHeight(icon.height);
        frame:Show();
end

------------------------------------------------------------------------------
-- Refresh the list if enough time has passed since last refresh
------------------------------------------------------------------------------

function SA_List_OnUpdate(elapsed)
        SA_LASTREFRESH = SA_LASTREFRESH - elapsed;
        if (SA_LASTREFRESH > 0) then
                return;
        end
        SA_LASTREFRESH = SA_BASERATE + (SA_INCREASE * SA_PREV.mob_count);
        SA_List_Update();
end

local TARGET_CLICKED = time()
function SA_List_Target_OnClick(arg)
        SA_Debug("TargetClick");
        TARGET_CLICKED = time();
        local target = arg.obj;
        if (not target) then printInfo("SmartAssist: Unknown target?"); return; end;
    -- if ctrl+alt is down paste all targetters names (except tanks) to chat
    if (IsControlKeyDown() and IsAltKeyDown()) then
        local names = "";
        local i = 0;
        for _,c in target.players do
                if (not SA_IsTank(c.unitName)) then
                        names = names..c.unitName..", ";
                        i = i + 1;
                end;
        end
        if (i>0) then 
                names = string.sub(names, 0, -3);
                ChatFrameEditBox:SetText(names.." ");
                        ChatFrameEditBox:Show();
        end
        return;
    end
        
        -- todo: problem is that target.players[1].unitName may have changed target between updating the list --> click
        -- however we could impove situation by assisting the player that has the MOST common target amon all targetters
        -- ie. iterate all targetters and check if UnitIsUnit(unit, others) the player who has most highest count is 
        -- most likelly the correct one! That would however do 40*39 (1590) checks at worst!
        -- additionally / alternatively we could check target name (if only one player for example) and disaply 
        -- error (+sound?) if it has been changed
        
        -- initial implementation (just verboses when debug on)
        --local counts = {};
        local hicount = 0;
        local unitId = "";
        for _,player in target.players do
                --counts[player.unitId] = SA_List_TargetShareCount(player, target.players);
                local count = SA_List_TargetShareCount(player, target.players);
                if (count > hicount) then
                        hicount = count;
                        unitId = player.unitId;
                end
        end
        --for unitId,count in counts do
                --SA_Debug(unitId.." shares target with "..tostring(count), 1);
        --end
        SA_Debug("Assisting "..unitId.." which has highest common count of "..hicount);
        AssistUnit(unitId);
        
        -- initiate assist
        --SA_Debug("assisting "..target.players[1].unitName);
        --AssistUnit(target.players[1].unitId);
        
        SA_PostAssist();
        -- update the list immediattely
        SA_PREV.title_mode = -1; -- resets title mode on next refresh
        SA_List_Refresh();
end

-- return number of players in list that have same target as player
function SA_List_TargetShareCount(player, list)
        local count = 0;
        for _,compare in list do
                if (UnitIsUnit(player.unitId.."target", compare.unitId.."target")) then
                        count = count + 1;
                end
        end
        return count;
end

function SA_List_Target_OnEnter(arg)
        local text = SA_List_Target_GetTooltip(arg);
        GameTooltip:SetOwner(arg, "ANCHOR_LEFT");
        GameTooltip:SetText(text,1,1,1,1,1);
        
        -- prevent going to pausemode if target has just been clicked
        if (time() - TARGET_CLICKED > 3) then
                -- Add some "sleep" to refreshing the list. It's not good to update the list while user is trying to select something.
                SA_LASTREFRESH = SA_BASERATE * 3;
                SA_PREV.title_mode = -1; -- resets title mode on next refresh
                SA_List_SetTitleButton(MODE_PAUSED);
        end
end

function SA_List_Target_OnLeave(arg)
        GameTooltip:Hide();
end

function SA_List_Target_GetTooltip(arg)
        local target = arg.obj;
        if (not target) then return "Unknown?"; end;
        local text = target.fullName.."\nTargetted by:\n";
        for k,v in target.players do
                -- colorize text by class
                local cv = RAID_CLASS_COLORS[string.upper(v.class)];
                local color="";
                if (not cv) then
                        color = "|cff888888";
                else
                        color = SA_ToTextCol(cv.r, cv.g, cv.b);
                end
                text = text .. color..v.unitName .. "|r" .. "\n";
        end
        return text;
end

------------------------------------------------------------------------------
-- Request list to be refreshed immediattely
------------------------------------------------------------------------------

function SA_List_Refresh()
        --SA_Debug("Requesting refresh now, target="..tostring(UnitName("target")));
        SA_LASTREFRESH = 0;
end

------------------------------------------------------------------------------
-- get puller from candidate list
------------------------------------------------------------------------------

function SA_GetPuller(candidates)
        for _,v in candidates do
                if (v.unitName == SA_OPTIONS.puller) then
                        return v;
                end
        end
end

------------------------------------------------------------------------------
-- check if candidates target is already targetted by someone
-- return the target if found and nil if not
------------------------------------------------------------------------------

function SA_GetExistingTarget(candidate, targets)
        for _,target in targets do
                if (UnitIsUnit(target.players[1].target, candidate.target)) then
                        return target;
                end
        end
        return nil;
end

function SA_List_NameSplit(str)
  local t = {n=0}
  local function helper(word) table.insert(t, word) end
  if not string.find(string.gsub(str, "%w+", helper), "%S") then return t end
end

------------------------------------------------------------------------------
-- construct target from candidate
------------------------------------------------------------------------------

function SA_GetTarget(candidate, pullerId)
        target = {};
        target["players"] = { candidate };
        target["name"] = UnitName(candidate.target);
        target["fullName"] = target.name;
        -- intelligent name split
        if (SA_OPTIONS.IntelligentSplit and target.name) then
                local parts = SA_List_NameSplit(target.name);
                if (parts) then
                        if (table.getn(parts)>2) then
                                if (parts[2]~="of") then -- splitting "shade of naxxramas" -> "of naxxramas" is stupid, dont split if second word is "of"
                                        target.name = string.sub(target.name, string.len(parts[1])+1 );
                                end
                        end
                end
        end
        target["health"] = UnitHealth(candidate.target);
        target["targetName"] = UnitName(candidate.target.."target");
        -- test fix for "shang's problem"
        -- this seems to happend when unit is near edge of known area and it's target is outide of it. Hence my client doesn't know anything about this unit ..
        if (target.targetName=="Unknown Entity" or target.targetName=="Unknown") then
                target.targetName=nil;
        end
        -- target.self gives us a reference to mob, example: party<n>target
        target["self"] = candidate.target;
        target["targetHealth"] = ceil( UnitHealth( candidate.target.."target" ) / UnitHealthMax( candidate.target.."target" ) * 100 );
        local _,targetClass = UnitClass(target.self);
        target["targetClass"] = targetClass;
        
        target["playersCount"] = 1;
        if (SA_IsMarked(candidate.target)) then
                target["marked"] = true;
        else
                target["marked"] = false;
        end

        if (isUnitCC(candidate.target)) then
                target["cced"] = true;
        else
                target["cced"] = false;
        end
        
        -- todo: xxx, only if enabled
        target["icon"] = GetRaidTargetIndex(target.self);
        if (not target.icon) then
                target["icon"] = 0;
        end
        
        -- is world boss, elite etc
        target["classification"] = UnitClassification(candidate.target);
        
        -- set myTarget to true if this is my target
        target["myTarget"] = UnitIsUnit(target.self, "target");
        
        if (pullerId~="") then
                target["pullerTarget"] = UnitIsUnit(target.self, pullerId.."target");
        end
        -- is unit in combat, 20.6.2006 - if it has target, treat as in combat!
        target["inCombat"] = UnitAffectingCombat(target.self) or target.targetName;
        
        return target;
end

-- return true if this candidate target should be added to list
function SA_Add_To_List(candidate)
        if( UnitCanAssist("player", candidate.unitId) and 
            UnitExists(candidate.target) and
            ((UnitAffectingCombat(candidate.target) or SA_OPTIONS.OutOfCombat) and not UnitIsDead(candidate.target)) and 
            UnitCanAttack("player", candidate.target)) 
        then
                return true;
        else
                return false;
        end
end

function SA_List_SortTarget(a,b)
        -- marked first
        if (a.marked and not b.marked) then return true; end;
        if (b.marked and not a.marked) then return false; end;

        -- puller target second
        --if (a.pullerTarget and not b.pullerTarget) then return true; end;
        --if (b.pullerTarget and not a.pullerTarget) then return false; end;

        -- ooc last
        if (a.inCombat and not b.inCombat) then return true; end;
        if (b.inCombat and not a.inCombat) then return false; end;
                        
        -- cced second lastest
        if (a.cced and not b.cced) then return false; end;
        if (b.cced and not a.cced) then return true; end;
                
        -- if both unmarked, then sort by name
        if (a.name == b.name) then
                return a.playersCount > b.playersCount;
        else
                return a.name > b.name;
        end
end

------------------------------------------------------------------------------
-- updates the available assists list
------------------------------------------------------------------------------

-- this table contains all variables used in incoming detection & printing
local incoming = { prev_iter=0, currently=0, prev_time=0 };

function SA_List_Update()

        -- if disabled, stop immediattely
        if (not SA_OPTIONS.ShowAvailable) then return; end
        
        local candidates, members= SA_GetCandidates(false)
        table.sort(candidates, function(a,b) return SA_SortCandidate(a,b,members) end);
        
        -- filter out candidates out of range if assisting only members nearby
        -- TODO: might not be good since causes overheading AND we should still add those players outside range to target?
        if (SA_OPTIONS.AssistOnlyNearest) then
                candidates = SA_FilterCandidatesByDistance(candidates, false);
        end
        
        -- add player to candidates if enabled
        if (UnitExists("target") and SA_OPTIONS.AddMyTarget) then
                table.insert(candidates, SA_GetPlayerAsCandidate());
        end

        local aggro_count = 0;
        local wb_count = 0;
        
        local pullerId = "";
        if (SA_OPTIONS.puller) then
                local puller = SA_GetPuller(candidates);
                -- puller might be null if it doesn't exist in group
                if (puller) then
                        pullerId = puller.unitId;
                end
        end
        
        -- construct list of available assists / targets
        local targets = {};
        for _,candidate in candidates do
                if (SA_Add_To_List(candidate)) then
                        local target = SA_GetExistingTarget(candidate, targets);
                        if (target) then
                                -- append targetting candidate to target
                                table.insert(target.players, candidate);
                                target.playersCount = target.playersCount + 1;
                        else
                                -- this candidate target is not targetted by anyone else, construct new target to the list
                                local target = SA_GetTarget(candidate, pullerId);
                                
                                -- filtering if enabled
                                if (SA_OPTIONS.Filter) then
                                        if string.find(string.lower(target.fullName), SA_OPTIONS.Filter) then
                                                table.insert(targets, target);
                                        end
                                else
                                        table.insert(targets, target);
                                end
                        end
                end
        end
        
        -- update non-aggro cache and mark cache, this is used when SORTING candidates (see SA_SortCandidate)
        SA_MARKEDCACHE = {};
        SA_PASSIVECACHE = {};
        local dirty = false;
        local i = 0;
        local mc = 0;
        local ooc_seen = false;
        for _,target in targets do
                i = i + 1;
                if (target.marked) then
                        if (i ~= 1 and mc == 0) then
                                -- marked target not in first place, list is tainted
                                SA_Debug("mark dirty list "..target.name, 1);
                                dirty = true;
                        end
                        mc = mc + 1;
                        for _,c in target.players do
                                SA_MARKEDCACHE[c.unitName] = true;
                        end
                end
                if (not target.inCombat or target.cced) then
                        ooc_seen = true;
                        for _,c in target.players do
                                SA_PASSIVECACHE[c.unitName] = true;
                        end
                else
                        -- incombat targets after ooc targets, list is tainted
                        if (ooc_seen) then
                                SA_Debug("ooc dirty list "..target.name, 1);
                                dirty = true;
                        end
                end
        end
        
        -- if list is dirty abort refresh and request new one with correct cache,
        -- update 22.4.2006     cache is not correct on next refresh either, in fact it may pass long time it is okay. 
        --                                              hence added PREV_DIRTY flag until problem is investigated
        -- update 20.6.2006             sort does not seem to work correctly with buff (ie. hunters mark) priorization!
        -- update 09.7.2006             sorting seems to work fine now, cleanup this + futile flag?
        if (dirty and not SA_PREV.dirty) then
                -- try again soon
                SA_LASTREFRESH = 0.2;
                SA_PREV.dirty = true;
                return;
        end
        SA_PREV.dirty = false;
        
        -- sort targets depending options
        if (SA_OPTIONS.PreserveOrder) then
                table.sort(targets, function(a,b) return SA_List_SortTarget(a,b) end);
        end
        
        ------------------------------------------------------------------------------
        
        local i = 0;
        local overloaded = false;
        
        for _,target in targets do
                i = i + 1;
                if (i>10) then 
                        overloaded = true;
                        break; 
                end
                
                local box = SA_LIST_BOXES[i];
                box.frame.obj = target; -- store the target data to box (used ie. in tooltips)
                box.frame:Show();
                        
                -- set values & texts
                local targetText = target.players[1].unitName;
                if (not SA_OPTIONS.HideTBY) then
                        targetText = "|cffffffffT.by |r"..targetText;
                end
                if (target.playersCount > 1) then
                        targetText = targetText.." + "..tostring(target.playersCount-1); -- -1 for the one who targets (who + n)
                end
                -- append prefixes to mob name + for elite, WB+ for worldboss
                local mobText = target.name;
                if (target.classification == "elite") then
                        mobText = SA_SEMIALERT.."+|r"..mobText;
                end
                if (target.classification == "worldboss") then
                        mobText = SA_SEMIALERT.."WB+|r"..mobText;
                        if (target.inCombat) then
                                wb_count = wb_count + 1;
                        end
                end
                box.mobBar:SetValue(target.health);
                box.mobText:SetText(mobText);
                
                -- colorize box
                if (not target.inCombat) then
                        -- if this is out of combat and my target, apply only slight green efect
                        if (target.myTarget) then
                                box.frame:SetBackdropColor(0,0.65,0,0.65);
                                box.frame:SetBackdropBorderColor(0,1,0,0.75);
                        elseif (target.pullerTarget) then
                                -- yellow background on puller target
                                box.frame:SetBackdropColor(1,1,0,0.3);
                                box.frame:SetBackdropBorderColor(0,0,0,0.4); -- regular grey
                        else
                                box.frame:SetBackdropColor(0,0,0,0.4);
                                box.frame:SetBackdropBorderColor(0,0,0,0.4); -- regular grey
                        end
                else
                        local addinc = true;
                        -- set border color
                        if (target.marked) then
                                box.frame:SetBackdropBorderColor(1,0,0,1);
                        elseif (target.myTarget) then
                                box.frame:SetBackdropBorderColor(0,1,0,1);
                        elseif (target.cced) then
                                box.frame:SetBackdropBorderColor(0,0,0,1);
                                addinc = false;
                        else
                                box.frame:SetBackdropBorderColor(0.8,0.8,0.8,1);
                        end
                        -- add to incoming
                        if (addinc) then
                                incoming.currently = incoming.currently + 1;
                        end
                        
                        -- set background color
                        if (target.myTarget) then
                                box.frame:SetBackdropColor(0,0.75,0,0.85);
                        elseif (target.pullerTarget) then
                                -- yellow background on puller target
                                box.frame:SetBackdropColor(1,1,0,0.3);
                        else
                                box.frame:SetBackdropColor(0,0,0,0.5);
                        end
                end
                
                -- colorize target target text in some situations
                if (target.targetName) then
                        local col = "";
                        local endcol = "";
                        if (UnitName("player") == target.targetName) then
                                -- targetting player
                                aggro_count = aggro_count + 1;
                                if (SA_OPTIONS.TankMode) then
                                        col = SA_SEMIALERT;
                                else
                                        col = SA_ALERTCOL;
                                end
                        elseif (not SA_IsTank(target.targetName) and SA_OPTIONS.TankMode) then
                                -- targeting non tank in tank mode
                                col = SA_ALERTCOL;
                        elseif (SA_IsTank(target.targetName)) then
                                -- targeting tank always on tank color, regardless of tank mode
                                col = SA_TANKCOL;
                        end
                        -- append end of color code marking
                        if (col~="") then
                                endcol = "|r";
                        end
                        box.targetBar:SetValue(target.targetHealth);
                        box.targetText:SetText("T:"..col..target.targetName..endcol);
                else
                        box.targetBar:SetValue(0);
                        box.targetText:SetText("?");
                end
                
                -- colorize mob bar
                if (UnitIsTapped(target.self) and not UnitIsTappedByPlayer(target.self)) then
                        -- target is "grey" to us
                        box.mobBar:SetStatusBarColor(0.8, 0.8, 0.8);
                elseif (UnitPlayerControlled(target.self)) then
                        -- pvp target
                        box.mobBar:SetStatusBarColor(1, 0, 0);
                elseif (not target.inCombat) then
                        -- out of combat target
                        box.mobBar:SetStatusBarColor(0.38, 0.38, 0.38);
                else
                        -- normal green
                        box.mobBar:SetStatusBarColor(0, 1, 0);
                end
                
                -- make texts visible always (bugs on some systems)
                -- code peeked from TextStatusBar_UpdateTextString sources
                -- perhaps I should've called that method but it does some other unwanted things
                box.mobBar.TextString:Show();
                box.targetBar.TextString:Show();
                box.targetOf:SetText(targetText);
                
                -- set class icon (texture coordinates)
                local coords = SA_TEXTCOORDS[target.targetClass];
                box.classIcon:SetTexCoord(coords.left, coords.right, coords.top, coords.bottom);
                
                -- set target icon (texture coordinates)
                -- TODO: xxx, only if feature enabled
                if (target.icon>0) then
                        local icon = UnitPopupButtons["RAID_TARGET_"..target.icon];
                        box.targetIcon:SetTexCoord(icon.tCoordLeft, icon.tCoordRight, icon.tCoordTop, icon.tCoordBottom );
                        box.targetIcon:Show();
                else
                        box.targetIcon:Hide();
                end

                -- hunters mark icon
                if (target.marked and SA_OPTIONS.HuntersMarkIconMode>1) then
                        box.huntersMarkIcon:Show();
                else
                        box.huntersMarkIcon:Hide();
                end
                
        end
        
        ------------------------------------------------------------------------------
        
        -- if count has been changed update visibility
        -- TODO: apparently there is no performance loss calling hide/show .. so this is partially futile now
        if (i ~= SA_PREV.mob_count) then
                for j = i + 1, SA_PREV.mob_count do
                        if (j>10) then
                                break;
                        end
                        SA_LIST_BOXES[j].frame:Hide();
                end
        end
        
        -- if player have a ACQUIRED aggro, play optional sound to alert player
        if (aggro_count > SA_PREV.aggro_count) then
                if (SA_OPTIONS.AudioWarning) then PlaySound(SA_OPTIONS.SoundGainAggro); end;
                if (SA_OPTIONS.VerboseAcquiredAggro) then
                        SA_Verbose("Acquired aggro!", COLOR_ALERT); 
                end
        end
        -- if player have a LOST aggro, play optional sound to alert player
        if (aggro_count < SA_PREV.aggro_count and i >= SA_PREV.mob_count) then
                if (SA_OPTIONS.LostAudioWarning) then PlaySoundFile(SA_OPTIONS.SoundLoseAggro); end;
                if (SA_OPTIONS.VerboseLostAggro) then
                        SA_Verbose("Lost aggro!", COLOR_ALERT);
                end
        end
        -- if incoming worldbosses, play optional sound to alert player, TODO: OPTION!
        if (wb_count > SA_PREV.wb_count) then
                SA_Verbose("Incoming worldboss!", COLOR_ALERT);
                PlaySoundFile(SA_OPTIONS.SoundIncomingWoldBoss);
        end
        
        -- if we have new mobs in combat
        -- TODO: on incoming worldboss level unit play alert sound!
        if (SA_OPTIONS.VerboseIncoming) then
                local now = time();
                if (incoming.currently > incoming.prev_iter and now - incoming.prev_time > 4) then
                        
                        local amount = incoming.currently - incoming.prev_iter;
                        if (amount > 1) then
                                SA_Verbose("Incoming x"..amount.."!", COLOR_ALERT); 
                        else
                                SA_Verbose("Incoming!", COLOR_ALERT); 
                        end
                        
                        incoming.prev_time = now;
                end
                incoming.prev_iter = incoming.currently;
                incoming.currently = 0;
        end
        
        -- update title to correct mode
        if (i>0) then
                title_mode = MODE_NORMAL;
        else
                title_mode = MODE_OOC;
        end
        if (SA_OPTIONS.Filter) then
                title_mode = MODE_FILTERED;
        end
        if (SA_OPTIONS.AssistOnlyNearest) then
                title_mode = MODE_NEAREST;
        end
        if (overloaded) then
                title_mode = MODE_OVERLOADED;
        end
        
        -- update title button if mode has been changed
        if (title_mode ~= SA_PREV.title_mode) then
                SA_List_SetTitleButton(title_mode);
        end
        
        -- set prev data for next iteration
        SA_PREV.title_mode = title_mode;
        SA_PREV.mob_count = i;
        SA_PREV.wb_count = wb_count;
        SA_PREV.aggro_count = aggro_count;
end

------------------------------------------------------------------------------
-- Title button handling
------------------------------------------------------------------------------

function SA_List_SetTitleButton(mode)
        if (mode == MODE_NORMAL or mode == MODE_OOC) then
                if (mode == MODE_NORMAL) then
                        SAListTitleButton:SetAlpha(1);
                else
                        if (SA_OPTIONS.HideTitle) then
                                SAListTitleButton:SetAlpha(0);
                        else
                                SAListTitleButton:SetAlpha(SA_OPTIONS.ListOOCAlpha);
                        end
                end
                SAListTitleButton:SetBackdropColor(0, 0, 0, 0.3);
                SAListTitleButton:SetBackdropBorderColor(1,1,1,1)
                SAListTitle:SetText("Available assists");
        elseif (mode == MODE_FILTERED or mode == MODE_NEAREST) then
        SAListTitleButton:SetAlpha(1);
        SAListTitleButton:SetBackdropColor(0,0.6,0.8,1);
        SAListTitleButton:SetBackdropBorderColor(0,0.6,0.8,1);
        local text = "";
        if (mode == MODE_FILTERED) then
                text = "Filtered";
        end
        if (mode == MODE_NEAREST) then
                text = "Nearest";
        end
        SAListTitle:SetText(text);
    elseif (mode == MODE_PAUSED) then
        SAListTitleButton:SetAlpha(1);
        SAListTitleButton:SetBackdropColor(0.3,0.3,0.3,1);
        SAListTitleButton:SetBackdropBorderColor(0.6,0.6,0.6,1);
        SAListTitle:SetText("Paused");
        elseif (mode == MODE_OVERLOADED) then
                SAListTitleButton:SetAlpha(1);
                SAListTitleButton:SetBackdropColor(1,0,0,1);
                SAListTitleButton:SetBackdropBorderColor(1,0,0,1);
                SAListTitle:SetText("OVERLOADED");
        else
                printInfo("smartassist error: unknown title mode "..tostring(mode));
        end
end

SA_PREV_ALPHA = 1;
function SA_List_TitleButton_OnEnter()
        SA_PREV_ALPHA = SAListTitleButton:GetAlpha();
        SAListTitleButton:SetAlpha(1);
end

function SA_List_TitleButton_OnLeave()
        SAListTitleButton:SetAlpha(SA_PREV_ALPHA);
end

Generated by GNU Enscript 1.6.5.90.