vanilla-wow-addons – Rev 1
?pathlinks?
-- AUTHOR: Paranoidi
-- $LastChangedDate: 2006-08-14 18:34:44 +0300 (Mon, 14 Aug 2006) $
-- $LastChangedBy: marko $
-- $Rev: 247 $
--[[
local px,py = GetPlayerMapPosition("player");
local tx,ty = GetPlayerMapPosition("raid"..idx);
local xdist = tx-px;
local ydist = ty-py;
local dist = sqrt(math.pow(xdist,2)+math.pow(ydist,2));
http://intern.darklegion.de/interface/profiler.jpg
testaa profileria?
BUG: jonku pelaajan targettaaminen ei nollaa järjestystä (kuten tyhjän valinta) !
--]]
-- integration to myAddons
SmartAssistDetails = {
name = "SmartAssist",
description = "SmartAssist is an addon which improves default assisting system in groups.",
version = "1.2.9",
releaseDate = string.sub("$LastChangedDate: 2006-08-14 18:34:44 +0300 (Mon, 14 Aug 2006) $", 18, 28),
author = "paranoidi",
email = "paranoidi@gmx.net",
category = MYADDONS_CATEGORY_COMBAT,
frame = "SmartAssistFrame",
optionsframe = "SAOptionsFrame"
};
-- options
SA_OPTIONS_WRAPPER = {};
SA_OPTIONS = nil;
SA_MARKEDCACHE = {};
SA_PASSIVECACHE = {};
ASSIST_EMOTES = {};
PREVIOUS_ASSISTS = {};
PREVIOUS_NEAREST = false;
PREVIOUS_FALLBACK = false;
PREVIOUS_ASSIST_TIME = 0;
PREVIOUS_NEAREST_TIME = 0;
TARGET_CHANGED = false;
SMARTASSIST_CORE_REV = tonumber(string.sub("$Rev: 247 $", 7, -3));
BINDING_HEADER_SMARTASSIST = "SmartAssist";
BINDING_NAME_SASET = "Set puller";
BINDING_NAME_SAASSIST = "Assist";
SA_RUNNING = false; -- used to disable sounds and detect target changes
PASSIVE_WARN_FLAG = false; -- is passive warning visible
COLOR_DEFAULT = {r=0, g=0.8, b=1};
COLOR_ALERT = {r=0.9, g=0, b=0};
StaticPopupDialogs["SA_NO_BINDINGS"] = {
text = TEXT("You must bind SmartAssist key before being able to use this addon!"),
button1 = TEXT(ACCEPT),
OnAccept = function()
printInfo("Tip: Press ESC key and go to 'bind keys' options. SmartAssist keys should be there at somewhere near bottom of the list.");
end,
timeout = 0,
whileDead = 1,
};
StaticPopupDialogs["SA_UPGRADE_RESETPOS"] = {
text = TEXT("SmartAssist will need to reset frame positons due changes in the way scaling is used."),
button1 = TEXT(ACCEPT),
OnAccept = function()
SA_ResetFramePos();
end,
timeout = 0,
whileDead = 1,
};
StaticPopupDialogs["SA_AUTOCONF"] = {
text = TEXT("Auto configure Smart Actions?"),
button1 = TEXT(ACCEPT),
button2 = TEXT(CANCEL),
OnAccept = function()
SA_AutoConfigureSmartActions();
printInfo("You should see SmartAssist options and verify detected settings. Use command /sa to access configuration.");
end,
timeout = 0,
whileDead = 1,
};
StaticPopupDialogs["SA_DISABLE_ATTACK"] = {
text = TEXT("You have 'attack on assist' enabled in interface options which will cause problems with SmartAssist.\n\nAllow SmartAssist to turn it off (recommended) ?"),
button1 = TEXT(YES),
button2 = TEXT(NO),
OnAccept = function()
SetCVar("assistAttack", "0");
end,
OnCancel = function()
SA_OPTIONS.AutoAttackWhineIgnored = true;
end,
timeout = 0,
whileDead = 1,
};
function SA_OnLoad()
SLASH_SMARTASSIST1 = "/smartassist";
SLASH_SMARTASSIST2 = "/sa";
SlashCmdList["SMARTASSIST"] = function(msg)
SA_Command(msg);
end
end
function SA_InitOption(option, value)
if (SA_OPTIONS[option]==nil) then SA_OPTIONS[option]=value; end;
end
------------------------------------------------------------------------------
-- event: variables loaded
------------------------------------------------------------------------------
function SA_VariablesLoaded()
-- Register the addon in myAddOns
if(myAddOnsFrame_Register) then
myAddOnsFrame_Register(SmartAssistDetails);
end
printInfo("SmartAssist "..SmartAssistDetails.version.." loaded.")
-- add item to every possible context menu
table.insert(UnitPopupMenus["PARTY"], "PULLER");
table.insert(UnitPopupMenus["PLAYER"], "PULLER");
table.insert(UnitPopupMenus["RAID"], "PULLER");
UnitPopupButtons["PULLER"] = { text = TEXT("Set as puller"), dist = 0 };
SA_Init();
end
function SA_Init()
-- load realm & char specific configs
local id = SA_GetAccountID();
if (SA_OPTIONS_WRAPPER[id]==nil) then
printInfo("SmartAssist created new default settings for "..id);
SA_OPTIONS_WRAPPER[id] = {};
end
SA_OPTIONS = SA_OPTIONS_WRAPPER[id];
-- set default values to option variables if they do not exist
-- before rev 66 we had no stored rev number so we need to make sure it's in such cases atleast zero
SA_InitOption("Rev", 0);
-- special cases where we need to reset variables when upgrading the addon from older version
if (SA_OPTIONS.Rev<240 and SA_OPTIONS.Rev>0) then
StaticPopup_Show("SA_UPGRADE_RESETPOS");
end
-- basic options
SA_InitOption("VisualWarning", true);
SA_InitOption("AssistOnEmote", true);
SA_InitOption("PriorizeHealth", true);
SA_InitOption("PriorizeHealthValue", 35);
SA_InitOption("FallbackTargetNearest", true);
SA_InitOption("CheckNearest", false);
SA_InitOption("NearestMustBePvP", false);
SA_InitOption("NearestMustBeTargetting", true);
SA_InitOption("AssistOnlyNearest", false);
-- available assist list
SA_InitOption("ShowAvailable", true);
SA_InitOption("PreserveOrder", false);
SA_InitOption("OutOfCombat", false);
SA_InitOption("TankMode", false); -- todo: class based defaults
SA_InitOption("ClassIconMode", 1);
SA_InitOption("TargetIconMode", 3);
SA_InitOption("HuntersMarkIconMode", 1);
SA_InitOption("AddMyTarget", false);
SA_InitOption("ListWidth", 150);
SA_InitOption("ListScale", 1.0);
SA_InitOption("ListOOCAlpha", 0.4);
SA_InitOption("ListSpacing", -5);
SA_InitOption("ListHorizontal", false);
SA_InitOption("ListTwoRow", false);
SA_InitOption("LockList", false);
SA_InitOption("HideTBY", false);
SA_InitOption("HideTitle", false);
SA_InitOption("AudioWarning", true); -- todo: class based defaults
SA_InitOption("LostAudioWarning", false); -- todo: class based defaults
SA_InitOption("VerboseAcquiredAggro", true);
SA_InitOption("VerboseLostAggro", false);
SA_InitOption("TextAlpha", 0.9);
SA_InitOption("IntelligentSplit", false);
-- advanced
SA_InitOption("DisableSliderValue", 15);
SA_InitOption("DisableTargetNearest", true);
SA_InitOption("DisablePriorityHealth", true);
SA_InitOption("AssistKeyMode", 1);
SA_InitOption("DisableAutoCastKeyMode", 2);
SA_InitOption("VerboseAssist", true);
SA_InitOption("VerboseIncoming", false);
SA_InitOption("VerboseNearest", true);
SA_InitOption("VerboseUnableToAssist", false);
SA_InitOption("DisableAssistWithoutPuller", false);
SA_InitOption("PauseResetsOrder", false);
-- smart actions
SA_InitOption("AssistSpells", {});
SA_InitOption("TriggerAssist", true);
-- geek (not in options)
SA_InitOption("SoundLoseAggro", "Sound\\Spells\\EyeOfKilroggDeath.wav");
SA_InitOption("SoundIncomingWoldBoss", "Sound\\Spells\\PVPFlagTaken.wav");
SA_InitOption("SoundGainAggro", "AuctionWindowClose");
-- development
SA_InitOption("DebugLevel", 0);
-- display auto configuration dialog on first run
SA_InitOption("AutoConfiguredV2", false);
if (not SA_OPTIONS.AutoConfiguredV2) then
SA_OPTIONS.AutoConfiguredV2 = true;
StaticPopup_Show("SA_AUTOCONF");
end
-- init auto cast on assist defaults for hunters
if (SA_OPTIONS["AutoAssist"]==nil and UnitClass("player") == CLASSNAME_HUNTER) then
SA_OPTIONS.AutoAssist = true;
SA_OPTIONS.FallbackOnBusy = true;
SA_OPTIONS.AutoAssistTexture = "Interface\\Icons\\INV_Weapon_Rifle_08";
SA_OPTIONS.AutoAssistName = SPELL_AUTOSHOT;
end
SA_InitOption("AutoAssist", false);
SA_InitOption("AutoPetAttack", false);
SA_InitOption("AutoPetAttackBusy", false);
-- check if should display 'attack on assist' disable dialog if not ignored
SA_InitOption("AutoAttackWhineIgnored", false);
SA_CheckForAssistAttack();
-- prefered assitance order (just a guess, got better one?)
SA_InitOption("ClassOrder",{ CLASSNAME_WARRIOR, CLASSNAME_HUNTER,
CLASSNAME_ROGUE, CLASSNAME_PALADIN,
CLASSNAME_DRUID, CLASSNAME_SHAMAN, CLASSNAME_MAGE,
CLASSNAME_WARLOCK, CLASSNAME_PRIEST } );
-- store revision number to configuration, this can be used to detect some special cases where we need to reset
-- exsisting options (ie. bug in assistance order list) in new releases
SA_OPTIONS.Rev = SMARTASSIST_CORE_REV;
ASSIST_EMOTES = { EMOTE_EVERYONE_ATTACK, EMOTE_ATTACK, EMOTE_HELP };
if (SA_OPTIONS.ShowAvailable) then
SAListFrame:Show();
else
SAListFrame:Hide();
end
end
-- check if assist attack is enabled
function SA_CheckForAssistAttack()
if (not SA_OPTIONS.AutoAttackWhineIgnored) then
if (GetCVar("assistAttack")~="0" and SA_OPTIONS.AutoAssist and SA_OPTIONS.AutoAssistName) then
StaticPopup_Show("SA_DISABLE_ATTACK");
end
end
end
-------------------------------------------------------------------------------
-- WARNING ICON EVENTS
-------------------------------------------------------------------------------
SA_WARN_SPEED = 0.07;
SA_WARN_REFRESH = SA_WARN_SPEED;
SA_WARN_ALPHA = 0.4;
SA_WARN_ALPHA_UP = true;
function SA_WarnOnUpdate(elapsed)
if (not PASSIVE_WARN_FLAG) then return; end;
SA_WARN_REFRESH = SA_WARN_REFRESH - elapsed;
if (SA_WARN_REFRESH > 0) then
return;
end
SA_WARN_REFRESH = SA_WARN_SPEED;
-- if target target is != nil (attacking someone) then REMOVE the icon
if (UnitName("targettarget")~=nil) then
SA_Debug("target target != nil .. remove the warning", 1);
SA_ResetWarning();
SA_AggroedTargetMsg();
return;
end
if (SA_WARN_ALPHA_UP) then
SA_WARN_ALPHA = SA_WARN_ALPHA + 0.1;
else
SA_WARN_ALPHA = SA_WARN_ALPHA - 0.1;
end
if (SA_WARN_ALPHA<0.4) then
SA_WARN_ALPHA_UP = true;
SA_WARN_ALPHA = 0.5;
end
if (SA_WARN_ALPHA>1.0) then
SA_WARN_ALPHA_UP = false;
SA_WARN_ALPHA = 0.9;
end
SAWarningFrame:SetAlpha(SA_WARN_ALPHA);
end
-------------------------------------------------------------------------------
-- handles smart assist slash commands
-------------------------------------------------------------------------------
function SA_Command(msg)
local _,_,value = string.find(msg, "debug (%d)");
if (value) then
SA_OPTIONS.DebugLevel = tonumber(value);
printInfo("Setting debug level to "..tostring(SA_OPTIONS.DebugLevel));
elseif (msg=="available") then
SA_ToggleAvailable();
elseif (msg=="assist") then
DoSmartAssist();
elseif (msg=="reset" or msg=="reset all") then
-- resets frame positions
SA_ResetFramePos();
-- clears this character settings and initializes defaults
if (msg=="reset all") then
printInfo("Reseting SmartAssist settings for all characters");
SA_OPTIONS_WRAPPER = {};
else
printInfo("Reseting SmartAssist settings for this character");
local id = SA_GetAccountID();
SA_OPTIONS_WRAPPER[id] = {};
end
SA_Init();
elseif (msg=="rev" or msg=="ver") then
if (SMARTASSIST_REV) then
printInfo("SmartAssist revision "..SMARTASSIST_REV);
else
printInfo("Unofficial or broken build! Revision number missing! Core rev "..SA_OPTIONS.Rev);
end
elseif (msg=="dump") then
if (not DevTools_Dump) then
printInfo("Requires devtools");
else
printInfo("Dumping internal variables:");
printInfo("PREVIOUS_ASSISTS: ");
DevTools_Dump(PREVIOUS_ASSISTS);
printInfo("PREVIOUS_NEAREST: "..tostring(PREVIOUS_NEAREST));
printInfo("TARGET_CHANGED: "..tostring(TARGET_CHANGED));
printInfo("SA_RUNNING: "..tostring(SA_RUNNING));
end
else
printInfo("Available parameters:");
printInfo("ver - Display addon version information.");
printInfo("assist - Execute SmartAssist for macros. Or call DoSmartAssist()");
printInfo("reset - Reset character settings and frame positions.");
printInfo("reset all - Reset all characters settings and frame positions.");
SA_ShowOptions();
end
end
function SA_ResetFramePos()
SAListFrame:ClearAllPoints();
SAListFrame:SetPoint("TOP", "UIParent", "TOP", 0, -20);
SAListFrame:StopMovingOrSizing();
SAWarningFrame:ClearAllPoints();
SAWarningFrame:SetPoint("TOP", "UIParent", "TOP", 5, -25);
SAWarningFrame:StopMovingOrSizing();
SAOptionsFrame:ClearAllPoints();
SAOptionsFrame:SetPoint("CENTER", "UIParent", "CENTER", 0, 0);
SAOptionsFrame:StopMovingOrSizing();
end
function SA_ShowOptions()
-- open config, or warn about key bindings
local key1, key2 = GetBindingKey("SAASSIST");
if (key1==nil and key2==nil) then
StaticPopup_Show("SA_NO_BINDINGS");
else
SAOptionsFrame:Show();
end
end
-------------------------------------------------------------------------------
-- SMART ACTIONS
-------------------------------------------------------------------------------
local old_UseAction = UseAction;
function UseAction(m1, m2, m3)
local name = SA_GetSlotName(m1);
local assistAction = false;
if (SA_TableIndex(SA_OPTIONS.AssistSpells, name) ~= -1) then
assistAction = true;
end
-- no target selected and attack action
-- treat targetting player as no target
if (SA_OPTIONS.TriggerAssist and (UnitName("target")==nil or UnitIsUnit("target","player")) and assistAction) then
SA_Debug("no target, initiating assist by action!", 1);
FindTarget(false, false);
-- target is not player selected so don't attack unless target already in combat
if (not UnitAffectingCombat("target")) then
SA_Debug("not in combat, aborting action", 1);
SA_ShowWarning();
return;
end
end
-- if trying to attack friendly unit, initiate smart action
-- UnitIsFriend bugs when dueling people in your party
-- Added UnitPlayerControlled because on pve server as non flagged attacking the opposite faction initiated assist!
if (assistAction and not UnitCanAttack("target", "player") and not (UnitPlayerControlled("target") and UnitFactionGroup("player")~=UnitFactionGroup("target"))) then
SA_Debug("friendly target, assistAction = "..tostring(assistAction), 1);
if (UnitCanAttack("player", "targettarget")) then
SA_Debug("friendly target, assisting", 1);
AssistUnit("target");
else
-- supresses annoyance when attacking and ally has no target
if (assistAction) then
SA_Debug("aborting action, supressing annoyance", 1);
return;
end
end
end
return old_UseAction(m1, m2, m3);
end
-------------------------------------------------------------------------------
-- implements shift click assist
-- should work with ANYWHERE where you can select unit
-------------------------------------------------------------------------------
local SA_PREV_ASSIST_TIME = 0;
function SA_PlayerTargetChanged()
TARGET_CHANGED = true;
-- target lost
if (not UnitExists("target")) then
SA_ResetWarning();
if (not SA_RUNNING) then
PREVIOUS_ASSISTS = {};
PREVIOUS_NEAREST = false;
end
SA_List_Refresh();
return; -- fixes issue when we have target already (fires two events, on lost and on gain)
end
-- asisst by unit selection & modifier key
if ( (IsShiftKeyDown() and SA_OPTIONS.AssistKeyMode==1) or
(IsControlKeyDown() and SA_OPTIONS.AssistKeyMode==2) or
(IsAltKeyDown() and SA_OPTIONS.AssistKeyMode==3) )
then
-- this time check (hoax) removes looping, when selecting player that has friendly taget. Without this
-- it goes there and starts to assist him
-- todo: this can be done better .. if we check what's the target!
local now = time();
local dif = now - SA_PREV_ASSIST_TIME;
if (dif == 0) then return; end
SA_PREV_ASSIST_TIME = now;
if (UnitCanAssist("player", "target") and
UnitCanAttack("player", "targettarget"))
then
SAMsgFrame:AddMessage("Assisting "..UnitName("target"), 0.0, 0.8, 0.0, 1, 2.5);
AssistUnit("target");
SA_PostAssist();
else
if (UnitExists("target") and not UnitIsDead("target")) then
UIErrorsFrame:AddMessage("Unable to assist", 1.0, 0, 0, 1.0, 1.5);
end
end
end
SA_List_Refresh();
end
------------------------------------------------------------------------------
-- credits to popup assist author, refactored a bit tough :)
------------------------------------------------------------------------------
local old_UnitPopup_OnClick = UnitPopup_OnClick;
function UnitPopup_OnClick(index)
local dropdownFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
local unit = dropdownFrame.unit;
if ( this.value == "PULLER" ) then
SA_SetPuller(unit);
end
return old_UnitPopup_OnClick(index);
end
------------------------------------------------------------------------------
-- param: unit - ID to be set as puller
-- set unit as puller
------------------------------------------------------------------------------
function SA_SetPuller(unit)
if (UnitIsFriend(unit, "player")) then
local candidates,_ = SA_GetCandidates(true);
-- check that current puller exists in party, if not clear
if (not candidates[UnitName(unit)]) then
SAMsgFrame:AddMessage("Puller must be in party / raid", 1.0, 1.0, 1.0, 1, 3);
else
SA_OPTIONS["puller"] = UnitName(unit);
SAMsgFrame:AddMessage("The assigned puller is now "..SA_OPTIONS["puller"], 1.0, 1.0, 1.0, 1, 1.5);
end
else
SA_OPTIONS["puller"] = nil;
SAMsgFrame:AddMessage("Assigned puller cleared", 1.0, 1.0, 1.0, 1, 1.5);
end
-- just incase that option window is visible, update the text
SA_Options_UpdatePullerText();
end
------------------------------------------------------------------------------
-- credits to mozz
-- this is used to disable some anying unit deselect sounds while assisting
-- (we have to clear the selected unit in some cases)
------------------------------------------------------------------------------
local old_PlaySound = PlaySound;
function PlaySound(name)
if (not SA_RUNNING) then
return old_PlaySound(name);
end
if (name ~= "INTERFACESOUND_LOSTTARGETUNIT" and name ~= "igCharacterNPCSelect" ) then
return old_PlaySound(name);
end
end
------------------------------------------------------------------------------
-- main method, this is called when smartassist does its thing
------------------------------------------------------------------------------
function DoSmartAssist()
SA_RUNNING = true;
-- if has friendly target, straight assist it
local f = true;
if (UnitExists("target")) then
if (not UnitCanAttack("target", "player") and not (UnitPlayerControlled("target") and UnitFactionGroup("player")~=UnitFactionGroup("target"))) then
if (SA_OPTIONS.VerboseAssist) then
SA_Verbose("Assisting selected player "..UnitName("target"));
end
AssistUnit("target");
f = false;
end
end
if (f) then
FindTarget(true, false);
end
SA_RUNNING = false;
SA_PostAssist();
SA_List_Refresh();
end
------------------------------------------------------------------------------
-- post assist logic (after target has been selected)
------------------------------------------------------------------------------
function SA_PostAssist()
-- auto-assist, disable if certain modifier key is down
if ( ((not IsShiftKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==1) or
(not IsControlKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==2) or
(not IsAltKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==3)) and
UnitExists("target") and
UnitAffectingCombat("target") )
then
if (SA_OPTIONS.AutoAssist and SA_OPTIONS.AutoAssistName)
then
SA_Debug("Target already in combat, assisting with "..SA_OPTIONS.AutoAssistName, 3);
CastSpellByName(SA_OPTIONS.AutoAssistName); -- casts highest level
end
if ((SA_OPTIONS.AutoPetAttack and UnitExists("pet") and not UnitIsDead("pet") and not UnitExists("pettarget")) or
(SA_OPTIONS.AutoPetAttack and UnitExists("pet") and not UnitIsDead("pet") and SA_OPTIONS.AutoPetAttackBusy))
then
SA_Debug("Attacking with pet", 3);
PetAttack();
end
end
-- visual alert, if needed
SA_ShowWarning();
end
------------------------------------------------------------------------------
-- receives all emote events, implements assist by emote
------------------------------------------------------------------------------
function SA_EmoteAssist(arg1)
if (not SA_OPTIONS.AssistOnEmote) then
return;
end
-- todo idea: hitting assist key should go back to previous target (if possible)
-- parse emotes
for k,v in ASSIST_EMOTES do
local sp, ep, name = string.find(arg1, "(%w+) "..v);
if (name ~= nil) then
SA_Debug("assist emote detected, name="..name, 1);
end
-- if name exists, we have a match
if name and name ~= EMOTE_OWN then
local candidates,_ = SA_GetCandidates(true);
if (candidates[name]~=nil) then
SAMsgFrame:AddMessage("Assisting "..name, 0.0, 0.8, 0.0, 1, 2.5);
AssistUnit(candidates[name].unitId);
return true;
end
SA_Debug("not assisting "..name..", not in party/raid", 1);
end
end
end
function SA_UnitHealth(arg1)
-- flag is used to reduce overheading, this gets called a lot ..
if (not PASSIVE_WARN_FLAG) then
return;
end
if (arg1=="target") then
SA_ResetWarning();
-- if someone else attacked the target, show another text saying its ok to start blasting
-- this is not idiot proof check, seems to work fine tough
if (not UnitIsUnit("targettarget", "pet") and not UnitIsUnit("targettarget", "player")) then
SA_Debug("Someone else has attacked our target, show notification", 4);
SA_AggroedTargetMsg();
else
SA_Debug("player has most likelly attacked the target", 4);
-- todo: perhaps send pull message to party?
end
end
-- todo idea: we could watch other players health here and suggest assist
end
-----------------------------------------------------------------
-- show warning if target is not in combat and feature is enabled
-----------------------------------------------------------------
function SA_ShowWarning()
if (not UnitAffectingCombat("target") and UnitExists("target") and
UnitName("targettarget")==nil and SA_OPTIONS.VisualWarning and
not UnitPlayerControlled("target"))
then
PASSIVE_WARN_FLAG = true;
SAWarningFrame:Show();
return true;
end
end
function SA_AggroedTargetMsg()
-- the old msg was annoying as hell, replaced with SCT message, only if available
-- todo: add as option!
if (SCT_Display) then
SCT_Display(message, COLOR_DEFAULT);
end
end
function SA_ResetWarning()
PASSIVE_WARN_FLAG = false;
SAWarningFrame:Hide();
end
-------------------------------------------------------------
-- get distance unit is in, 0 if out of range
-- this funnly looking nesting makes minimal calls to api.
-- ie. No need to check for closer ranges if unit is not even
-- in 28yard range
-------------------------------------------------------------
-- TODO: NOT USED ATM!
function SA_GetDistance(unit)
if (CheckInteractDistance(unit, 4)) then
if (CheckInteractDistance(unit, 3)) then
if (CheckInteractDistance(unit, 2)) then
if (CheckInteractDistance(unit, 1)) then
return 1;
end
return 2;
end
return 3;
end
return 4;
end
return -1;
end
-----------------------------------------------------------------------------------------
-- construct and return one candidate
-- if unit has invalid flag it should not be used because it does not contain all fields
-- foexample pets that are very far away do not have all fields present
-----------------------------------------------------------------------------------------
function SA_GetCandidate(unit, i)
local candidate = {};
candidate["unitName"] = UnitName(unit..i);
candidate["unitId"] = unit..i;
candidate["target"] = unit..i.."target";
candidate["health"] = ceil( UnitHealth( unit..i ) / UnitHealthMax( unit..i ) * 100 );
candidate["class"] = UnitClass(unit..i);
candidate["dead"] = UnitIsDead(unit..i);
if (string.find(unit, "pet") ~= nil) then
candidate["pet"] = true;
end
if (candidate.unitName == nil) then
--SA_DebugCandidate(candidate);
candidate["invalid"] = true;
end
if (candidate.class == nil) then
--SA_DebugCandidate(candidate);
candidate["invalid"] = true;
end
if (candidate.health == nil) then
--printInfo("Problem with smartassist; unit health is unknown!");
candidate["invalid"] = true;
SA_DebugCandidate(candidate);
end
return candidate;
end
function SA_GetPlayerAsCandidate()
local candidate = {};
candidate["unitName"] = UnitName("player");
candidate["unitId"] = "player";
candidate["target"] = "target";
candidate["health"] = ceil( UnitHealth( "player" ) / UnitHealthMax( "player" ) * 100 );
candidate["class"] = UnitClass("player");
candidate["dead"] = UnitIsDead("player");
return candidate;
end
------------------------------------------------------------------
-- return iteration info: is this party or raid, how many members
------------------------------------------------------------------
function SA_GetIterInfo()
local mode, members = nil;
if (GetNumRaidMembers()>0) then
mode = "raid";
members = GetNumRaidMembers();
elseif (GetNumPartyMembers()>0) then
mode = "party";
members = GetNumPartyMembers();
end
return mode, members;
end
------------------------------------------------------------------
-- converts candidate list to map version
-- this is used in some places where we need list AND map versions
------------------------------------------------------------------
function SA_ConvertCandidateListToMap(candidates)
local map = {};
for k,v in candidates do
map[v.unitName] = v;
end
return map;
end
-------------------------------------------------------------
-- params: if map is true then as a map where key is unitName
-- if map is false then as list
-- return list of possible assistable units
-------------------------------------------------------------
local UNIQUE_WARNED = false;
function SA_GetCandidates(map)
local candidates = {};
local mode, members = SA_GetIterInfo(); -- got stack overflow once here
local pullerAdded = false;
-- for soloing and party, add our pet
if (UnitExists("pet")) then
local op = SA_GetCandidate("pet", "");
if (not op.invalid and not op.dead) then
-- fixes problem when there are multiple pets with same name
if (SA_OPTIONS["puller"] == op.unitName) then
pullerAdded = true;
end
if (map) then
candidates[op.unitName] = op;
else
table.insert(candidates, op);
end
end
end
-- if no party/raid, abort here
if (members==nil) then return candidates, 0; end;
local myname = UnitName("player");
for i = 1, members do
local candidate = SA_GetCandidate(mode, i);
-- do not add invalid, dead or myself
if (not candidate.invalid and not candidate.dead and candidate["unitName"] ~= myname) then
if (map) then
candidates[candidate.unitName] = candidate;
else
table.insert(candidates, candidate);
end
-- add pet to list if has one
if (UnitExists(mode.."pet"..i)) then
local pcandidate = SA_GetCandidate(mode.."pet", i);
if (not pcandidate.invalid and not pcandidate.dead) then
-- hotfix for unique pet puller problem
-- players are unique by server so we don't have to worry about them
if (SA_OPTIONS["puller"] == pcandidate.unitName and pullerAdded) then
if (not UNIQUE_WARNED) then
printInfo("SMARTASSIST PROBLEM: Puller is not unique!");
UNIQUE_WARNED = true;
end
else
if (map) then
candidates[pcandidate.unitName] = pcandidate;
else
table.insert(candidates, pcandidate);
end
-- if added puller unit, set flag that no other unit with same name can be added
if SA_OPTIONS["puller"] == pcandidate.unitName then
pullerAdded = true;
end
end
end
end
end
end
return candidates, members;
end
------------------------------------------------------------------------------
-- if we have enabled filtering targets, remove those candidates who have
-- something else. It was much more convient to implement this way rather
-- than to modify get candidates and gazillion other places
------------------------------------------------------------------------------
function SA_FilterCandidates(candidates, map)
local filtered = {};
for key,candidate in candidates do
if (UnitExists(candidate.target) and string.find(string.lower(UnitName(candidate.target)), SA_OPTIONS.Filter)) then
if (map) then
filtered[key] = candidate;
else
table.insert(filtered, candidate);
end
end
end
return filtered;
end
function SA_FilterCandidatesByDistance(candidates, map)
--debugprofilestart();
local filtered = {};
for key,candidate in candidates do
if (CheckInteractDistance(candidate.unitId, 4)) then
if (map) then
filtered[key] = candidate;
else
table.insert(filtered, candidate);
end
end
end
--SA_Debug("filtering by distance took "..debugprofilestop().." ms", 1);
return filtered;
end
------------------------------------------------------------------------------
-- sort method for candidates
------------------------------------------------------------------------------
local detected_bug = false;
function SA_SortCandidate(a, b, members)
-- TODO: INVESTIGATE, but HOW?
-- this is added to debug odd behaviour in molten core where crash occured (b was nil, crashed at priorize health)
-- OKAY: found out the cause, if there are two units with same name (pet. ie. Cat) and your own is set as puller,
-- this should be fixed in getCandidates but is UNTESTED, remove this when tested
-- Update 20.6.2006 - still bugs on VERY rare occasions!
if (detected_bug==false and (a==nil or b==nil)) then
printInfo("?? BUG IN SMARTASSIST:");
local cand,_ = SA_GetCandidates(false);
local old_state = SA_OPTIONS.Debug;
SA_OPTIONS.Debug = true;
SA_DebugCandidates(cand);
SA_OPTIONS.Debug = old_state;
detected_bug = true;
end
-- priority health always first
-- TODO: DOES NOT TAKE ACCOUNT THE MEMBERS > n DISABLING !!! <----------------xxxxxxxxxxxxxxxxxxxxx---------------xxxxxxxxxxxxxxxxxxxx------------xxxxxxxxxxxxxx
-- SHOULD DO NOW! TESTING!!! xxxxxxxxxxxxxxx
local priorize = SA_OPTIONS.PriorizeHealth;
if (members > SA_OPTIONS.DisableSliderValue and SA_OPTIONS.DisablePriorityHealth) then
priorize = false;
end
if (priorize) then
if (a.health < SA_OPTIONS.PriorizeHealthValue or b.health < SA_OPTIONS.PriorizeHealthValue)
then
return a.health < b.health;
end
end
-- depriorize players having passive target
if (SA_PASSIVECACHE[a.unitName] and not SA_PASSIVECACHE[b.unitName]) then return false; end;
if (SA_PASSIVECACHE[b.unitName] and not SA_PASSIVECACHE[a.unitName]) then return true; end;
-- our puller should be always top priority
if (a.unitName == SA_OPTIONS["puller"]) then return true; end
if (b.unitName == SA_OPTIONS["puller"]) then return false; end
-- priorize players whose target is marked
if (SA_MARKEDCACHE[a.unitName] and not SA_MARKEDCACHE[b.unitName]) then return true; end;
if (SA_MARKEDCACHE[b.unitName] and not SA_MARKEDCACHE[a.unitName]) then return false; end;
-- de-priorize pets
if (a.pet and not b.pet) then return false; end;
if (b.pet and not a.pet) then return true; end;
-- CT RaidAssist support, if we have main tanks set in CT RaidAssist prefer them
if (CT_RA_MainTanks ~= nil) then
local a_tank, b_tank = false;
for _,v in CT_RA_MainTanks do
if (a.unitName == v) then a_tank=true; end
if (b.unitName == v) then b_tank=true; end
end
-- if both are CTRA tanks, sort by priority (mt first!) -- TODO: perhaps put OT first ?
if (a_tank and b_tank) then
return SA_TableIndex(CT_RA_MainTanks, a.unitName) < SA_TableIndex(CT_RA_MainTanks, b.unitName);
end
if (a_tank) then return true; end
if (b_tank) then return false; end
end
-- if we have same class, sort secondary by health
-- TODO: add option to disable this and use alphabetic order instead (should keep the list more stable)
-- perhaps use alphabetic order when in groups larger than > n
-- 20.6.2006 - testing out disabling if larger than > n
if (a.class == b.class) then
if (priorize) then
return a.health < b.health;
else
return a.unitName < b.unitName;
end
end
-- and last, teh normal case where sorted by class
return SA_TableIndex(SA_OPTIONS.ClassOrder, a.class) < SA_TableIndex(SA_OPTIONS.ClassOrder, b.class);
end
------------------------------------------------------------------------------
-- Makes sure that the puller is in the party and if not clear / set to pet.
-- This gets called everytime assist is used
------------------------------------------------------------------------------
function SA_RefreshPuller(candidatelist)
-- check that current puller exists in party, if not clear
local candidates = SA_ConvertCandidateListToMap(candidatelist);
if (SA_OPTIONS["puller"]~=nil and candidates[SA_OPTIONS["puller"]]==nil) then
SA_Debug("Puller does not exist in candidates, clearing", 3);
SA_OPTIONS["puller"]=nil;
end
-- if there is no puller and we have pet, set it as one
if (SA_OPTIONS["puller"]==nil and UnitExists("pet") and not UnitIsDead("pet")) then
SA_Debug("no puller set, pet exists -> setting it as one", 3);
SA_OPTIONS["puller"] = UnitName("pet");
end
end
------------------------------------------------------------------------------------------
-- params:
-- allowNearest = allow fallback to target nearest
-- recursive = is this recursive call, if it is we cannot add outside targets to skip list
-- Todo: better way for recursive?
------------------------------------------------------------------------------------------
function FindTarget(allowNearest, recursive)
-- reset PREVIOUS_FALLBACK, store real value in local variable. Easier this way than to handle all returns ...
local previous_fallback = PREVIOUS_FALLBACK;
PREVIOUS_FALLBACK = false;
local candidates, members = SA_GetCandidates(false);
-- reset order if target is kept over 3s
if (time() - PREVIOUS_ASSIST_TIME > 3 and SA_OPTIONS.PauseResetsOrder) then
SA_Debug("over 3s since last assist, reseting PREVIOUS_ASSISTS", 2);
PREVIOUS_ASSISTS = {};
PREVIOUS_ASSIST_TIME = time();
end
-- allow targeting nearest attacking again
if (time() - PREVIOUS_NEAREST_TIME > 5) then
SA_Debug("over 5s since last assist, reseting PREVIOUS_NEAREST", 2);
PREVIOUS_NEAREST = false;
PREVIOUS_NEAREST_TIME = time();
end
if (SA_OPTIONS.Filter) then
SA_Debug("filtering with "..tostring(SA_OPTIONS.Filter), 2);
candidates = SA_FilterCandidates(candidates, false);
end
-- filter out candidates out of range if assisting only members nearby
if (SA_OPTIONS.AssistOnlyNearest) then
candidates = SA_FilterCandidatesByDistance(candidates, false);
end
SA_RefreshPuller(candidates);
-- if we should not assist anyone from raid without puller, clear the candidates list
if (SA_OPTIONS.DisableAssistWithoutPuller and SA_OPTIONS["puller"]==nil) then
SA_Debug("DisableAssistWithoutPuller and no puller -> clearing candidates list", 2);
candidates = {};
end
-- try to target nearest before going to assist from raid, note that this is different from allowNearest
if (SA_OPTIONS.CheckNearest and not PREVIOUS_NEAREST and not recursive) then
--local pre_valid = isValidTarget("target");
-- okei, eli jos target nearest ei vaiha targettia niin se tuo myöhempi else palauttaa edellisen assistauksen targetin -> bug bug!
TARGET_CHANGED = false;
TargetNearestEnemy();
SA_Debug("check nearest got = "..tostring(UnitName("target")),1);
local valid = isValidTarget("target") and UnitAffectingCombat("target");
if (SA_OPTIONS.NearestMustBePvP) then
if (not UnitPlayerControlled("target")) then
valid = false;
end
end
if (SA_OPTIONS.NearestMustBeTargetting) then
--if (UnitIsUnit("targettarget", "player")) then
if (UnitName("targettarget") ~= UnitName("player")) then
valid = false;
end
end
if (valid) then
SA_Debug("*** found good target from check nearest target="..tostring(UnitName("target")),1);
if (SA_OPTIONS.VerboseNearest) then
SA_Verbose("Targeted nearest", ALERT_COLOR);
end
PREVIOUS_NEAREST = true;
return;
else
if (TARGET_CHANGED) then
SA_Debug("invalid check nearest, restored target = "..tostring(UnitName("target")),1);
TargetLastTarget();
end
end
end
PREVIOUS_NEAREST = false;
-- store table size to variable because iterating it multiple times is no good
table.sort(candidates, function(a,b) return SA_SortCandidate(a,b,members) end);
for _,candidate in candidates do
-- this is ingenious loop which determines if current candidate target has been targetted on previous assists
-- not a idiot proof check since previous party members might have changed target since then, but works amazingly well
-- atleast preventing situation where multiple members have same target and you press assist key multiple times
-- 18.1.2006 - changed to show already targetted msg to test if its futile
-- 21.1.2006 - this is triggered _multiple_ times per assist on some occasions, seems to be when there is only one target..
local previously_targetted = false;
for assisted,_ in PREVIOUS_ASSISTS do
if (UnitExists(assisted)) then
if (UnitIsUnit(candidate.target, assisted.."target")) then
--SA_Debug("** already targetted once "..tostring(UnitName(assisted)));
previously_targetted = true;
break;
end
end
end
-- if current target is same as our candidate, consider it "assisted" (in other words: skip it)
-- added 24.1.2006 - this should make cycling trough enemies work more smoothly
if (UnitExists("target") and UnitIsUnit(candidate.target, "target")) then
SA_Debug(tostring(candidate.unitId).." has same target as we ("..tostring(UnitName("target")).."), consider this unit as assisted", 3);
PREVIOUS_ASSISTS[candidate.unitId] = true;
end
-- test each candidate, skips previously assisted UNLESS it has health below critical value
local priority_health = candidate.health < SA_OPTIONS.PriorizeHealthValue and SA_OPTIONS.PriorizeHealth;
if (members > SA_OPTIONS.DisableSliderValue and SA_OPTIONS.DisablePriorityHealth) then
priority_health = false;
end
if ( (PREVIOUS_ASSISTS[candidate.unitId]==nil or priority_health) and (not previously_targetted) )
then
-- test if candidate (partyN, pet, raidN etc) has valid target
if (UnitCanAssist("player", candidate.unitId) and isValidTarget(candidate.target))
then
if (SA_OPTIONS.VerboseAssist) then
if (priority_health) then
SA_Verbose("Priority assisting "..candidate.unitName.." ("..candidate.health.."%)", COLOR_ALERT);
else
SA_Verbose("Assisting "..candidate.unitName);
end
end
SA_Debug("Found a good target from "..candidate.unitName.." ("..candidate.unitId..")", 3);
AssistUnit(candidate["unitId"]);
PREVIOUS_ASSISTS[candidate.unitId] = true;
return;
end
else
SA_Debug("** Skipping "..candidate.unitName.." ("..candidate.unitId..")", 4);
end
end
-- if we have skiplist, now is good time to clear it and try again with all members, recursive call is only made once
if (SA_TableSize(PREVIOUS_ASSISTS)>0 and not recursive) then
SA_Debug("** Unable to assist anyone but we have skiplist ("..SA_TableSize(PREVIOUS_ASSISTS)..") -> clearing it and trying again..", 3);
PREVIOUS_ASSISTS = {};
return FindTarget(allowNearest, true);
end
-- we might had good target already when assist was used, if there are no other targets available we must exit now
-- falling back to target nearest in that case would be idiotic
-- 29.7.2006 - do not abort on good target if previously acquired target using fallback to nearest,
-- this allows toggling targets using smartassist key like TAB key.
if (isValidTarget("target") and not previous_fallback) then
SA_Debug("had already good target", 3);
return;
end
if (SA_OPTIONS.FallbackTargetNearest and allowNearest) then
if (SA_OPTIONS.DisableTargetNearest and members > SA_OPTIONS.DisableSliderValue) then
if (SA_OPTIONS.VerboseUnableToAssist) then
printInfo("Unable to assist anyone. Targetting nearest is suspended in groups this large.");
end
return;
end
if (SA_OPTIONS.VerboseUnableToAssist) then
printInfo("Unable to assist anyone. Trying to target nearest enemy.");
end
TargetNearestEnemy();
if (not isValidTarget("target")) then
ClearTarget();
else
PREVIOUS_FALLBACK = true;
SA_Debug("fallback to target nearest found good target", 2);
end
end
end