vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 -- AUTHOR: Paranoidi
2  
3 -- $LastChangedDate: 2006-08-14 18:34:44 +0300 (Mon, 14 Aug 2006) $
4 -- $LastChangedBy: marko $
5 -- $Rev: 247 $
6  
7 --[[
8  
9 local px,py = GetPlayerMapPosition("player");
10 local tx,ty = GetPlayerMapPosition("raid"..idx);
11 local xdist = tx-px;
12 local ydist = ty-py;
13 local dist = sqrt(math.pow(xdist,2)+math.pow(ydist,2));
14  
15 http://intern.darklegion.de/interface/profiler.jpg
16  
17 testaa profileria?
18  
19 BUG: jonku pelaajan targettaaminen ei nollaa järjestystä (kuten tyhjän valinta) !
20  
21 --]]
22  
23  
24 -- integration to myAddons
25 SmartAssistDetails = {
26 name = "SmartAssist",
27 description = "SmartAssist is an addon which improves default assisting system in groups.",
28 version = "1.2.9",
29 releaseDate = string.sub("$LastChangedDate: 2006-08-14 18:34:44 +0300 (Mon, 14 Aug 2006) $", 18, 28),
30 author = "paranoidi",
31 email = "paranoidi@gmx.net",
32 category = MYADDONS_CATEGORY_COMBAT,
33 frame = "SmartAssistFrame",
34 optionsframe = "SAOptionsFrame"
35 };
36  
37 -- options
38 SA_OPTIONS_WRAPPER = {};
39 SA_OPTIONS = nil;
40  
41 SA_MARKEDCACHE = {};
42 SA_PASSIVECACHE = {};
43 ASSIST_EMOTES = {};
44 PREVIOUS_ASSISTS = {};
45 PREVIOUS_NEAREST = false;
46 PREVIOUS_FALLBACK = false;
47 PREVIOUS_ASSIST_TIME = 0;
48 PREVIOUS_NEAREST_TIME = 0;
49 TARGET_CHANGED = false;
50  
51 SMARTASSIST_CORE_REV = tonumber(string.sub("$Rev: 247 $", 7, -3));
52 BINDING_HEADER_SMARTASSIST = "SmartAssist";
53 BINDING_NAME_SASET = "Set puller";
54 BINDING_NAME_SAASSIST = "Assist";
55  
56 SA_RUNNING = false; -- used to disable sounds and detect target changes
57 PASSIVE_WARN_FLAG = false; -- is passive warning visible
58  
59 COLOR_DEFAULT = {r=0, g=0.8, b=1};
60 COLOR_ALERT = {r=0.9, g=0, b=0};
61  
62 StaticPopupDialogs["SA_NO_BINDINGS"] = {
63 text = TEXT("You must bind SmartAssist key before being able to use this addon!"),
64 button1 = TEXT(ACCEPT),
65 OnAccept = function()
66 printInfo("Tip: Press ESC key and go to 'bind keys' options. SmartAssist keys should be there at somewhere near bottom of the list.");
67 end,
68 timeout = 0,
69 whileDead = 1,
70 };
71  
72 StaticPopupDialogs["SA_UPGRADE_RESETPOS"] = {
73 text = TEXT("SmartAssist will need to reset frame positons due changes in the way scaling is used."),
74 button1 = TEXT(ACCEPT),
75 OnAccept = function()
76 SA_ResetFramePos();
77 end,
78 timeout = 0,
79 whileDead = 1,
80 };
81  
82 StaticPopupDialogs["SA_AUTOCONF"] = {
83 text = TEXT("Auto configure Smart Actions?"),
84 button1 = TEXT(ACCEPT),
85 button2 = TEXT(CANCEL),
86 OnAccept = function()
87 SA_AutoConfigureSmartActions();
88 printInfo("You should see SmartAssist options and verify detected settings. Use command /sa to access configuration.");
89 end,
90 timeout = 0,
91 whileDead = 1,
92 };
93  
94 StaticPopupDialogs["SA_DISABLE_ATTACK"] = {
95 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) ?"),
96 button1 = TEXT(YES),
97 button2 = TEXT(NO),
98 OnAccept = function()
99 SetCVar("assistAttack", "0");
100 end,
101 OnCancel = function()
102 SA_OPTIONS.AutoAttackWhineIgnored = true;
103 end,
104 timeout = 0,
105 whileDead = 1,
106 };
107  
108 function SA_OnLoad()
109 SLASH_SMARTASSIST1 = "/smartassist";
110 SLASH_SMARTASSIST2 = "/sa";
111 SlashCmdList["SMARTASSIST"] = function(msg)
112 SA_Command(msg);
113 end
114 end
115  
116 function SA_InitOption(option, value)
117 if (SA_OPTIONS[option]==nil) then SA_OPTIONS[option]=value; end;
118 end
119  
120 ------------------------------------------------------------------------------
121 -- event: variables loaded
122 ------------------------------------------------------------------------------
123  
124 function SA_VariablesLoaded()
125 -- Register the addon in myAddOns
126 if(myAddOnsFrame_Register) then
127 myAddOnsFrame_Register(SmartAssistDetails);
128 end
129 printInfo("SmartAssist "..SmartAssistDetails.version.." loaded.")
130  
131 -- add item to every possible context menu
132 table.insert(UnitPopupMenus["PARTY"], "PULLER");
133 table.insert(UnitPopupMenus["PLAYER"], "PULLER");
134 table.insert(UnitPopupMenus["RAID"], "PULLER");
135  
136 UnitPopupButtons["PULLER"] = { text = TEXT("Set as puller"), dist = 0 };
137  
138 SA_Init();
139 end
140  
141  
142 function SA_Init()
143  
144 -- load realm & char specific configs
145 local id = SA_GetAccountID();
146 if (SA_OPTIONS_WRAPPER[id]==nil) then
147 printInfo("SmartAssist created new default settings for "..id);
148 SA_OPTIONS_WRAPPER[id] = {};
149 end
150 SA_OPTIONS = SA_OPTIONS_WRAPPER[id];
151  
152 -- set default values to option variables if they do not exist
153  
154 -- before rev 66 we had no stored rev number so we need to make sure it's in such cases atleast zero
155 SA_InitOption("Rev", 0);
156  
157 -- special cases where we need to reset variables when upgrading the addon from older version
158 if (SA_OPTIONS.Rev<240 and SA_OPTIONS.Rev>0) then
159 StaticPopup_Show("SA_UPGRADE_RESETPOS");
160 end
161  
162 -- basic options
163 SA_InitOption("VisualWarning", true);
164 SA_InitOption("AssistOnEmote", true);
165 SA_InitOption("PriorizeHealth", true);
166 SA_InitOption("PriorizeHealthValue", 35);
167 SA_InitOption("FallbackTargetNearest", true);
168 SA_InitOption("CheckNearest", false);
169 SA_InitOption("NearestMustBePvP", false);
170 SA_InitOption("NearestMustBeTargetting", true);
171 SA_InitOption("AssistOnlyNearest", false);
172 -- available assist list
173 SA_InitOption("ShowAvailable", true);
174 SA_InitOption("PreserveOrder", false);
175 SA_InitOption("OutOfCombat", false);
176 SA_InitOption("TankMode", false); -- todo: class based defaults
177 SA_InitOption("ClassIconMode", 1);
178 SA_InitOption("TargetIconMode", 3);
179 SA_InitOption("HuntersMarkIconMode", 1);
180 SA_InitOption("AddMyTarget", false);
181 SA_InitOption("ListWidth", 150);
182 SA_InitOption("ListScale", 1.0);
183 SA_InitOption("ListOOCAlpha", 0.4);
184 SA_InitOption("ListSpacing", -5);
185 SA_InitOption("ListHorizontal", false);
186 SA_InitOption("ListTwoRow", false);
187 SA_InitOption("LockList", false);
188 SA_InitOption("HideTBY", false);
189 SA_InitOption("HideTitle", false);
190 SA_InitOption("AudioWarning", true); -- todo: class based defaults
191 SA_InitOption("LostAudioWarning", false); -- todo: class based defaults
192 SA_InitOption("VerboseAcquiredAggro", true);
193 SA_InitOption("VerboseLostAggro", false);
194 SA_InitOption("TextAlpha", 0.9);
195 SA_InitOption("IntelligentSplit", false);
196 -- advanced
197 SA_InitOption("DisableSliderValue", 15);
198 SA_InitOption("DisableTargetNearest", true);
199 SA_InitOption("DisablePriorityHealth", true);
200 SA_InitOption("AssistKeyMode", 1);
201 SA_InitOption("DisableAutoCastKeyMode", 2);
202 SA_InitOption("VerboseAssist", true);
203 SA_InitOption("VerboseIncoming", false);
204 SA_InitOption("VerboseNearest", true);
205 SA_InitOption("VerboseUnableToAssist", false);
206 SA_InitOption("DisableAssistWithoutPuller", false);
207 SA_InitOption("PauseResetsOrder", false);
208 -- smart actions
209 SA_InitOption("AssistSpells", {});
210 SA_InitOption("TriggerAssist", true);
211 -- geek (not in options)
212 SA_InitOption("SoundLoseAggro", "Sound\\Spells\\EyeOfKilroggDeath.wav");
213 SA_InitOption("SoundIncomingWoldBoss", "Sound\\Spells\\PVPFlagTaken.wav");
214 SA_InitOption("SoundGainAggro", "AuctionWindowClose");
215 -- development
216 SA_InitOption("DebugLevel", 0);
217  
218 -- display auto configuration dialog on first run
219 SA_InitOption("AutoConfiguredV2", false);
220 if (not SA_OPTIONS.AutoConfiguredV2) then
221 SA_OPTIONS.AutoConfiguredV2 = true;
222 StaticPopup_Show("SA_AUTOCONF");
223 end
224  
225 -- init auto cast on assist defaults for hunters
226 if (SA_OPTIONS["AutoAssist"]==nil and UnitClass("player") == CLASSNAME_HUNTER) then
227 SA_OPTIONS.AutoAssist = true;
228 SA_OPTIONS.FallbackOnBusy = true;
229 SA_OPTIONS.AutoAssistTexture = "Interface\\Icons\\INV_Weapon_Rifle_08";
230 SA_OPTIONS.AutoAssistName = SPELL_AUTOSHOT;
231 end
232 SA_InitOption("AutoAssist", false);
233 SA_InitOption("AutoPetAttack", false);
234 SA_InitOption("AutoPetAttackBusy", false);
235  
236 -- check if should display 'attack on assist' disable dialog if not ignored
237 SA_InitOption("AutoAttackWhineIgnored", false);
238 SA_CheckForAssistAttack();
239  
240 -- prefered assitance order (just a guess, got better one?)
241 SA_InitOption("ClassOrder",{ CLASSNAME_WARRIOR, CLASSNAME_HUNTER,
242 CLASSNAME_ROGUE, CLASSNAME_PALADIN,
243 CLASSNAME_DRUID, CLASSNAME_SHAMAN, CLASSNAME_MAGE,
244 CLASSNAME_WARLOCK, CLASSNAME_PRIEST } );
245  
246 -- store revision number to configuration, this can be used to detect some special cases where we need to reset
247 -- exsisting options (ie. bug in assistance order list) in new releases
248 SA_OPTIONS.Rev = SMARTASSIST_CORE_REV;
249  
250 ASSIST_EMOTES = { EMOTE_EVERYONE_ATTACK, EMOTE_ATTACK, EMOTE_HELP };
251  
252 if (SA_OPTIONS.ShowAvailable) then
253 SAListFrame:Show();
254 else
255 SAListFrame:Hide();
256 end
257 end
258  
259 -- check if assist attack is enabled
260 function SA_CheckForAssistAttack()
261 if (not SA_OPTIONS.AutoAttackWhineIgnored) then
262 if (GetCVar("assistAttack")~="0" and SA_OPTIONS.AutoAssist and SA_OPTIONS.AutoAssistName) then
263 StaticPopup_Show("SA_DISABLE_ATTACK");
264 end
265 end
266 end
267  
268 -------------------------------------------------------------------------------
269 -- WARNING ICON EVENTS
270 -------------------------------------------------------------------------------
271  
272 SA_WARN_SPEED = 0.07;
273 SA_WARN_REFRESH = SA_WARN_SPEED;
274 SA_WARN_ALPHA = 0.4;
275 SA_WARN_ALPHA_UP = true;
276  
277 function SA_WarnOnUpdate(elapsed)
278 if (not PASSIVE_WARN_FLAG) then return; end;
279 SA_WARN_REFRESH = SA_WARN_REFRESH - elapsed;
280 if (SA_WARN_REFRESH > 0) then
281 return;
282 end
283 SA_WARN_REFRESH = SA_WARN_SPEED;
284  
285 -- if target target is != nil (attacking someone) then REMOVE the icon
286 if (UnitName("targettarget")~=nil) then
287 SA_Debug("target target != nil .. remove the warning", 1);
288 SA_ResetWarning();
289 SA_AggroedTargetMsg();
290 return;
291 end
292  
293 if (SA_WARN_ALPHA_UP) then
294 SA_WARN_ALPHA = SA_WARN_ALPHA + 0.1;
295 else
296 SA_WARN_ALPHA = SA_WARN_ALPHA - 0.1;
297 end
298 if (SA_WARN_ALPHA<0.4) then
299 SA_WARN_ALPHA_UP = true;
300 SA_WARN_ALPHA = 0.5;
301 end
302 if (SA_WARN_ALPHA>1.0) then
303 SA_WARN_ALPHA_UP = false;
304 SA_WARN_ALPHA = 0.9;
305 end
306 SAWarningFrame:SetAlpha(SA_WARN_ALPHA);
307 end
308  
309 -------------------------------------------------------------------------------
310 -- handles smart assist slash commands
311 -------------------------------------------------------------------------------
312  
313 function SA_Command(msg)
314 local _,_,value = string.find(msg, "debug (%d)");
315 if (value) then
316 SA_OPTIONS.DebugLevel = tonumber(value);
317 printInfo("Setting debug level to "..tostring(SA_OPTIONS.DebugLevel));
318 elseif (msg=="available") then
319 SA_ToggleAvailable();
320 elseif (msg=="assist") then
321 DoSmartAssist();
322 elseif (msg=="reset" or msg=="reset all") then
323 -- resets frame positions
324 SA_ResetFramePos();
325  
326 -- clears this character settings and initializes defaults
327 if (msg=="reset all") then
328 printInfo("Reseting SmartAssist settings for all characters");
329 SA_OPTIONS_WRAPPER = {};
330 else
331 printInfo("Reseting SmartAssist settings for this character");
332 local id = SA_GetAccountID();
333 SA_OPTIONS_WRAPPER[id] = {};
334 end
335 SA_Init();
336 elseif (msg=="rev" or msg=="ver") then
337 if (SMARTASSIST_REV) then
338 printInfo("SmartAssist revision "..SMARTASSIST_REV);
339 else
340 printInfo("Unofficial or broken build! Revision number missing! Core rev "..SA_OPTIONS.Rev);
341 end
342 elseif (msg=="dump") then
343 if (not DevTools_Dump) then
344 printInfo("Requires devtools");
345 else
346 printInfo("Dumping internal variables:");
347 printInfo("PREVIOUS_ASSISTS: ");
348 DevTools_Dump(PREVIOUS_ASSISTS);
349 printInfo("PREVIOUS_NEAREST: "..tostring(PREVIOUS_NEAREST));
350 printInfo("TARGET_CHANGED: "..tostring(TARGET_CHANGED));
351 printInfo("SA_RUNNING: "..tostring(SA_RUNNING));
352 end
353 else
354 printInfo("Available parameters:");
355 printInfo("ver - Display addon version information.");
356 printInfo("assist - Execute SmartAssist for macros. Or call DoSmartAssist()");
357 printInfo("reset - Reset character settings and frame positions.");
358 printInfo("reset all - Reset all characters settings and frame positions.");
359 SA_ShowOptions();
360 end
361 end
362  
363 function SA_ResetFramePos()
364 SAListFrame:ClearAllPoints();
365 SAListFrame:SetPoint("TOP", "UIParent", "TOP", 0, -20);
366 SAListFrame:StopMovingOrSizing();
367 SAWarningFrame:ClearAllPoints();
368 SAWarningFrame:SetPoint("TOP", "UIParent", "TOP", 5, -25);
369 SAWarningFrame:StopMovingOrSizing();
370 SAOptionsFrame:ClearAllPoints();
371 SAOptionsFrame:SetPoint("CENTER", "UIParent", "CENTER", 0, 0);
372 SAOptionsFrame:StopMovingOrSizing();
373 end
374  
375 function SA_ShowOptions()
376 -- open config, or warn about key bindings
377 local key1, key2 = GetBindingKey("SAASSIST");
378 if (key1==nil and key2==nil) then
379 StaticPopup_Show("SA_NO_BINDINGS");
380 else
381 SAOptionsFrame:Show();
382 end
383 end
384  
385 -------------------------------------------------------------------------------
386 -- SMART ACTIONS
387 -------------------------------------------------------------------------------
388  
389 local old_UseAction = UseAction;
390 function UseAction(m1, m2, m3)
391  
392 local name = SA_GetSlotName(m1);
393 local assistAction = false;
394 if (SA_TableIndex(SA_OPTIONS.AssistSpells, name) ~= -1) then
395 assistAction = true;
396 end
397  
398 -- no target selected and attack action
399 -- treat targetting player as no target
400 if (SA_OPTIONS.TriggerAssist and (UnitName("target")==nil or UnitIsUnit("target","player")) and assistAction) then
401 SA_Debug("no target, initiating assist by action!", 1);
402 FindTarget(false, false);
403 -- target is not player selected so don't attack unless target already in combat
404 if (not UnitAffectingCombat("target")) then
405 SA_Debug("not in combat, aborting action", 1);
406 SA_ShowWarning();
407 return;
408 end
409 end
410  
411 -- if trying to attack friendly unit, initiate smart action
412  
413 -- UnitIsFriend bugs when dueling people in your party
414 -- Added UnitPlayerControlled because on pve server as non flagged attacking the opposite faction initiated assist!
415 if (assistAction and not UnitCanAttack("target", "player") and not (UnitPlayerControlled("target") and UnitFactionGroup("player")~=UnitFactionGroup("target"))) then
416 SA_Debug("friendly target, assistAction = "..tostring(assistAction), 1);
417 if (UnitCanAttack("player", "targettarget")) then
418 SA_Debug("friendly target, assisting", 1);
419 AssistUnit("target");
420 else
421 -- supresses annoyance when attacking and ally has no target
422 if (assistAction) then
423 SA_Debug("aborting action, supressing annoyance", 1);
424 return;
425 end
426 end
427 end
428  
429 return old_UseAction(m1, m2, m3);
430 end
431  
432 -------------------------------------------------------------------------------
433 -- implements shift click assist
434 -- should work with ANYWHERE where you can select unit
435 -------------------------------------------------------------------------------
436  
437 local SA_PREV_ASSIST_TIME = 0;
438 function SA_PlayerTargetChanged()
439 TARGET_CHANGED = true;
440  
441 -- target lost
442 if (not UnitExists("target")) then
443 SA_ResetWarning();
444 if (not SA_RUNNING) then
445 PREVIOUS_ASSISTS = {};
446 PREVIOUS_NEAREST = false;
447 end
448 SA_List_Refresh();
449 return; -- fixes issue when we have target already (fires two events, on lost and on gain)
450 end
451  
452 -- asisst by unit selection & modifier key
453 if ( (IsShiftKeyDown() and SA_OPTIONS.AssistKeyMode==1) or
454 (IsControlKeyDown() and SA_OPTIONS.AssistKeyMode==2) or
455 (IsAltKeyDown() and SA_OPTIONS.AssistKeyMode==3) )
456 then
457 -- this time check (hoax) removes looping, when selecting player that has friendly taget. Without this
458 -- it goes there and starts to assist him
459 -- todo: this can be done better .. if we check what's the target!
460  
461 local now = time();
462 local dif = now - SA_PREV_ASSIST_TIME;
463 if (dif == 0) then return; end
464 SA_PREV_ASSIST_TIME = now;
465  
466 if (UnitCanAssist("player", "target") and
467 UnitCanAttack("player", "targettarget"))
468 then
469 SAMsgFrame:AddMessage("Assisting "..UnitName("target"), 0.0, 0.8, 0.0, 1, 2.5);
470 AssistUnit("target");
471 SA_PostAssist();
472 else
473 if (UnitExists("target") and not UnitIsDead("target")) then
474 UIErrorsFrame:AddMessage("Unable to assist", 1.0, 0, 0, 1.0, 1.5);
475 end
476 end
477 end
478  
479 SA_List_Refresh();
480 end
481  
482 ------------------------------------------------------------------------------
483 -- credits to popup assist author, refactored a bit tough :)
484 ------------------------------------------------------------------------------
485  
486 local old_UnitPopup_OnClick = UnitPopup_OnClick;
487 function UnitPopup_OnClick(index)
488 local dropdownFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
489 local unit = dropdownFrame.unit;
490 if ( this.value == "PULLER" ) then
491 SA_SetPuller(unit);
492 end
493 return old_UnitPopup_OnClick(index);
494 end
495  
496 ------------------------------------------------------------------------------
497 -- param: unit - ID to be set as puller
498 -- set unit as puller
499 ------------------------------------------------------------------------------
500  
501 function SA_SetPuller(unit)
502 if (UnitIsFriend(unit, "player")) then
503 local candidates,_ = SA_GetCandidates(true);
504 -- check that current puller exists in party, if not clear
505 if (not candidates[UnitName(unit)]) then
506 SAMsgFrame:AddMessage("Puller must be in party / raid", 1.0, 1.0, 1.0, 1, 3);
507 else
508 SA_OPTIONS["puller"] = UnitName(unit);
509 SAMsgFrame:AddMessage("The assigned puller is now "..SA_OPTIONS["puller"], 1.0, 1.0, 1.0, 1, 1.5);
510 end
511 else
512 SA_OPTIONS["puller"] = nil;
513 SAMsgFrame:AddMessage("Assigned puller cleared", 1.0, 1.0, 1.0, 1, 1.5);
514 end
515 -- just incase that option window is visible, update the text
516 SA_Options_UpdatePullerText();
517 end
518  
519 ------------------------------------------------------------------------------
520 -- credits to mozz
521 -- this is used to disable some anying unit deselect sounds while assisting
522 -- (we have to clear the selected unit in some cases)
523 ------------------------------------------------------------------------------
524  
525 local old_PlaySound = PlaySound;
526 function PlaySound(name)
527 if (not SA_RUNNING) then
528 return old_PlaySound(name);
529 end
530 if (name ~= "INTERFACESOUND_LOSTTARGETUNIT" and name ~= "igCharacterNPCSelect" ) then
531 return old_PlaySound(name);
532 end
533 end
534  
535 ------------------------------------------------------------------------------
536 -- main method, this is called when smartassist does its thing
537 ------------------------------------------------------------------------------
538  
539 function DoSmartAssist()
540 SA_RUNNING = true;
541 -- if has friendly target, straight assist it
542 local f = true;
543 if (UnitExists("target")) then
544 if (not UnitCanAttack("target", "player") and not (UnitPlayerControlled("target") and UnitFactionGroup("player")~=UnitFactionGroup("target"))) then
545 if (SA_OPTIONS.VerboseAssist) then
546 SA_Verbose("Assisting selected player "..UnitName("target"));
547 end
548 AssistUnit("target");
549 f = false;
550 end
551 end
552 if (f) then
553 FindTarget(true, false);
554 end
555 SA_RUNNING = false;
556 SA_PostAssist();
557 SA_List_Refresh();
558 end
559  
560 ------------------------------------------------------------------------------
561 -- post assist logic (after target has been selected)
562 ------------------------------------------------------------------------------
563  
564 function SA_PostAssist()
565 -- auto-assist, disable if certain modifier key is down
566 if ( ((not IsShiftKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==1) or
567 (not IsControlKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==2) or
568 (not IsAltKeyDown() and SA_OPTIONS.DisableAutoCastKeyMode==3)) and
569 UnitExists("target") and
570 UnitAffectingCombat("target") )
571 then
572 if (SA_OPTIONS.AutoAssist and SA_OPTIONS.AutoAssistName)
573 then
574 SA_Debug("Target already in combat, assisting with "..SA_OPTIONS.AutoAssistName, 3);
575 CastSpellByName(SA_OPTIONS.AutoAssistName); -- casts highest level
576 end
577 if ((SA_OPTIONS.AutoPetAttack and UnitExists("pet") and not UnitIsDead("pet") and not UnitExists("pettarget")) or
578 (SA_OPTIONS.AutoPetAttack and UnitExists("pet") and not UnitIsDead("pet") and SA_OPTIONS.AutoPetAttackBusy))
579 then
580 SA_Debug("Attacking with pet", 3);
581 PetAttack();
582 end
583 end
584 -- visual alert, if needed
585 SA_ShowWarning();
586 end
587  
588 ------------------------------------------------------------------------------
589 -- receives all emote events, implements assist by emote
590 ------------------------------------------------------------------------------
591  
592 function SA_EmoteAssist(arg1)
593 if (not SA_OPTIONS.AssistOnEmote) then
594 return;
595 end
596 -- todo idea: hitting assist key should go back to previous target (if possible)
597 -- parse emotes
598 for k,v in ASSIST_EMOTES do
599 local sp, ep, name = string.find(arg1, "(%w+) "..v);
600 if (name ~= nil) then
601 SA_Debug("assist emote detected, name="..name, 1);
602 end
603 -- if name exists, we have a match
604 if name and name ~= EMOTE_OWN then
605 local candidates,_ = SA_GetCandidates(true);
606 if (candidates[name]~=nil) then
607 SAMsgFrame:AddMessage("Assisting "..name, 0.0, 0.8, 0.0, 1, 2.5);
608 AssistUnit(candidates[name].unitId);
609 return true;
610 end
611 SA_Debug("not assisting "..name..", not in party/raid", 1);
612 end
613 end
614 end
615  
616 function SA_UnitHealth(arg1)
617 -- flag is used to reduce overheading, this gets called a lot ..
618 if (not PASSIVE_WARN_FLAG) then
619 return;
620 end
621 if (arg1=="target") then
622 SA_ResetWarning();
623 -- if someone else attacked the target, show another text saying its ok to start blasting
624 -- this is not idiot proof check, seems to work fine tough
625 if (not UnitIsUnit("targettarget", "pet") and not UnitIsUnit("targettarget", "player")) then
626 SA_Debug("Someone else has attacked our target, show notification", 4);
627 SA_AggroedTargetMsg();
628 else
629 SA_Debug("player has most likelly attacked the target", 4);
630 -- todo: perhaps send pull message to party?
631 end
632 end
633 -- todo idea: we could watch other players health here and suggest assist
634 end
635  
636 -----------------------------------------------------------------
637 -- show warning if target is not in combat and feature is enabled
638 -----------------------------------------------------------------
639 function SA_ShowWarning()
640 if (not UnitAffectingCombat("target") and UnitExists("target") and
641 UnitName("targettarget")==nil and SA_OPTIONS.VisualWarning and
642 not UnitPlayerControlled("target"))
643 then
644 PASSIVE_WARN_FLAG = true;
645 SAWarningFrame:Show();
646 return true;
647 end
648 end
649  
650 function SA_AggroedTargetMsg()
651 -- the old msg was annoying as hell, replaced with SCT message, only if available
652 -- todo: add as option!
653 if (SCT_Display) then
654 SCT_Display(message, COLOR_DEFAULT);
655 end
656 end
657  
658 function SA_ResetWarning()
659 PASSIVE_WARN_FLAG = false;
660 SAWarningFrame:Hide();
661 end
662  
663 -------------------------------------------------------------
664 -- get distance unit is in, 0 if out of range
665 -- this funnly looking nesting makes minimal calls to api.
666 -- ie. No need to check for closer ranges if unit is not even
667 -- in 28yard range
668 -------------------------------------------------------------
669  
670 -- TODO: NOT USED ATM!
671  
672 function SA_GetDistance(unit)
673 if (CheckInteractDistance(unit, 4)) then
674 if (CheckInteractDistance(unit, 3)) then
675 if (CheckInteractDistance(unit, 2)) then
676 if (CheckInteractDistance(unit, 1)) then
677 return 1;
678 end
679 return 2;
680 end
681 return 3;
682 end
683 return 4;
684 end
685 return -1;
686 end
687  
688 -----------------------------------------------------------------------------------------
689 -- construct and return one candidate
690 -- if unit has invalid flag it should not be used because it does not contain all fields
691 -- foexample pets that are very far away do not have all fields present
692 -----------------------------------------------------------------------------------------
693  
694 function SA_GetCandidate(unit, i)
695 local candidate = {};
696 candidate["unitName"] = UnitName(unit..i);
697 candidate["unitId"] = unit..i;
698 candidate["target"] = unit..i.."target";
699 candidate["health"] = ceil( UnitHealth( unit..i ) / UnitHealthMax( unit..i ) * 100 );
700 candidate["class"] = UnitClass(unit..i);
701 candidate["dead"] = UnitIsDead(unit..i);
702 if (string.find(unit, "pet") ~= nil) then
703 candidate["pet"] = true;
704 end
705 if (candidate.unitName == nil) then
706 --SA_DebugCandidate(candidate);
707 candidate["invalid"] = true;
708 end
709 if (candidate.class == nil) then
710 --SA_DebugCandidate(candidate);
711 candidate["invalid"] = true;
712 end
713 if (candidate.health == nil) then
714 --printInfo("Problem with smartassist; unit health is unknown!");
715 candidate["invalid"] = true;
716 SA_DebugCandidate(candidate);
717 end
718 return candidate;
719 end
720  
721 function SA_GetPlayerAsCandidate()
722 local candidate = {};
723 candidate["unitName"] = UnitName("player");
724 candidate["unitId"] = "player";
725 candidate["target"] = "target";
726 candidate["health"] = ceil( UnitHealth( "player" ) / UnitHealthMax( "player" ) * 100 );
727 candidate["class"] = UnitClass("player");
728 candidate["dead"] = UnitIsDead("player");
729 return candidate;
730 end
731  
732 ------------------------------------------------------------------
733 -- return iteration info: is this party or raid, how many members
734 ------------------------------------------------------------------
735  
736 function SA_GetIterInfo()
737 local mode, members = nil;
738 if (GetNumRaidMembers()>0) then
739 mode = "raid";
740 members = GetNumRaidMembers();
741 elseif (GetNumPartyMembers()>0) then
742 mode = "party";
743 members = GetNumPartyMembers();
744 end
745 return mode, members;
746 end
747  
748 ------------------------------------------------------------------
749 -- converts candidate list to map version
750 -- this is used in some places where we need list AND map versions
751 ------------------------------------------------------------------
752  
753 function SA_ConvertCandidateListToMap(candidates)
754 local map = {};
755 for k,v in candidates do
756 map[v.unitName] = v;
757 end
758 return map;
759 end
760  
761 -------------------------------------------------------------
762 -- params: if map is true then as a map where key is unitName
763 -- if map is false then as list
764 -- return list of possible assistable units
765 -------------------------------------------------------------
766  
767 local UNIQUE_WARNED = false;
768 function SA_GetCandidates(map)
769 local candidates = {};
770 local mode, members = SA_GetIterInfo(); -- got stack overflow once here
771 local pullerAdded = false;
772  
773 -- for soloing and party, add our pet
774 if (UnitExists("pet")) then
775 local op = SA_GetCandidate("pet", "");
776 if (not op.invalid and not op.dead) then
777 -- fixes problem when there are multiple pets with same name
778 if (SA_OPTIONS["puller"] == op.unitName) then
779 pullerAdded = true;
780 end
781  
782 if (map) then
783 candidates[op.unitName] = op;
784 else
785 table.insert(candidates, op);
786 end
787 end
788 end
789  
790 -- if no party/raid, abort here
791 if (members==nil) then return candidates, 0; end;
792  
793 local myname = UnitName("player");
794  
795 for i = 1, members do
796 local candidate = SA_GetCandidate(mode, i);
797 -- do not add invalid, dead or myself
798 if (not candidate.invalid and not candidate.dead and candidate["unitName"] ~= myname) then
799 if (map) then
800 candidates[candidate.unitName] = candidate;
801 else
802 table.insert(candidates, candidate);
803 end
804  
805 -- add pet to list if has one
806 if (UnitExists(mode.."pet"..i)) then
807 local pcandidate = SA_GetCandidate(mode.."pet", i);
808 if (not pcandidate.invalid and not pcandidate.dead) then
809 -- hotfix for unique pet puller problem
810 -- players are unique by server so we don't have to worry about them
811 if (SA_OPTIONS["puller"] == pcandidate.unitName and pullerAdded) then
812 if (not UNIQUE_WARNED) then
813 printInfo("SMARTASSIST PROBLEM: Puller is not unique!");
814 UNIQUE_WARNED = true;
815 end
816 else
817 if (map) then
818 candidates[pcandidate.unitName] = pcandidate;
819 else
820 table.insert(candidates, pcandidate);
821 end
822  
823 -- if added puller unit, set flag that no other unit with same name can be added
824 if SA_OPTIONS["puller"] == pcandidate.unitName then
825 pullerAdded = true;
826 end
827 end
828 end
829 end
830 end
831 end
832  
833 return candidates, members;
834 end
835  
836 ------------------------------------------------------------------------------
837 -- if we have enabled filtering targets, remove those candidates who have
838 -- something else. It was much more convient to implement this way rather
839 -- than to modify get candidates and gazillion other places
840 ------------------------------------------------------------------------------
841  
842 function SA_FilterCandidates(candidates, map)
843 local filtered = {};
844 for key,candidate in candidates do
845 if (UnitExists(candidate.target) and string.find(string.lower(UnitName(candidate.target)), SA_OPTIONS.Filter)) then
846 if (map) then
847 filtered[key] = candidate;
848 else
849 table.insert(filtered, candidate);
850 end
851 end
852 end
853 return filtered;
854 end
855  
856 function SA_FilterCandidatesByDistance(candidates, map)
857 --debugprofilestart();
858  
859 local filtered = {};
860 for key,candidate in candidates do
861 if (CheckInteractDistance(candidate.unitId, 4)) then
862 if (map) then
863 filtered[key] = candidate;
864 else
865 table.insert(filtered, candidate);
866 end
867 end
868 end
869  
870 --SA_Debug("filtering by distance took "..debugprofilestop().." ms", 1);
871 return filtered;
872 end
873  
874 ------------------------------------------------------------------------------
875 -- sort method for candidates
876 ------------------------------------------------------------------------------
877  
878 local detected_bug = false;
879 function SA_SortCandidate(a, b, members)
880 -- TODO: INVESTIGATE, but HOW?
881 -- this is added to debug odd behaviour in molten core where crash occured (b was nil, crashed at priorize health)
882 -- OKAY: found out the cause, if there are two units with same name (pet. ie. Cat) and your own is set as puller,
883 -- this should be fixed in getCandidates but is UNTESTED, remove this when tested
884 -- Update 20.6.2006 - still bugs on VERY rare occasions!
885 if (detected_bug==false and (a==nil or b==nil)) then
886 printInfo("?? BUG IN SMARTASSIST:");
887 local cand,_ = SA_GetCandidates(false);
888 local old_state = SA_OPTIONS.Debug;
889 SA_OPTIONS.Debug = true;
890 SA_DebugCandidates(cand);
891 SA_OPTIONS.Debug = old_state;
892 detected_bug = true;
893 end
894  
895 -- priority health always first
896 -- TODO: DOES NOT TAKE ACCOUNT THE MEMBERS > n DISABLING !!! <----------------xxxxxxxxxxxxxxxxxxxxx---------------xxxxxxxxxxxxxxxxxxxx------------xxxxxxxxxxxxxx
897 -- SHOULD DO NOW! TESTING!!! xxxxxxxxxxxxxxx
898 local priorize = SA_OPTIONS.PriorizeHealth;
899 if (members > SA_OPTIONS.DisableSliderValue and SA_OPTIONS.DisablePriorityHealth) then
900 priorize = false;
901 end
902 if (priorize) then
903 if (a.health < SA_OPTIONS.PriorizeHealthValue or b.health < SA_OPTIONS.PriorizeHealthValue)
904 then
905 return a.health < b.health;
906 end
907 end
908  
909 -- depriorize players having passive target
910 if (SA_PASSIVECACHE[a.unitName] and not SA_PASSIVECACHE[b.unitName]) then return false; end;
911 if (SA_PASSIVECACHE[b.unitName] and not SA_PASSIVECACHE[a.unitName]) then return true; end;
912  
913 -- our puller should be always top priority
914 if (a.unitName == SA_OPTIONS["puller"]) then return true; end
915 if (b.unitName == SA_OPTIONS["puller"]) then return false; end
916  
917 -- priorize players whose target is marked
918 if (SA_MARKEDCACHE[a.unitName] and not SA_MARKEDCACHE[b.unitName]) then return true; end;
919 if (SA_MARKEDCACHE[b.unitName] and not SA_MARKEDCACHE[a.unitName]) then return false; end;
920  
921 -- de-priorize pets
922 if (a.pet and not b.pet) then return false; end;
923 if (b.pet and not a.pet) then return true; end;
924  
925 -- CT RaidAssist support, if we have main tanks set in CT RaidAssist prefer them
926 if (CT_RA_MainTanks ~= nil) then
927 local a_tank, b_tank = false;
928 for _,v in CT_RA_MainTanks do
929 if (a.unitName == v) then a_tank=true; end
930 if (b.unitName == v) then b_tank=true; end
931 end
932 -- if both are CTRA tanks, sort by priority (mt first!) -- TODO: perhaps put OT first ?
933 if (a_tank and b_tank) then
934 return SA_TableIndex(CT_RA_MainTanks, a.unitName) < SA_TableIndex(CT_RA_MainTanks, b.unitName);
935 end
936  
937 if (a_tank) then return true; end
938 if (b_tank) then return false; end
939 end
940  
941 -- if we have same class, sort secondary by health
942 -- TODO: add option to disable this and use alphabetic order instead (should keep the list more stable)
943 -- perhaps use alphabetic order when in groups larger than > n
944 -- 20.6.2006 - testing out disabling if larger than > n
945 if (a.class == b.class) then
946 if (priorize) then
947 return a.health < b.health;
948 else
949 return a.unitName < b.unitName;
950 end
951 end
952  
953 -- and last, teh normal case where sorted by class
954 return SA_TableIndex(SA_OPTIONS.ClassOrder, a.class) < SA_TableIndex(SA_OPTIONS.ClassOrder, b.class);
955 end
956  
957 ------------------------------------------------------------------------------
958 -- Makes sure that the puller is in the party and if not clear / set to pet.
959 -- This gets called everytime assist is used
960 ------------------------------------------------------------------------------
961  
962 function SA_RefreshPuller(candidatelist)
963 -- check that current puller exists in party, if not clear
964 local candidates = SA_ConvertCandidateListToMap(candidatelist);
965 if (SA_OPTIONS["puller"]~=nil and candidates[SA_OPTIONS["puller"]]==nil) then
966 SA_Debug("Puller does not exist in candidates, clearing", 3);
967 SA_OPTIONS["puller"]=nil;
968 end
969  
970 -- if there is no puller and we have pet, set it as one
971 if (SA_OPTIONS["puller"]==nil and UnitExists("pet") and not UnitIsDead("pet")) then
972 SA_Debug("no puller set, pet exists -> setting it as one", 3);
973 SA_OPTIONS["puller"] = UnitName("pet");
974 end
975 end
976  
977 ------------------------------------------------------------------------------------------
978 -- params:
979 -- allowNearest = allow fallback to target nearest
980 -- recursive = is this recursive call, if it is we cannot add outside targets to skip list
981 -- Todo: better way for recursive?
982 ------------------------------------------------------------------------------------------
983  
984 function FindTarget(allowNearest, recursive)
985 -- reset PREVIOUS_FALLBACK, store real value in local variable. Easier this way than to handle all returns ...
986 local previous_fallback = PREVIOUS_FALLBACK;
987 PREVIOUS_FALLBACK = false;
988  
989 local candidates, members = SA_GetCandidates(false);
990  
991 -- reset order if target is kept over 3s
992 if (time() - PREVIOUS_ASSIST_TIME > 3 and SA_OPTIONS.PauseResetsOrder) then
993 SA_Debug("over 3s since last assist, reseting PREVIOUS_ASSISTS", 2);
994 PREVIOUS_ASSISTS = {};
995 PREVIOUS_ASSIST_TIME = time();
996 end
997  
998 -- allow targeting nearest attacking again
999 if (time() - PREVIOUS_NEAREST_TIME > 5) then
1000 SA_Debug("over 5s since last assist, reseting PREVIOUS_NEAREST", 2);
1001 PREVIOUS_NEAREST = false;
1002 PREVIOUS_NEAREST_TIME = time();
1003 end
1004  
1005 if (SA_OPTIONS.Filter) then
1006 SA_Debug("filtering with "..tostring(SA_OPTIONS.Filter), 2);
1007 candidates = SA_FilterCandidates(candidates, false);
1008 end
1009  
1010 -- filter out candidates out of range if assisting only members nearby
1011 if (SA_OPTIONS.AssistOnlyNearest) then
1012 candidates = SA_FilterCandidatesByDistance(candidates, false);
1013 end
1014  
1015 SA_RefreshPuller(candidates);
1016  
1017 -- if we should not assist anyone from raid without puller, clear the candidates list
1018 if (SA_OPTIONS.DisableAssistWithoutPuller and SA_OPTIONS["puller"]==nil) then
1019 SA_Debug("DisableAssistWithoutPuller and no puller -> clearing candidates list", 2);
1020 candidates = {};
1021 end
1022  
1023 -- try to target nearest before going to assist from raid, note that this is different from allowNearest
1024 if (SA_OPTIONS.CheckNearest and not PREVIOUS_NEAREST and not recursive) then
1025 --local pre_valid = isValidTarget("target");
1026 -- okei, eli jos target nearest ei vaiha targettia niin se tuo myöhempi else palauttaa edellisen assistauksen targetin -> bug bug!
1027 TARGET_CHANGED = false;
1028 TargetNearestEnemy();
1029 SA_Debug("check nearest got = "..tostring(UnitName("target")),1);
1030 local valid = isValidTarget("target") and UnitAffectingCombat("target");
1031 if (SA_OPTIONS.NearestMustBePvP) then
1032 if (not UnitPlayerControlled("target")) then
1033 valid = false;
1034 end
1035 end
1036 if (SA_OPTIONS.NearestMustBeTargetting) then
1037 --if (UnitIsUnit("targettarget", "player")) then
1038 if (UnitName("targettarget") ~= UnitName("player")) then
1039 valid = false;
1040 end
1041 end
1042 if (valid) then
1043 SA_Debug("*** found good target from check nearest target="..tostring(UnitName("target")),1);
1044 if (SA_OPTIONS.VerboseNearest) then
1045 SA_Verbose("Targeted nearest", ALERT_COLOR);
1046 end
1047 PREVIOUS_NEAREST = true;
1048 return;
1049 else
1050 if (TARGET_CHANGED) then
1051 SA_Debug("invalid check nearest, restored target = "..tostring(UnitName("target")),1);
1052 TargetLastTarget();
1053 end
1054 end
1055 end
1056 PREVIOUS_NEAREST = false;
1057  
1058 -- store table size to variable because iterating it multiple times is no good
1059 table.sort(candidates, function(a,b) return SA_SortCandidate(a,b,members) end);
1060  
1061 for _,candidate in candidates do
1062 -- this is ingenious loop which determines if current candidate target has been targetted on previous assists
1063 -- not a idiot proof check since previous party members might have changed target since then, but works amazingly well
1064 -- atleast preventing situation where multiple members have same target and you press assist key multiple times
1065  
1066 -- 18.1.2006 - changed to show already targetted msg to test if its futile
1067 -- 21.1.2006 - this is triggered _multiple_ times per assist on some occasions, seems to be when there is only one target..
1068  
1069 local previously_targetted = false;
1070 for assisted,_ in PREVIOUS_ASSISTS do
1071 if (UnitExists(assisted)) then
1072 if (UnitIsUnit(candidate.target, assisted.."target")) then
1073 --SA_Debug("** already targetted once "..tostring(UnitName(assisted)));
1074 previously_targetted = true;
1075 break;
1076 end
1077 end
1078 end
1079  
1080 -- if current target is same as our candidate, consider it "assisted" (in other words: skip it)
1081 -- added 24.1.2006 - this should make cycling trough enemies work more smoothly
1082 if (UnitExists("target") and UnitIsUnit(candidate.target, "target")) then
1083 SA_Debug(tostring(candidate.unitId).." has same target as we ("..tostring(UnitName("target")).."), consider this unit as assisted", 3);
1084 PREVIOUS_ASSISTS[candidate.unitId] = true;
1085 end
1086  
1087 -- test each candidate, skips previously assisted UNLESS it has health below critical value
1088 local priority_health = candidate.health < SA_OPTIONS.PriorizeHealthValue and SA_OPTIONS.PriorizeHealth;
1089  
1090 if (members > SA_OPTIONS.DisableSliderValue and SA_OPTIONS.DisablePriorityHealth) then
1091 priority_health = false;
1092 end
1093  
1094 if ( (PREVIOUS_ASSISTS[candidate.unitId]==nil or priority_health) and (not previously_targetted) )
1095 then
1096 -- test if candidate (partyN, pet, raidN etc) has valid target
1097 if (UnitCanAssist("player", candidate.unitId) and isValidTarget(candidate.target))
1098 then
1099 if (SA_OPTIONS.VerboseAssist) then
1100 if (priority_health) then
1101 SA_Verbose("Priority assisting "..candidate.unitName.." ("..candidate.health.."%)", COLOR_ALERT);
1102 else
1103 SA_Verbose("Assisting "..candidate.unitName);
1104 end
1105 end
1106 SA_Debug("Found a good target from "..candidate.unitName.." ("..candidate.unitId..")", 3);
1107 AssistUnit(candidate["unitId"]);
1108 PREVIOUS_ASSISTS[candidate.unitId] = true;
1109 return;
1110 end
1111 else
1112 SA_Debug("** Skipping "..candidate.unitName.." ("..candidate.unitId..")", 4);
1113 end
1114 end
1115  
1116 -- if we have skiplist, now is good time to clear it and try again with all members, recursive call is only made once
1117 if (SA_TableSize(PREVIOUS_ASSISTS)>0 and not recursive) then
1118 SA_Debug("** Unable to assist anyone but we have skiplist ("..SA_TableSize(PREVIOUS_ASSISTS)..") -> clearing it and trying again..", 3);
1119 PREVIOUS_ASSISTS = {};
1120 return FindTarget(allowNearest, true);
1121 end
1122  
1123 -- we might had good target already when assist was used, if there are no other targets available we must exit now
1124 -- falling back to target nearest in that case would be idiotic
1125 -- 29.7.2006 - do not abort on good target if previously acquired target using fallback to nearest,
1126 -- this allows toggling targets using smartassist key like TAB key.
1127 if (isValidTarget("target") and not previous_fallback) then
1128 SA_Debug("had already good target", 3);
1129 return;
1130 end
1131  
1132 if (SA_OPTIONS.FallbackTargetNearest and allowNearest) then
1133 if (SA_OPTIONS.DisableTargetNearest and members > SA_OPTIONS.DisableSliderValue) then
1134 if (SA_OPTIONS.VerboseUnableToAssist) then
1135 printInfo("Unable to assist anyone. Targetting nearest is suspended in groups this large.");
1136 end
1137 return;
1138 end
1139 if (SA_OPTIONS.VerboseUnableToAssist) then
1140 printInfo("Unable to assist anyone. Trying to target nearest enemy.");
1141 end
1142 TargetNearestEnemy();
1143 if (not isValidTarget("target")) then
1144 ClearTarget();
1145 else
1146 PREVIOUS_FALLBACK = true;
1147 SA_Debug("fallback to target nearest found good target", 2);
1148 end
1149 end
1150 end