vanilla-wow-addons – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | SA_LIST_BOXES = {}; |
2 | |||
3 | -- TODO: incombat interval ja outofcombat interval! performance++ =) |
||
4 | |||
5 | SA_REFRESHRATE = 1.5; |
||
6 | |||
7 | SA_BASERATE = 0.6; |
||
8 | SA_INCREASE = 0.1; -- how much to decrease refresh rate per mob |
||
9 | SA_LASTREFRESH = SA_BASERATE; |
||
10 | |||
11 | SA_SEMIALERT = "|cffffa303"; |
||
12 | SA_ALERTCOL = "|cffff0000"; |
||
13 | SA_TANKCOL = "|cff00ff00"; |
||
14 | |||
15 | -- title modes |
||
16 | |||
17 | MODE_OOC = 1; |
||
18 | MODE_NORMAL = 2; |
||
19 | MODE_FILTERED = 3; |
||
20 | MODE_PAUSED = 4; |
||
21 | MODE_NEAREST = 5; |
||
22 | MODE_OVERLOADED = 10; |
||
23 | |||
24 | -- contains all data from last update |
||
25 | |||
26 | SA_PREV = { |
||
27 | ["aggro_count"] = 0, |
||
28 | ["mob_count"] = 0, |
||
29 | ["wb_count"] = 0, |
||
30 | ["title_mode"] = -1, |
||
31 | ["dirty"] = false |
||
32 | }; |
||
33 | |||
34 | -- class icon texture coordinates |
||
35 | |||
36 | SA_TEXTCOORDS={}; |
||
37 | SA_TEXTCOORDS["PRIEST"] = { left=0.50, right=0.75, top=0.25, bottom=0.50 }; |
||
38 | SA_TEXTCOORDS["MAGE"] = { left=0.25, right=0.50, top=0.00, bottom=0.25 }; |
||
39 | SA_TEXTCOORDS["WARLOCK"] = { left=0.75, right=1.00, top=0.25, bottom=0.50 }; |
||
40 | SA_TEXTCOORDS["DRUID"] = { left=0.75, right=1.00, top=0.00, bottom=0.25 }; |
||
41 | SA_TEXTCOORDS["HUNTER"] = { left=0.00, right=0.25, top=0.25, bottom=0.50 }; |
||
42 | SA_TEXTCOORDS["ROGUE"] = { left=0.50, right=0.75, top=0.00, bottom=0.25 }; |
||
43 | SA_TEXTCOORDS["WARRIOR"] = { left=0.00, right=0.25, top=0.00, bottom=0.25 }; |
||
44 | SA_TEXTCOORDS["SHAMAN"] = { left=0.25, right=0.50, top=0.25, bottom=0.50 }; |
||
45 | SA_TEXTCOORDS["PALADIN"] = { left=0.00, right=0.25, top=0.50, bottom=0.75 }; |
||
46 | |||
47 | -- predefined icon locations |
||
48 | |||
49 | SA_ICONPOS={n=8}; |
||
50 | SA_ICONPOS[1] = { name="Disabled", point="TOPLEFT", relativePoint="", x=-26, y=-28, width=25, height=25 }; |
||
51 | SA_ICONPOS[2] = { name="Left bottom", point="TOPLEFT", relativePoint="", x=-26, y=-28, width=25, height=25 }; |
||
52 | SA_ICONPOS[3] = { name="Left top", point="TOPLEFT", relativePoint="", x=-26, y=-3, width=25, height=25}; |
||
53 | SA_ICONPOS[4] = { name="Inside box", point="BOTTOMRIGHT", relativePoint="", x=0, y=0, width=25, height=25}; |
||
54 | SA_ICONPOS[5] = { name="Right bottom", point="TOPRIGHT", relativePoint="", x=26, y=-28, width=25, height=25 }; |
||
55 | SA_ICONPOS[6] = { name="Right top", point="TOPRIGHT", relativePoint="", x=26, y=-3, width=25, height=25}; |
||
56 | SA_ICONPOS[7] = { name="Left big", point="TOPLEFT", relativePoint="", x=-51, y=-3, width=50, height=50}; |
||
57 | SA_ICONPOS[8] = { name="Right big", point="TOPRIGHT", relativePoint="", x=51, y=-3, width=50, height=50}; |
||
58 | |||
59 | |||
60 | ------------------------------------------------------------------------------ |
||
61 | |||
62 | function SA_InitBar(bar, text) |
||
63 | bar:SetMinMaxValues(0, 100); |
||
64 | bar:SetValue(100); |
||
65 | text:SetHeight(12); |
||
66 | text:SetPoint("CENTER", bar:GetName(), "CENTER", 0, 0); |
||
67 | SetTextStatusBarText(bar, text); |
||
68 | ShowTextStatusBarText(bar); |
||
69 | end |
||
70 | |||
71 | function SA_List_OnLoad() |
||
72 | -- initialize boxes |
||
73 | for i=1, 10 do |
||
74 | local box = {}; |
||
75 | box["frame"] = getglobal("Target"..i); |
||
76 | box["mobText"] = getglobal("Target"..i.."MobText"); |
||
77 | box["targetText"] = getglobal("Target"..i.."TargetText"); |
||
78 | box["targetOf"] = getglobal("Target"..i.."TargetOf"); |
||
79 | box["mobBar"] = getglobal("Target"..i.."MobBar"); |
||
80 | box["targetBar"] = getglobal("Target"..i.."TargetBar"); |
||
81 | box["classIcon"] = getglobal("Target"..i.."ClassIcon"); |
||
82 | box["targetIcon"] = getglobal("Target"..i.."TargetIcon"); |
||
83 | box["huntersMarkIcon"] = getglobal("Target"..i.."HuntersMarkIcon"); |
||
84 | SA_LIST_BOXES[i] = box; |
||
85 | end |
||
86 | end |
||
87 | |||
88 | function SA_List_OnShow() |
||
89 | -- TODO: this should be done only if options altered from xml defaults |
||
90 | SA_List_UpdateAppearance(); |
||
91 | |||
92 | if (SA_OPTIONS.ListScale ~= 1.0) then |
||
93 | SAListFrameScaler:SetScale(SA_OPTIONS.ListScale); |
||
94 | end |
||
95 | |||
96 | -- set title button to correct mode |
||
97 | SA_List_SetTitleButton(MODE_OOC); |
||
98 | end |
||
99 | |||
100 | ------------------------------------------------------------------------------ |
||
101 | -- initialize list dropdown menu |
||
102 | ------------------------------------------------------------------------------ |
||
103 | |||
104 | function SA_List_FrameDropDown_OnLoad() |
||
105 | UIDropDownMenu_Initialize(this, SA_List_FrameDropDown_Initialize, "MENU"); |
||
106 | end |
||
107 | |||
108 | ------------------------------------------------------------------------------ |
||
109 | -- note: this is called always when dropdown is shown! |
||
110 | ------------------------------------------------------------------------------ |
||
111 | |||
112 | function SA_List_FrameDropDown_Initialize() |
||
113 | if (not SA_OPTIONS) then |
||
114 | -- options not available yet (onload event) -> abort |
||
115 | return; |
||
116 | end |
||
117 | |||
118 | item = {}; |
||
119 | item.text = "Filter targets"; |
||
120 | if (SA_OPTIONS.Filter) then |
||
121 | item.checked = 1; |
||
122 | end |
||
123 | item.func = SA_List_Menu_Filter; |
||
124 | UIDropDownMenu_AddButton(item); |
||
125 | |||
126 | item = {}; |
||
127 | item.text = "Assist with pet"; |
||
128 | if (SA_OPTIONS.AutoPetAttack) then |
||
129 | item.checked = 1; |
||
130 | end |
||
131 | item.keepShownOnClick = 1; |
||
132 | item.func = SA_List_Menu_AssistWithPet; |
||
133 | UIDropDownMenu_AddButton(item); |
||
134 | |||
135 | item = {}; |
||
136 | item.text = "Assist only players nearby (28 yards)"; |
||
137 | if (SA_OPTIONS.AssistOnlyNearest) then |
||
138 | item.checked = 1; |
||
139 | end |
||
140 | item.func = SA_List_Menu_AssistOnlyNearest; |
||
141 | UIDropDownMenu_AddButton(item); |
||
142 | |||
143 | item = {}; |
||
144 | item.text = "Display out of combat targets"; |
||
145 | item.checked = SA_OPTIONS.OutOfCombat; |
||
146 | item.func = SA_List_Menu_ShowOOC; |
||
147 | UIDropDownMenu_AddButton(item); |
||
148 | |||
149 | item = {}; |
||
150 | item.text = "Lock list position"; |
||
151 | item.func = SA_List_Menu_Lock; |
||
152 | item.checked = SA_OPTIONS.LockList; |
||
153 | UIDropDownMenu_AddButton(item); |
||
154 | |||
155 | item = {}; |
||
156 | item.text = "SmartAssist options"; |
||
157 | item.notCheckable = 1; |
||
158 | item.func = SA_ShowOptions; |
||
159 | UIDropDownMenu_AddButton(item); |
||
160 | end |
||
161 | |||
162 | function SA_List_Menu_Lock() |
||
163 | SA_ToggleOption("LockList"); |
||
164 | end |
||
165 | |||
166 | function SA_List_Menu_AssistWithPet() |
||
167 | SA_ToggleOption("AutoPetAttack"); |
||
168 | end |
||
169 | |||
170 | function SA_List_Menu_AssistOnlyNearest() |
||
171 | SA_ToggleOption("AssistOnlyNearest"); |
||
172 | if (SA_OPTIONS.AssistOnlyNearest) then |
||
173 | printInfo("Assisting only players nearby"); |
||
174 | end |
||
175 | end |
||
176 | |||
177 | function SA_List_Menu_ShowOOC() |
||
178 | SA_ToggleOption("OutOfCombat"); |
||
179 | end |
||
180 | |||
181 | function SA_List_Menu_Filter() |
||
182 | if (SA_OPTIONS.Filter == nil) then |
||
183 | SAFilterFrame:Show(); |
||
184 | else |
||
185 | SA_OPTIONS.Filter = nil; |
||
186 | printInfo("Filtering disabled"); |
||
187 | SA_List_SetTitleButton(MODE_OOC); |
||
188 | end; |
||
189 | end |
||
190 | |||
191 | function SA_List_FilterButtonOK_OnClick() |
||
192 | local text = SAFilterEditBox:GetText(); |
||
193 | if (string.len(text) > 0) then |
||
194 | SA_OPTIONS.Filter = string.lower(text); |
||
195 | 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."); |
||
196 | SA_List_SetTitleButton(MODE_FILTERED); |
||
197 | else |
||
198 | SA_OPTIONS.Filter = nil; |
||
199 | printInfo("Filtering disabled"); |
||
200 | SA_List_SetTitleButton(MODE_OOC); |
||
201 | end |
||
202 | SAFilterFrame:Hide(); |
||
203 | end |
||
204 | |||
205 | ------------------------------------------------------------------------------ |
||
206 | -- Title button clicked (show menu) or drag |
||
207 | -- kudos goes for DamageMeters |
||
208 | ------------------------------------------------------------------------------ |
||
209 | |||
210 | function SA_List_TitleButton_OnClick() |
||
211 | local button = arg1; |
||
212 | if ( button == "LeftButton" and not SA_OPTIONS.LockList) then |
||
213 | -- drag frame |
||
214 | if ( this:GetButtonState() == "PUSHED" ) then |
||
215 | SAListFrame:StopMovingOrSizing(); |
||
216 | else |
||
217 | SAListFrame:StartMoving(); |
||
218 | end |
||
219 | elseif ( button == "RightButton" ) then |
||
220 | -- show menu |
||
221 | ToggleDropDownMenu(1, nil, SAListFrameDropDown, "SAListFrameDropDown", -33, 25); |
||
222 | end |
||
223 | end |
||
224 | |||
225 | ------------------------------------------------------------------------------ |
||
226 | -- change outlook for the list depending options |
||
227 | ------------------------------------------------------------------------------ |
||
228 | |||
229 | function SA_List_HasSideIcons() |
||
230 | if (SA_OPTIONS.ClassIconMode==2 or SA_OPTIONS.ClassIconMode==3 or |
||
231 | SA_OPTIONS.TargetIconMode==2 or SA_OPTIONS.TargetIconMode==3 or |
||
232 | SA_OPTIONS.HuntersMarkIconMode==2 or SA_OPTIONS.HuntersMarkIconMode==3) |
||
233 | then |
||
234 | return true; |
||
235 | end |
||
236 | end |
||
237 | |||
238 | function SA_List_UpdateAppearance() |
||
239 | local width = SA_OPTIONS.ListWidth; |
||
240 | |||
241 | local x = 0; |
||
242 | local y = 0; |
||
243 | if (SA_OPTIONS.ListHorizontal) then |
||
244 | -- horizontal |
||
245 | local gap = 10; |
||
246 | if (SA_List_HasSideIcons()) then |
||
247 | gap = gap - 30; |
||
248 | end |
||
249 | if (SA_OPTIONS.ListSpacing > 0) then |
||
250 | x = width - gap + SA_OPTIONS.ListSpacing; |
||
251 | else |
||
252 | x = - (width - gap - SA_OPTIONS.ListSpacing); |
||
253 | end |
||
254 | else |
||
255 | -- vertical |
||
256 | local height = ceil(Target1:GetHeight()) - 8; |
||
257 | if (SA_OPTIONS.ListSpacing > 0) then |
||
258 | y = height + SA_OPTIONS.ListSpacing; |
||
259 | else |
||
260 | y = - (height-SA_OPTIONS.ListSpacing); |
||
261 | end |
||
262 | end |
||
263 | |||
264 | SAListFrame:SetWidth(width); |
||
265 | SAListTitleButton:SetWidth(width); |
||
266 | |||
267 | local anchor="TOPLEFT"; |
||
268 | for i,box in SA_LIST_BOXES do |
||
269 | if (i==1) then |
||
270 | -- first box is ancored to title |
||
271 | if (y<=0) then |
||
272 | box.frame:SetPoint(anchor, "SAListFrame", anchor, 0, -17); |
||
273 | else |
||
274 | box.frame:SetPoint(anchor, "SAListFrame", anchor, 0, 54); |
||
275 | end |
||
276 | elseif (i==6 and SA_OPTIONS.ListTwoRow) then |
||
277 | -- two rows |
||
278 | if (SA_OPTIONS.ListHorizontal) then |
||
279 | local gap = SA_OPTIONS.ListSpacing; |
||
280 | if (gap>0) then |
||
281 | gap = -gap; |
||
282 | end |
||
283 | gap = gap + 10; |
||
284 | box.frame:SetPoint(anchor, "Target1", "BOTTOMLEFT", 0, gap); |
||
285 | else |
||
286 | local gap = SA_OPTIONS.ListSpacing; |
||
287 | if (gap<0) then |
||
288 | gap = -gap; |
||
289 | end |
||
290 | gap = gap - 8; |
||
291 | -- if icons visible next to box |
||
292 | if (SA_List_HasSideIcons()) then |
||
293 | gap = gap + 30; |
||
294 | end |
||
295 | box.frame:ClearAllPoints(); |
||
296 | box.frame:SetPoint(anchor, "Target1", "TOPRIGHT", gap, 0); |
||
297 | end |
||
298 | else |
||
299 | -- rest are anchored to previous box |
||
300 | box.frame:ClearAllPoints(); |
||
301 | box.frame:SetPoint(anchor, "Target"..i-1, anchor, x, y); |
||
302 | end |
||
303 | |||
304 | box.frame:SetWidth(width); |
||
305 | -- scale inside elements width |
||
306 | box.mobBar:SetWidth(width-20); |
||
307 | box.targetBar:SetWidth(width-20); |
||
308 | box.mobText:SetWidth(width-30); |
||
309 | box.targetText:SetWidth(width-30); |
||
310 | |||
311 | -- update display targeted by stuff |
||
312 | box.targetOf:SetWidth(width-20); |
||
313 | |||
314 | -- set texts alpha |
||
315 | box.mobText:SetAlpha(SA_OPTIONS.TextAlpha); |
||
316 | box.targetText:SetAlpha(SA_OPTIONS.TextAlpha); |
||
317 | |||
318 | -- set icon positions |
||
319 | if (SA_OPTIONS.ClassIconMode>1) then |
||
320 | local icon = SA_ICONPOS[SA_OPTIONS.ClassIconMode]; |
||
321 | SA_List_UpdateIconAppearance(box.classIcon, i, icon); |
||
322 | else |
||
323 | box.classIcon:Hide(); |
||
324 | end |
||
325 | |||
326 | if (SA_OPTIONS.TargetIconMode>1) then |
||
327 | local icon = SA_ICONPOS[SA_OPTIONS.TargetIconMode]; |
||
328 | SA_List_UpdateIconAppearance(box.targetIcon, i, icon); |
||
329 | else |
||
330 | box.targetIcon:Hide(); |
||
331 | end |
||
332 | |||
333 | if (SA_OPTIONS.HuntersMarkIconMode>1) then |
||
334 | local icon = SA_ICONPOS[SA_OPTIONS.HuntersMarkIconMode]; |
||
335 | SA_List_UpdateIconAppearance(box.huntersMarkIcon, i, icon); |
||
336 | else |
||
337 | box.huntersMarkIcon:Hide(); |
||
338 | end |
||
339 | |||
340 | end |
||
341 | end |
||
342 | |||
343 | -- helper function, updates icon appearance |
||
344 | function SA_List_UpdateIconAppearance(frame, i, icon) |
||
345 | frame:ClearAllPoints(); |
||
346 | frame:SetPoint(icon.point, "Target"..i, icon.point, icon.x, icon.y); |
||
347 | frame:SetWidth(icon.width); |
||
348 | frame:SetHeight(icon.height); |
||
349 | frame:Show(); |
||
350 | end |
||
351 | |||
352 | ------------------------------------------------------------------------------ |
||
353 | -- Refresh the list if enough time has passed since last refresh |
||
354 | ------------------------------------------------------------------------------ |
||
355 | |||
356 | function SA_List_OnUpdate(elapsed) |
||
357 | SA_LASTREFRESH = SA_LASTREFRESH - elapsed; |
||
358 | if (SA_LASTREFRESH > 0) then |
||
359 | return; |
||
360 | end |
||
361 | SA_LASTREFRESH = SA_BASERATE + (SA_INCREASE * SA_PREV.mob_count); |
||
362 | SA_List_Update(); |
||
363 | end |
||
364 | |||
365 | local TARGET_CLICKED = time() |
||
366 | function SA_List_Target_OnClick(arg) |
||
367 | SA_Debug("TargetClick"); |
||
368 | TARGET_CLICKED = time(); |
||
369 | local target = arg.obj; |
||
370 | if (not target) then printInfo("SmartAssist: Unknown target?"); return; end; |
||
371 | -- if ctrl+alt is down paste all targetters names (except tanks) to chat |
||
372 | if (IsControlKeyDown() and IsAltKeyDown()) then |
||
373 | local names = ""; |
||
374 | local i = 0; |
||
375 | for _,c in target.players do |
||
376 | if (not SA_IsTank(c.unitName)) then |
||
377 | names = names..c.unitName..", "; |
||
378 | i = i + 1; |
||
379 | end; |
||
380 | end |
||
381 | if (i>0) then |
||
382 | names = string.sub(names, 0, -3); |
||
383 | ChatFrameEditBox:SetText(names.." "); |
||
384 | ChatFrameEditBox:Show(); |
||
385 | end |
||
386 | return; |
||
387 | end |
||
388 | |||
389 | -- todo: problem is that target.players[1].unitName may have changed target between updating the list --> click |
||
390 | -- however we could impove situation by assisting the player that has the MOST common target amon all targetters |
||
391 | -- ie. iterate all targetters and check if UnitIsUnit(unit, others) the player who has most highest count is |
||
392 | -- most likelly the correct one! That would however do 40*39 (1590) checks at worst! |
||
393 | -- additionally / alternatively we could check target name (if only one player for example) and disaply |
||
394 | -- error (+sound?) if it has been changed |
||
395 | |||
396 | -- initial implementation (just verboses when debug on) |
||
397 | --local counts = {}; |
||
398 | local hicount = 0; |
||
399 | local unitId = ""; |
||
400 | for _,player in target.players do |
||
401 | --counts[player.unitId] = SA_List_TargetShareCount(player, target.players); |
||
402 | local count = SA_List_TargetShareCount(player, target.players); |
||
403 | if (count > hicount) then |
||
404 | hicount = count; |
||
405 | unitId = player.unitId; |
||
406 | end |
||
407 | end |
||
408 | --for unitId,count in counts do |
||
409 | --SA_Debug(unitId.." shares target with "..tostring(count), 1); |
||
410 | --end |
||
411 | SA_Debug("Assisting "..unitId.." which has highest common count of "..hicount); |
||
412 | AssistUnit(unitId); |
||
413 | |||
414 | -- initiate assist |
||
415 | --SA_Debug("assisting "..target.players[1].unitName); |
||
416 | --AssistUnit(target.players[1].unitId); |
||
417 | |||
418 | SA_PostAssist(); |
||
419 | -- update the list immediattely |
||
420 | SA_PREV.title_mode = -1; -- resets title mode on next refresh |
||
421 | SA_List_Refresh(); |
||
422 | end |
||
423 | |||
424 | -- return number of players in list that have same target as player |
||
425 | function SA_List_TargetShareCount(player, list) |
||
426 | local count = 0; |
||
427 | for _,compare in list do |
||
428 | if (UnitIsUnit(player.unitId.."target", compare.unitId.."target")) then |
||
429 | count = count + 1; |
||
430 | end |
||
431 | end |
||
432 | return count; |
||
433 | end |
||
434 | |||
435 | function SA_List_Target_OnEnter(arg) |
||
436 | local text = SA_List_Target_GetTooltip(arg); |
||
437 | GameTooltip:SetOwner(arg, "ANCHOR_LEFT"); |
||
438 | GameTooltip:SetText(text,1,1,1,1,1); |
||
439 | |||
440 | -- prevent going to pausemode if target has just been clicked |
||
441 | if (time() - TARGET_CLICKED > 3) then |
||
442 | -- Add some "sleep" to refreshing the list. It's not good to update the list while user is trying to select something. |
||
443 | SA_LASTREFRESH = SA_BASERATE * 3; |
||
444 | SA_PREV.title_mode = -1; -- resets title mode on next refresh |
||
445 | SA_List_SetTitleButton(MODE_PAUSED); |
||
446 | end |
||
447 | end |
||
448 | |||
449 | function SA_List_Target_OnLeave(arg) |
||
450 | GameTooltip:Hide(); |
||
451 | end |
||
452 | |||
453 | function SA_List_Target_GetTooltip(arg) |
||
454 | local target = arg.obj; |
||
455 | if (not target) then return "Unknown?"; end; |
||
456 | local text = target.fullName.."\nTargetted by:\n"; |
||
457 | for k,v in target.players do |
||
458 | -- colorize text by class |
||
459 | local cv = RAID_CLASS_COLORS[string.upper(v.class)]; |
||
460 | local color=""; |
||
461 | if (not cv) then |
||
462 | color = "|cff888888"; |
||
463 | else |
||
464 | color = SA_ToTextCol(cv.r, cv.g, cv.b); |
||
465 | end |
||
466 | text = text .. color..v.unitName .. "|r" .. "\n"; |
||
467 | end |
||
468 | return text; |
||
469 | end |
||
470 | |||
471 | ------------------------------------------------------------------------------ |
||
472 | -- Request list to be refreshed immediattely |
||
473 | ------------------------------------------------------------------------------ |
||
474 | |||
475 | function SA_List_Refresh() |
||
476 | --SA_Debug("Requesting refresh now, target="..tostring(UnitName("target"))); |
||
477 | SA_LASTREFRESH = 0; |
||
478 | end |
||
479 | |||
480 | ------------------------------------------------------------------------------ |
||
481 | -- get puller from candidate list |
||
482 | ------------------------------------------------------------------------------ |
||
483 | |||
484 | function SA_GetPuller(candidates) |
||
485 | for _,v in candidates do |
||
486 | if (v.unitName == SA_OPTIONS.puller) then |
||
487 | return v; |
||
488 | end |
||
489 | end |
||
490 | end |
||
491 | |||
492 | ------------------------------------------------------------------------------ |
||
493 | -- check if candidates target is already targetted by someone |
||
494 | -- return the target if found and nil if not |
||
495 | ------------------------------------------------------------------------------ |
||
496 | |||
497 | function SA_GetExistingTarget(candidate, targets) |
||
498 | for _,target in targets do |
||
499 | if (UnitIsUnit(target.players[1].target, candidate.target)) then |
||
500 | return target; |
||
501 | end |
||
502 | end |
||
503 | return nil; |
||
504 | end |
||
505 | |||
506 | function SA_List_NameSplit(str) |
||
507 | local t = {n=0} |
||
508 | local function helper(word) table.insert(t, word) end |
||
509 | if not string.find(string.gsub(str, "%w+", helper), "%S") then return t end |
||
510 | end |
||
511 | |||
512 | ------------------------------------------------------------------------------ |
||
513 | -- construct target from candidate |
||
514 | ------------------------------------------------------------------------------ |
||
515 | |||
516 | function SA_GetTarget(candidate, pullerId) |
||
517 | target = {}; |
||
518 | target["players"] = { candidate }; |
||
519 | target["name"] = UnitName(candidate.target); |
||
520 | target["fullName"] = target.name; |
||
521 | -- intelligent name split |
||
522 | if (SA_OPTIONS.IntelligentSplit and target.name) then |
||
523 | local parts = SA_List_NameSplit(target.name); |
||
524 | if (parts) then |
||
525 | if (table.getn(parts)>2) then |
||
526 | if (parts[2]~="of") then -- splitting "shade of naxxramas" -> "of naxxramas" is stupid, dont split if second word is "of" |
||
527 | target.name = string.sub(target.name, string.len(parts[1])+1 ); |
||
528 | end |
||
529 | end |
||
530 | end |
||
531 | end |
||
532 | target["health"] = UnitHealth(candidate.target); |
||
533 | target["targetName"] = UnitName(candidate.target.."target"); |
||
534 | -- test fix for "shang's problem" |
||
535 | -- 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 .. |
||
536 | if (target.targetName=="Unknown Entity" or target.targetName=="Unknown") then |
||
537 | target.targetName=nil; |
||
538 | end |
||
539 | -- target.self gives us a reference to mob, example: party<n>target |
||
540 | target["self"] = candidate.target; |
||
541 | target["targetHealth"] = ceil( UnitHealth( candidate.target.."target" ) / UnitHealthMax( candidate.target.."target" ) * 100 ); |
||
542 | local _,targetClass = UnitClass(target.self); |
||
543 | target["targetClass"] = targetClass; |
||
544 | |||
545 | target["playersCount"] = 1; |
||
546 | if (SA_IsMarked(candidate.target)) then |
||
547 | target["marked"] = true; |
||
548 | else |
||
549 | target["marked"] = false; |
||
550 | end |
||
551 | |||
552 | if (isUnitCC(candidate.target)) then |
||
553 | target["cced"] = true; |
||
554 | else |
||
555 | target["cced"] = false; |
||
556 | end |
||
557 | |||
558 | -- todo: xxx, only if enabled |
||
559 | target["icon"] = GetRaidTargetIndex(target.self); |
||
560 | if (not target.icon) then |
||
561 | target["icon"] = 0; |
||
562 | end |
||
563 | |||
564 | -- is world boss, elite etc |
||
565 | target["classification"] = UnitClassification(candidate.target); |
||
566 | |||
567 | -- set myTarget to true if this is my target |
||
568 | target["myTarget"] = UnitIsUnit(target.self, "target"); |
||
569 | |||
570 | if (pullerId~="") then |
||
571 | target["pullerTarget"] = UnitIsUnit(target.self, pullerId.."target"); |
||
572 | end |
||
573 | -- is unit in combat, 20.6.2006 - if it has target, treat as in combat! |
||
574 | target["inCombat"] = UnitAffectingCombat(target.self) or target.targetName; |
||
575 | |||
576 | return target; |
||
577 | end |
||
578 | |||
579 | -- return true if this candidate target should be added to list |
||
580 | function SA_Add_To_List(candidate) |
||
581 | if( UnitCanAssist("player", candidate.unitId) and |
||
582 | UnitExists(candidate.target) and |
||
583 | ((UnitAffectingCombat(candidate.target) or SA_OPTIONS.OutOfCombat) and not UnitIsDead(candidate.target)) and |
||
584 | UnitCanAttack("player", candidate.target)) |
||
585 | then |
||
586 | return true; |
||
587 | else |
||
588 | return false; |
||
589 | end |
||
590 | end |
||
591 | |||
592 | function SA_List_SortTarget(a,b) |
||
593 | -- marked first |
||
594 | if (a.marked and not b.marked) then return true; end; |
||
595 | if (b.marked and not a.marked) then return false; end; |
||
596 | |||
597 | -- puller target second |
||
598 | --if (a.pullerTarget and not b.pullerTarget) then return true; end; |
||
599 | --if (b.pullerTarget and not a.pullerTarget) then return false; end; |
||
600 | |||
601 | -- ooc last |
||
602 | if (a.inCombat and not b.inCombat) then return true; end; |
||
603 | if (b.inCombat and not a.inCombat) then return false; end; |
||
604 | |||
605 | -- cced second lastest |
||
606 | if (a.cced and not b.cced) then return false; end; |
||
607 | if (b.cced and not a.cced) then return true; end; |
||
608 | |||
609 | -- if both unmarked, then sort by name |
||
610 | if (a.name == b.name) then |
||
611 | return a.playersCount > b.playersCount; |
||
612 | else |
||
613 | return a.name > b.name; |
||
614 | end |
||
615 | end |
||
616 | |||
617 | ------------------------------------------------------------------------------ |
||
618 | -- updates the available assists list |
||
619 | ------------------------------------------------------------------------------ |
||
620 | |||
621 | -- this table contains all variables used in incoming detection & printing |
||
622 | local incoming = { prev_iter=0, currently=0, prev_time=0 }; |
||
623 | |||
624 | function SA_List_Update() |
||
625 | |||
626 | -- if disabled, stop immediattely |
||
627 | if (not SA_OPTIONS.ShowAvailable) then return; end |
||
628 | |||
629 | local candidates, members= SA_GetCandidates(false) |
||
630 | table.sort(candidates, function(a,b) return SA_SortCandidate(a,b,members) end); |
||
631 | |||
632 | -- filter out candidates out of range if assisting only members nearby |
||
633 | -- TODO: might not be good since causes overheading AND we should still add those players outside range to target? |
||
634 | if (SA_OPTIONS.AssistOnlyNearest) then |
||
635 | candidates = SA_FilterCandidatesByDistance(candidates, false); |
||
636 | end |
||
637 | |||
638 | -- add player to candidates if enabled |
||
639 | if (UnitExists("target") and SA_OPTIONS.AddMyTarget) then |
||
640 | table.insert(candidates, SA_GetPlayerAsCandidate()); |
||
641 | end |
||
642 | |||
643 | local aggro_count = 0; |
||
644 | local wb_count = 0; |
||
645 | |||
646 | local pullerId = ""; |
||
647 | if (SA_OPTIONS.puller) then |
||
648 | local puller = SA_GetPuller(candidates); |
||
649 | -- puller might be null if it doesn't exist in group |
||
650 | if (puller) then |
||
651 | pullerId = puller.unitId; |
||
652 | end |
||
653 | end |
||
654 | |||
655 | -- construct list of available assists / targets |
||
656 | local targets = {}; |
||
657 | for _,candidate in candidates do |
||
658 | if (SA_Add_To_List(candidate)) then |
||
659 | local target = SA_GetExistingTarget(candidate, targets); |
||
660 | if (target) then |
||
661 | -- append targetting candidate to target |
||
662 | table.insert(target.players, candidate); |
||
663 | target.playersCount = target.playersCount + 1; |
||
664 | else |
||
665 | -- this candidate target is not targetted by anyone else, construct new target to the list |
||
666 | local target = SA_GetTarget(candidate, pullerId); |
||
667 | |||
668 | -- filtering if enabled |
||
669 | if (SA_OPTIONS.Filter) then |
||
670 | if string.find(string.lower(target.fullName), SA_OPTIONS.Filter) then |
||
671 | table.insert(targets, target); |
||
672 | end |
||
673 | else |
||
674 | table.insert(targets, target); |
||
675 | end |
||
676 | end |
||
677 | end |
||
678 | end |
||
679 | |||
680 | -- update non-aggro cache and mark cache, this is used when SORTING candidates (see SA_SortCandidate) |
||
681 | SA_MARKEDCACHE = {}; |
||
682 | SA_PASSIVECACHE = {}; |
||
683 | local dirty = false; |
||
684 | local i = 0; |
||
685 | local mc = 0; |
||
686 | local ooc_seen = false; |
||
687 | for _,target in targets do |
||
688 | i = i + 1; |
||
689 | if (target.marked) then |
||
690 | if (i ~= 1 and mc == 0) then |
||
691 | -- marked target not in first place, list is tainted |
||
692 | SA_Debug("mark dirty list "..target.name, 1); |
||
693 | dirty = true; |
||
694 | end |
||
695 | mc = mc + 1; |
||
696 | for _,c in target.players do |
||
697 | SA_MARKEDCACHE[c.unitName] = true; |
||
698 | end |
||
699 | end |
||
700 | if (not target.inCombat or target.cced) then |
||
701 | ooc_seen = true; |
||
702 | for _,c in target.players do |
||
703 | SA_PASSIVECACHE[c.unitName] = true; |
||
704 | end |
||
705 | else |
||
706 | -- incombat targets after ooc targets, list is tainted |
||
707 | if (ooc_seen) then |
||
708 | SA_Debug("ooc dirty list "..target.name, 1); |
||
709 | dirty = true; |
||
710 | end |
||
711 | end |
||
712 | end |
||
713 | |||
714 | -- if list is dirty abort refresh and request new one with correct cache, |
||
715 | -- update 22.4.2006 cache is not correct on next refresh either, in fact it may pass long time it is okay. |
||
716 | -- hence added PREV_DIRTY flag until problem is investigated |
||
717 | -- update 20.6.2006 sort does not seem to work correctly with buff (ie. hunters mark) priorization! |
||
718 | -- update 09.7.2006 sorting seems to work fine now, cleanup this + futile flag? |
||
719 | if (dirty and not SA_PREV.dirty) then |
||
720 | -- try again soon |
||
721 | SA_LASTREFRESH = 0.2; |
||
722 | SA_PREV.dirty = true; |
||
723 | return; |
||
724 | end |
||
725 | SA_PREV.dirty = false; |
||
726 | |||
727 | -- sort targets depending options |
||
728 | if (SA_OPTIONS.PreserveOrder) then |
||
729 | table.sort(targets, function(a,b) return SA_List_SortTarget(a,b) end); |
||
730 | end |
||
731 | |||
732 | ------------------------------------------------------------------------------ |
||
733 | |||
734 | local i = 0; |
||
735 | local overloaded = false; |
||
736 | |||
737 | for _,target in targets do |
||
738 | i = i + 1; |
||
739 | if (i>10) then |
||
740 | overloaded = true; |
||
741 | break; |
||
742 | end |
||
743 | |||
744 | local box = SA_LIST_BOXES[i]; |
||
745 | box.frame.obj = target; -- store the target data to box (used ie. in tooltips) |
||
746 | box.frame:Show(); |
||
747 | |||
748 | -- set values & texts |
||
749 | local targetText = target.players[1].unitName; |
||
750 | if (not SA_OPTIONS.HideTBY) then |
||
751 | targetText = "|cffffffffT.by |r"..targetText; |
||
752 | end |
||
753 | if (target.playersCount > 1) then |
||
754 | targetText = targetText.." + "..tostring(target.playersCount-1); -- -1 for the one who targets (who + n) |
||
755 | end |
||
756 | -- append prefixes to mob name + for elite, WB+ for worldboss |
||
757 | local mobText = target.name; |
||
758 | if (target.classification == "elite") then |
||
759 | mobText = SA_SEMIALERT.."+|r"..mobText; |
||
760 | end |
||
761 | if (target.classification == "worldboss") then |
||
762 | mobText = SA_SEMIALERT.."WB+|r"..mobText; |
||
763 | if (target.inCombat) then |
||
764 | wb_count = wb_count + 1; |
||
765 | end |
||
766 | end |
||
767 | box.mobBar:SetValue(target.health); |
||
768 | box.mobText:SetText(mobText); |
||
769 | |||
770 | -- colorize box |
||
771 | if (not target.inCombat) then |
||
772 | -- if this is out of combat and my target, apply only slight green efect |
||
773 | if (target.myTarget) then |
||
774 | box.frame:SetBackdropColor(0,0.65,0,0.65); |
||
775 | box.frame:SetBackdropBorderColor(0,1,0,0.75); |
||
776 | elseif (target.pullerTarget) then |
||
777 | -- yellow background on puller target |
||
778 | box.frame:SetBackdropColor(1,1,0,0.3); |
||
779 | box.frame:SetBackdropBorderColor(0,0,0,0.4); -- regular grey |
||
780 | else |
||
781 | box.frame:SetBackdropColor(0,0,0,0.4); |
||
782 | box.frame:SetBackdropBorderColor(0,0,0,0.4); -- regular grey |
||
783 | end |
||
784 | else |
||
785 | local addinc = true; |
||
786 | -- set border color |
||
787 | if (target.marked) then |
||
788 | box.frame:SetBackdropBorderColor(1,0,0,1); |
||
789 | elseif (target.myTarget) then |
||
790 | box.frame:SetBackdropBorderColor(0,1,0,1); |
||
791 | elseif (target.cced) then |
||
792 | box.frame:SetBackdropBorderColor(0,0,0,1); |
||
793 | addinc = false; |
||
794 | else |
||
795 | box.frame:SetBackdropBorderColor(0.8,0.8,0.8,1); |
||
796 | end |
||
797 | -- add to incoming |
||
798 | if (addinc) then |
||
799 | incoming.currently = incoming.currently + 1; |
||
800 | end |
||
801 | |||
802 | -- set background color |
||
803 | if (target.myTarget) then |
||
804 | box.frame:SetBackdropColor(0,0.75,0,0.85); |
||
805 | elseif (target.pullerTarget) then |
||
806 | -- yellow background on puller target |
||
807 | box.frame:SetBackdropColor(1,1,0,0.3); |
||
808 | else |
||
809 | box.frame:SetBackdropColor(0,0,0,0.5); |
||
810 | end |
||
811 | end |
||
812 | |||
813 | -- colorize target target text in some situations |
||
814 | if (target.targetName) then |
||
815 | local col = ""; |
||
816 | local endcol = ""; |
||
817 | if (UnitName("player") == target.targetName) then |
||
818 | -- targetting player |
||
819 | aggro_count = aggro_count + 1; |
||
820 | if (SA_OPTIONS.TankMode) then |
||
821 | col = SA_SEMIALERT; |
||
822 | else |
||
823 | col = SA_ALERTCOL; |
||
824 | end |
||
825 | elseif (not SA_IsTank(target.targetName) and SA_OPTIONS.TankMode) then |
||
826 | -- targeting non tank in tank mode |
||
827 | col = SA_ALERTCOL; |
||
828 | elseif (SA_IsTank(target.targetName)) then |
||
829 | -- targeting tank always on tank color, regardless of tank mode |
||
830 | col = SA_TANKCOL; |
||
831 | end |
||
832 | -- append end of color code marking |
||
833 | if (col~="") then |
||
834 | endcol = "|r"; |
||
835 | end |
||
836 | box.targetBar:SetValue(target.targetHealth); |
||
837 | box.targetText:SetText("T:"..col..target.targetName..endcol); |
||
838 | else |
||
839 | box.targetBar:SetValue(0); |
||
840 | box.targetText:SetText("?"); |
||
841 | end |
||
842 | |||
843 | -- colorize mob bar |
||
844 | if (UnitIsTapped(target.self) and not UnitIsTappedByPlayer(target.self)) then |
||
845 | -- target is "grey" to us |
||
846 | box.mobBar:SetStatusBarColor(0.8, 0.8, 0.8); |
||
847 | elseif (UnitPlayerControlled(target.self)) then |
||
848 | -- pvp target |
||
849 | box.mobBar:SetStatusBarColor(1, 0, 0); |
||
850 | elseif (not target.inCombat) then |
||
851 | -- out of combat target |
||
852 | box.mobBar:SetStatusBarColor(0.38, 0.38, 0.38); |
||
853 | else |
||
854 | -- normal green |
||
855 | box.mobBar:SetStatusBarColor(0, 1, 0); |
||
856 | end |
||
857 | |||
858 | -- make texts visible always (bugs on some systems) |
||
859 | -- code peeked from TextStatusBar_UpdateTextString sources |
||
860 | -- perhaps I should've called that method but it does some other unwanted things |
||
861 | box.mobBar.TextString:Show(); |
||
862 | box.targetBar.TextString:Show(); |
||
863 | box.targetOf:SetText(targetText); |
||
864 | |||
865 | -- set class icon (texture coordinates) |
||
866 | local coords = SA_TEXTCOORDS[target.targetClass]; |
||
867 | box.classIcon:SetTexCoord(coords.left, coords.right, coords.top, coords.bottom); |
||
868 | |||
869 | -- set target icon (texture coordinates) |
||
870 | -- TODO: xxx, only if feature enabled |
||
871 | if (target.icon>0) then |
||
872 | local icon = UnitPopupButtons["RAID_TARGET_"..target.icon]; |
||
873 | box.targetIcon:SetTexCoord(icon.tCoordLeft, icon.tCoordRight, icon.tCoordTop, icon.tCoordBottom ); |
||
874 | box.targetIcon:Show(); |
||
875 | else |
||
876 | box.targetIcon:Hide(); |
||
877 | end |
||
878 | |||
879 | -- hunters mark icon |
||
880 | if (target.marked and SA_OPTIONS.HuntersMarkIconMode>1) then |
||
881 | box.huntersMarkIcon:Show(); |
||
882 | else |
||
883 | box.huntersMarkIcon:Hide(); |
||
884 | end |
||
885 | |||
886 | end |
||
887 | |||
888 | ------------------------------------------------------------------------------ |
||
889 | |||
890 | -- if count has been changed update visibility |
||
891 | -- TODO: apparently there is no performance loss calling hide/show .. so this is partially futile now |
||
892 | if (i ~= SA_PREV.mob_count) then |
||
893 | for j = i + 1, SA_PREV.mob_count do |
||
894 | if (j>10) then |
||
895 | break; |
||
896 | end |
||
897 | SA_LIST_BOXES[j].frame:Hide(); |
||
898 | end |
||
899 | end |
||
900 | |||
901 | -- if player have a ACQUIRED aggro, play optional sound to alert player |
||
902 | if (aggro_count > SA_PREV.aggro_count) then |
||
903 | if (SA_OPTIONS.AudioWarning) then PlaySound(SA_OPTIONS.SoundGainAggro); end; |
||
904 | if (SA_OPTIONS.VerboseAcquiredAggro) then |
||
905 | SA_Verbose("Acquired aggro!", COLOR_ALERT); |
||
906 | end |
||
907 | end |
||
908 | -- if player have a LOST aggro, play optional sound to alert player |
||
909 | if (aggro_count < SA_PREV.aggro_count and i >= SA_PREV.mob_count) then |
||
910 | if (SA_OPTIONS.LostAudioWarning) then PlaySoundFile(SA_OPTIONS.SoundLoseAggro); end; |
||
911 | if (SA_OPTIONS.VerboseLostAggro) then |
||
912 | SA_Verbose("Lost aggro!", COLOR_ALERT); |
||
913 | end |
||
914 | end |
||
915 | -- if incoming worldbosses, play optional sound to alert player, TODO: OPTION! |
||
916 | if (wb_count > SA_PREV.wb_count) then |
||
917 | SA_Verbose("Incoming worldboss!", COLOR_ALERT); |
||
918 | PlaySoundFile(SA_OPTIONS.SoundIncomingWoldBoss); |
||
919 | end |
||
920 | |||
921 | -- if we have new mobs in combat |
||
922 | -- TODO: on incoming worldboss level unit play alert sound! |
||
923 | if (SA_OPTIONS.VerboseIncoming) then |
||
924 | local now = time(); |
||
925 | if (incoming.currently > incoming.prev_iter and now - incoming.prev_time > 4) then |
||
926 | |||
927 | local amount = incoming.currently - incoming.prev_iter; |
||
928 | if (amount > 1) then |
||
929 | SA_Verbose("Incoming x"..amount.."!", COLOR_ALERT); |
||
930 | else |
||
931 | SA_Verbose("Incoming!", COLOR_ALERT); |
||
932 | end |
||
933 | |||
934 | incoming.prev_time = now; |
||
935 | end |
||
936 | incoming.prev_iter = incoming.currently; |
||
937 | incoming.currently = 0; |
||
938 | end |
||
939 | |||
940 | -- update title to correct mode |
||
941 | if (i>0) then |
||
942 | title_mode = MODE_NORMAL; |
||
943 | else |
||
944 | title_mode = MODE_OOC; |
||
945 | end |
||
946 | if (SA_OPTIONS.Filter) then |
||
947 | title_mode = MODE_FILTERED; |
||
948 | end |
||
949 | if (SA_OPTIONS.AssistOnlyNearest) then |
||
950 | title_mode = MODE_NEAREST; |
||
951 | end |
||
952 | if (overloaded) then |
||
953 | title_mode = MODE_OVERLOADED; |
||
954 | end |
||
955 | |||
956 | -- update title button if mode has been changed |
||
957 | if (title_mode ~= SA_PREV.title_mode) then |
||
958 | SA_List_SetTitleButton(title_mode); |
||
959 | end |
||
960 | |||
961 | -- set prev data for next iteration |
||
962 | SA_PREV.title_mode = title_mode; |
||
963 | SA_PREV.mob_count = i; |
||
964 | SA_PREV.wb_count = wb_count; |
||
965 | SA_PREV.aggro_count = aggro_count; |
||
966 | end |
||
967 | |||
968 | ------------------------------------------------------------------------------ |
||
969 | -- Title button handling |
||
970 | ------------------------------------------------------------------------------ |
||
971 | |||
972 | function SA_List_SetTitleButton(mode) |
||
973 | if (mode == MODE_NORMAL or mode == MODE_OOC) then |
||
974 | if (mode == MODE_NORMAL) then |
||
975 | SAListTitleButton:SetAlpha(1); |
||
976 | else |
||
977 | if (SA_OPTIONS.HideTitle) then |
||
978 | SAListTitleButton:SetAlpha(0); |
||
979 | else |
||
980 | SAListTitleButton:SetAlpha(SA_OPTIONS.ListOOCAlpha); |
||
981 | end |
||
982 | end |
||
983 | SAListTitleButton:SetBackdropColor(0, 0, 0, 0.3); |
||
984 | SAListTitleButton:SetBackdropBorderColor(1,1,1,1) |
||
985 | SAListTitle:SetText("Available assists"); |
||
986 | elseif (mode == MODE_FILTERED or mode == MODE_NEAREST) then |
||
987 | SAListTitleButton:SetAlpha(1); |
||
988 | SAListTitleButton:SetBackdropColor(0,0.6,0.8,1); |
||
989 | SAListTitleButton:SetBackdropBorderColor(0,0.6,0.8,1); |
||
990 | local text = ""; |
||
991 | if (mode == MODE_FILTERED) then |
||
992 | text = "Filtered"; |
||
993 | end |
||
994 | if (mode == MODE_NEAREST) then |
||
995 | text = "Nearest"; |
||
996 | end |
||
997 | SAListTitle:SetText(text); |
||
998 | elseif (mode == MODE_PAUSED) then |
||
999 | SAListTitleButton:SetAlpha(1); |
||
1000 | SAListTitleButton:SetBackdropColor(0.3,0.3,0.3,1); |
||
1001 | SAListTitleButton:SetBackdropBorderColor(0.6,0.6,0.6,1); |
||
1002 | SAListTitle:SetText("Paused"); |
||
1003 | elseif (mode == MODE_OVERLOADED) then |
||
1004 | SAListTitleButton:SetAlpha(1); |
||
1005 | SAListTitleButton:SetBackdropColor(1,0,0,1); |
||
1006 | SAListTitleButton:SetBackdropBorderColor(1,0,0,1); |
||
1007 | SAListTitle:SetText("OVERLOADED"); |
||
1008 | else |
||
1009 | printInfo("smartassist error: unknown title mode "..tostring(mode)); |
||
1010 | end |
||
1011 | end |
||
1012 | |||
1013 | SA_PREV_ALPHA = 1; |
||
1014 | function SA_List_TitleButton_OnEnter() |
||
1015 | SA_PREV_ALPHA = SAListTitleButton:GetAlpha(); |
||
1016 | SAListTitleButton:SetAlpha(1); |
||
1017 | end |
||
1018 | |||
1019 | function SA_List_TitleButton_OnLeave() |
||
1020 | SAListTitleButton:SetAlpha(SA_PREV_ALPHA); |
||
1021 | end |