-
Notifications
You must be signed in to change notification settings - Fork 0
/
AccurateRange.lua
419 lines (350 loc) · 10.7 KB
/
AccurateRange.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
local MAJOR_VERSION = "LibAccurateRange-1.0"
local MINOR_VERSION = 1
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
if not AceLibrary:HasInstance("AceEvent-2.0") then error(MAJOR_VERSION .. " requires AceEvent-2.0") end
local AceEvent = AceLibrary("AceEvent-2.0")
local spells_40yd = {
["Holy Light"] = true, ["Flash of Light"] = true,
["Flash Heal"] = true, ["Heal"] = true, ["Lesser Heal"] = true, ["Greater Heal"] = true, ["Renew"] = true,
["Healing Touch"] = true, ["Regrowth"] = true, ["Rejuvenation"] = true,
["Healing Wave"] = true, ["Lesser Healing Wave"] = true, ["Chain Heal"] = true,
}
do
local locale = GetLocale()
if locale ~= "enUS" and locale ~= "enGB" then
if AceLibrary:HasInstance("Babble-Spell-2.2") then
local BS = AceLibrary("Babble-Spell-2.2")
local loc_spells_40yd = {}
for k, _ in pairs(spells_40yd) do
loc_spells_40yd[BS[k]] = true
end
spells_40yd = loc_spells_40yd
else
error("AccurateRange requires Babble-Spell-2.2 for localization")
end
end
end
local _G = getfenv(0)
local next = next
local table_insert, table_getn, table_setn = table.insert, table.getn, table.setn
local GetTime = GetTime
local AttackTarget = AttackTarget
local IsCurrentAction, IsActionInRange = IsCurrentAction, IsActionInRange
local CheckInteractDistance = CheckInteractDistance
local TargetLastTarget, TargetUnit = TargetLastTarget, TargetUnit
local UnitExists, UnitIsCharmed, UnitIsConnected, UnitIsDeadOrGhost, UnitIsFriend, UnitIsUnit, UnitIsVisible = UnitExists, UnitIsCharmed, UnitIsConnected, UnitIsDeadOrGhost, UnitIsFriend, UnitIsUnit, UnitIsVisible
local frame = CreateFrame("Frame")
local Frame_GetScript = frame.GetScript
local Frame_SetScript = frame.SetScript
local Frame_GetChildren = frame.GetChildren
local Frame_GetParent = frame.GetParent
local action_slot_40yd
local action_slot_attack
local scan_units = {}
local current_scan_unit
local scan_tip = CreateFrame("GameTooltip", "AccurateRangeScanTip", nil, "GameTooltipTemplate")
scan_tip:SetOwner(scan_tip, "ANCHOR_NONE")
local scan_tip_l1 = AccurateRangeScanTipTextLeft1
local scan_tip_r1 = AccurateRangeScanTipTextRight1
local function print(...)
for k = 1,table.getn(arg) do arg[k] = tostring(arg[k]) end
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00AccurateRange:|r " .. table.concat(arg, " "))
end
local function table_length(t)
local count = 0
local n = next(t)
while n do
count = count + 1
n = next(t, n)
end
return count
end
local timedb = {}
local function timef(label, func, a1, a2, a3, a4)
local start = debugprofilestop()
func(a1, a2, a3, a4)
local elapsed = debugprofilestop() - start
if not timedb[label] then
timedb[label] = { min = elapsed, max = elapsed, avg = elapsed }
else
--timedb[label].min =
end
print("<" .. (label or "unk") .. ">", elapsed / 1000000, "ms")
end
local function GetActionSpell(slot)
if not HasAction(slot) or GetActionText(slot) then
return nil
end
scan_tip:SetAction(slot)
local lines = scan_tip:NumLines()
if lines and lines > 0 then
local spell_name = scan_tip_l1:IsShown() and scan_tip_l1:GetText()
if spell_name ~= "" then
return spell_name
end
end
end
local function ScanActionButtons()
action_slot_40yd = nil
action_slot_attack = nil
for slot = 1, 120 do
local spell_name = GetActionSpell(slot)
if spells_40yd[spell_name] then
print(slot, ": ", spell_name)
action_slot_40yd = slot
if action_slot_attack then return end
end
--elseif spells_attack[spell_name] then
if IsAttackAction(slot) then
print(slot, ": ", spell_name)
action_slot_attack = slot
if action_slot_40yd then return end
end
end
error("AccurateRange: no suitable spell found in an action button")
end
local all_found_frames = {}
local event_found_frames
local disabled_frames
--[[
local function IsFrameInternal(f)
local function has_func(func)
return type(f[func]) == "function"
end
if type(rawget(f, 0)) == "userdata" and has_func("SetScript") and has_func("GetScript") and has_func("GetChildren") and has_func("GetParent") then
-- very effective, but too slow to use as the first test
Frame_GetScript(f, "OnEvent")
return true
end
end
local function IsFrame(f)
local succ, res = pcall(IsFrameInternal, f)
return succ and res
end
local function ScanFrame(frame)
if not all_found_frames[frame] then
all_found_frames[frame] = true
for _, f in ipairs({ Frame_GetChildren(frame) }) do
if not all_found_frames[f] and IsFrame(f) then ScanFrame(f) end
end
if Frame_GetParent(frame) then
ScanFrame(Frame_GetParent(frame))
end
end
end
local scanned_tables = {}
local function ScanTableFrames(t)
if scanned_tables[t] then return end
scanned_tables[t] = true
for name, f in pairs(t) do
if type(f) == "table" then
if IsFrame(f) then
ScanFrame(f)
end
ScanTableFrames(f)
end
end
end
]]
local function ScanFrames()
all_found_frames = {}
event_found_frames = nil
local frame = EnumerateFrames()
while frame do
all_found_frames[frame] = true
frame = EnumerateFrames(frame)
end
-- scanned_tables = {}
-- ScanTableFrames(_G)
-- scanned_tables = nil
print("Found", table_length(all_found_frames), "frames");
end
local function Frame_Tag_OnEvent()
event_found_frames[this] = true
end
local function DisableFrames()
disabled_frames = event_found_frames or all_found_frames
-- print("disabling", table_length(disabled_frames))
-- first run, tag the frames that are triggering OnEvent calls
local new_func
if not event_found_frames then
event_found_frames = {}
new_func = Frame_Tag_OnEvent
end
local frame = next(disabled_frames)
while frame do
frame.__ar_orig_onevent = Frame_GetScript(frame, "OnEvent")
Frame_SetScript(frame, "OnEvent", new_func)
frame = next(disabled_frames, frame)
end
end
local function RestoreFrames()
-- print("restoring", table_length(disabled_frames))
local frame = next(disabled_frames)
while frame do
Frame_SetScript(frame, "OnEvent", frame.__ar_orig_onevent)
frame = next(disabled_frames, frame)
end
end
local function IsValidUnit(unit)
return UnitExists(unit) and
not UnitIsUnit("player", unit) and
not UnitIsDeadOrGhost(unit) and
UnitIsConnected(unit) and
UnitIsVisible(unit) and
UnitIsFriend("player", unit) and
not UnitIsCharmed(unit)
end
local function AdvanceScanUnit()
current_scan_unit = next(scan_units, current_scan_unit)
if current_scan_unit == nil then
current_scan_unit = next(scan_units)
end
end
local function SetUnitRange(unit, inrange)
if scan_units[unit] ~= inrange then
scan_units[unit] = inrange
AceEvent:TriggerEvent("AccurateRange_UnitUpdate", unit, inrange)
end
end
local function GetNextScanUnit()
local start_scan_unit = current_scan_unit
while true do
local scan_unit = current_scan_unit
AdvanceScanUnit()
if UnitIsUnit(scan_unit, "player") then
SetUnitRange(scan_unit, true)
elseif IsValidUnit(scan_unit) then
if CheckInteractDistance(scan_unit, 4) then
SetUnitRange(scan_unit, true)
else
return scan_unit
end
else
SetUnitRange(scan_unit, false)
end
if current_scan_unit == start_scan_unit then
-- print("no valid units")
return nil
end
end
end
local function UpdateRoster()
local function add_unit(unit)
if UnitExists(unit) then
scan_units[unit] = 0
else
scan_units[unit] = nil
end
end
for i = 1, 40 do add_unit("raid" .. i) end
for i = 1, 4 do add_unit("party" .. i) end
scan_units["player"] = 0
current_scan_unit = next(scan_units)
end
local next_update_time = 0
local scan_update_time
local update_interval = 0.005
local max_updates_per_frame = 3
local restore_attack = false
local current_frame = 0
local max_update_time_ns = 2000000
local function AccurateRange_OnUpdate()
current_frame = current_frame + 1
-- AttackTarget won't work on the same frame that the target changed, so try
-- for the next few frames
if restore_attack and (current_frame - restore_attack) > 30 then
restore_attack = false
elseif restore_attack and not IsCurrentAction(action_slot_attack) then
AttackTarget()
restore_attack = false
end
if scan_update_time and GetTime() >= scan_update_time then
scan_update_time = nil
timef("ScanFrames", ScanFrames)
if not action_slot_attack or not action_slot_40yd then
ScanActionButtons()
end
end
if GetTime() < next_update_time then return end
next_update_time = GetTime() + update_interval
if not action_slot_40yd then return end
local start_time = debugprofilestop()
local num_updated_units = 0
local restore_frames
for upd = 1, max_updates_per_frame do
local unit = GetNextScanUnit()
if not unit then break end
num_updated_units = num_updated_units + 1
if action_slot_attack and IsCurrentAction(action_slot_attack) == 1 then
restore_attack = current_frame
end
local change_target = not UnitIsUnit("target", unit)
if change_target then
if not restore_frames then
-- timef("disable", DisableFrames)
DisableFrames()
restore_frames = true
end
-- timef("TargetUnit", TargetUnit, unit)
TargetUnit(unit)
end
local inrange = IsActionInRange(action_slot_40yd) == 1
SetUnitRange(unit, inrange)
if change_target then
TargetLastTarget()
end
local elapsed_time = debugprofilestop() - start_time
if elapsed_time > max_update_time_ns then
break
end
end
if restore_frames then
-- timef( "restore", RestoreFrames)
RestoreFrames()
end
local elapsed_time = debugprofilestop() - start_time
if random() > 0.99 and num_updated_units > 0 then
print("update done in", elapsed_time / 1000000, "ms,", num_updated_units, "units updated")
end
end
local function AccurateRange_OnEvent()
if event == "PLAYER_ENTERING_WORLD" then
scan_update_time = GetTime() + 3
elseif event == "ACTIONBAR_SLOT_CHANGED" then
if arg1 == 0 or action_slot_40yd == arg1 or action_slot_attack == arg1 or not action_slot_40yd or not action_slot_attack then
ScanActionButtons()
end
elseif event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE" then
UpdateRoster()
end
end
local function AccurateRange_IsUnitInRange(unit)
if scan_units[unit] == nil then
print(unit, "not found")
return
end
return scan_units[unit] == true
end
local function AccurateRange_Enable()
frame:SetScript("OnUpdate", AccurateRange_OnUpdate)
frame:SetScript("OnEvent", AccurateRange_OnEvent)
frame:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
frame:RegisterEvent("RAID_ROSTER_UPDATE")
UpdateRoster()
scan_update_time = GetTime() + 3
end
local function AccurateRange_Disable()
frame:UnregisterAllEvents()
frame:SetScript("OnUpdate", nil)
end
do
AccurateRange_Enable()
end
local AccurateRange = {
["IsUnitInRange"] = AccurateRange_IsUnitInRange
}
AceLibrary:Register(AccurateRange, MAJOR_VERSION, MINOR_VERSION)