yellowfive@57
|
1 -- AskMrRobot
|
yellowfive@57
|
2 -- Does cool stuff associated with askmrrobot.com:
|
yellowfive@57
|
3 -- Import/Export gear and optimization solutions from/to the website
|
yellowfive@57
|
4 -- Improve the combat logging experience and augment it with extra data not available directly in the log file
|
yellowfive@57
|
5 -- Team Optimizer convenience functionality
|
yellowfive@57
|
6
|
yellowfive@57
|
7 AskMrRobot = LibStub("AceAddon-3.0"):NewAddon("AskMrRobot", "AceEvent-3.0", "AceComm-3.0", "AceConsole-3.0", "AceSerializer-3.0")
|
yellowfive@57
|
8 local Amr = AskMrRobot
|
yellowfive@57
|
9 Amr.Serializer = LibStub("AskMrRobot-Serializer")
|
yellowfive@57
|
10
|
yellowfive@57
|
11 Amr.ADDON_NAME = "AskMrRobot"
|
yellowfive@57
|
12
|
yellowfive@57
|
13 -- types of inter-addon messages that we receive, used to parcel them out to the proper handlers
|
yellowfive@57
|
14 Amr.MessageTypes = {
|
yellowfive@57
|
15 Version = "_V",
|
yellowfive@57
|
16 VersionRequest = "_VR",
|
yellowfive@57
|
17 Team = "_T"
|
yellowfive@57
|
18 }
|
yellowfive@57
|
19
|
yellowfive@57
|
20 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
|
yellowfive@57
|
21 local AceGUI = LibStub("AceGUI-3.0")
|
yellowfive@57
|
22
|
yellowfive@57
|
23 -- minimap icon and LDB support
|
yellowfive@57
|
24 local _amrLDB = LibStub("LibDataBroker-1.1"):NewDataObject(Amr.ADDON_NAME, {
|
yellowfive@57
|
25 type = "launcher",
|
yellowfive@57
|
26 text = "Ask Mr. Robot",
|
yellowfive@57
|
27 icon = "Interface\\AddOns\\" .. Amr.ADDON_NAME .. "\\Media\\icon",
|
yellowfive@57
|
28 OnClick = function(self, button, down)
|
yellowfive@57
|
29 if button == "LeftButton" then
|
yellowfive@57
|
30 if IsControlKeyDown() then
|
yellowfive@57
|
31 Amr:Wipe()
|
yellowfive@57
|
32 else
|
yellowfive@57
|
33 Amr:Toggle()
|
yellowfive@57
|
34 end
|
yellowfive@57
|
35 elseif button == "RightButton" then
|
yellowfive@57
|
36 Amr:EquipGearSet()
|
yellowfive@57
|
37 end
|
yellowfive@57
|
38 end,
|
yellowfive@57
|
39 OnTooltipShow = function(tt)
|
yellowfive@57
|
40 tt:AddLine("Ask Mr. Robot", 1, 1, 1);
|
yellowfive@57
|
41 tt:AddLine(" ");
|
yellowfive@57
|
42 tt:AddLine(L.MinimapTooltip)
|
yellowfive@57
|
43 end
|
yellowfive@57
|
44 })
|
yellowfive@57
|
45 local _icon = LibStub("LibDBIcon-1.0")
|
yellowfive@57
|
46
|
yellowfive@57
|
47
|
yellowfive@57
|
48 -- initialize the database
|
yellowfive@57
|
49 local function initializeDb()
|
yellowfive@57
|
50
|
yellowfive@57
|
51 local defaults = {
|
yellowfive@57
|
52 char = {
|
yellowfive@57
|
53 FirstUse = true, -- true if this is first time use, gets cleared after seeing the export help splash window
|
yellowfive@57
|
54 SubSpecs = {}, -- last seen subspecs for this character, used to deal with some ambiguous specs
|
yellowfive@57
|
55 Equipped = {}, -- for each spec group (1 or 2), slot id to item link
|
yellowfive@57
|
56 BagItems = {}, -- list of item links for bag
|
yellowfive@57
|
57 BankItems = {}, -- list of item links for bank
|
yellowfive@57
|
58 VoidItems = {}, -- list of item links for void storage
|
yellowfive@57
|
59 BagItemsAndCounts = {}, -- used mainly for the shopping list
|
yellowfive@57
|
60 BankItemsAndCounts = {}, -- used mainly for the shopping list
|
yellowfive@57
|
61 GearSets = {}, -- imported gear sets, key by spec group (1 or 2), slot id to item object
|
yellowfive@57
|
62 ExtraItemData = {}, -- for each spec group (1 or 2): mainly for legacy support, item id to object with socketColor and duplicateId information
|
yellowfive@57
|
63 ExtraGemData = {}, -- for each spec group (1 or 2): gem enchant id to gem display information, and data used to detect identical gems (mainly for legacy support)
|
yellowfive@57
|
64 ExtraEnchantData = {}, -- for each spec group (1 or 2): enchant id to enchant display information and material information
|
yellowfive@57
|
65 Logging = { -- character logging settings
|
yellowfive@57
|
66 Enabled = false, -- whether logging is currently on or not
|
yellowfive@57
|
67 LastZone = nil, -- last zone the player was in
|
yellowfive@57
|
68 LastDiff = nil, -- last difficulty for the last zone the player was in
|
yellowfive@57
|
69 LastWipe = nil -- last time a wipe was called by this player
|
yellowfive@57
|
70 },
|
yellowfive@57
|
71 TeamOpt = {
|
yellowfive@57
|
72 AllItems = {}, -- all equippable items no matter where it is, list of item unique ids, used to determine when a player gains a new equippable item
|
yellowfive@57
|
73 History = {}, -- history of drops since joining the current group
|
yellowfive@57
|
74 Rolls = {}, -- current loot choices for a loot distribution in progress
|
yellowfive@57
|
75 Role = nil, -- Leader or Member, changes UI to the mode most appropriate for this user
|
yellowfive@57
|
76 Loot = {}, -- the last loot seen by the master looter
|
yellowfive@57
|
77 LootGuid = nil, -- guid of the last unit looted by the master looter, will be "container" if there is no target
|
yellowfive@57
|
78 LootInProgress = false -- true if looting is currently in progress
|
yellowfive@57
|
79 }
|
yellowfive@57
|
80 },
|
yellowfive@57
|
81 profile = {
|
yellowfive@57
|
82 minimap = { -- minimap hide/show and position settings
|
yellowfive@57
|
83 hide = false
|
yellowfive@57
|
84 },
|
yellowfive@57
|
85 window = {}, -- main window position settings
|
yellowfive@57
|
86 lootWindow = {}, -- loot window position settings
|
yellowfive@57
|
87 shopWindow = {}, -- shopping list window position settings
|
yellowfive@57
|
88 options = {
|
yellowfive@57
|
89 autoGear = false, -- auto-equip saved gear sets when changing specs
|
yellowfive@61
|
90 shopAh = false, -- auto-show shopping list at AH
|
yellowfive@61
|
91 uiScale = 1 -- custom scale for AMR UI
|
yellowfive@57
|
92 },
|
yellowfive@57
|
93 Logging = { -- global logging settings
|
yellowfive@57
|
94 Auto = {} -- for each instanceId, for each difficultyId, true if auto-logging enabled
|
yellowfive@57
|
95 }
|
yellowfive@57
|
96 },
|
yellowfive@57
|
97 global = {
|
yellowfive@57
|
98 Region = nil, -- region that this user is in, all characters on the same account should be the same region
|
yellowfive@57
|
99 Shopping = {}, -- shopping list data stored globally for access on any character
|
yellowfive@57
|
100 Logging = { -- a lot of log data is stored globally for simplicity, can only be raiding with one character at a time
|
yellowfive@57
|
101 Wipes = {}, -- times that a wipe was called
|
yellowfive@57
|
102 PlayerData = {}, -- player data gathered at fight start
|
yellowfive@57
|
103 PlayerExtras = {} -- player extra data like auras, gathered at fight start
|
yellowfive@57
|
104 },
|
yellowfive@57
|
105 TeamOpt = { -- this stuff is stored globally in case a player e.g. switches to an alt in a raid group
|
yellowfive@57
|
106 LootGear = {}, -- gear info that needs to be transmitted with the next loot
|
yellowfive@57
|
107 Rankings = {}, -- last rankings imported by the loot ranker
|
yellowfive@57
|
108 RankingString = nil -- last ranking string imported, kept around for efficient serialization
|
yellowfive@57
|
109 }
|
yellowfive@57
|
110 }
|
yellowfive@57
|
111 }
|
yellowfive@57
|
112
|
yellowfive@57
|
113 -- set defaults for auto-logging
|
yellowfive@57
|
114 for i, instanceId in ipairs(Amr.InstanceIdsOrdered) do
|
yellowfive@57
|
115 local byDiff = defaults.profile.Logging.Auto[instanceId]
|
yellowfive@57
|
116 if not byDiff then
|
yellowfive@57
|
117 byDiff = {}
|
yellowfive@57
|
118 defaults.profile.Logging.Auto[instanceId] = byDiff
|
yellowfive@57
|
119 end
|
yellowfive@57
|
120
|
yellowfive@57
|
121 for k, difficultyId in pairs(Amr.Difficulties) do
|
yellowfive@57
|
122 if byDiff[difficultyId] == nil then
|
yellowfive@57
|
123 byDiff[difficultyId] = false
|
yellowfive@57
|
124 end
|
yellowfive@57
|
125 end
|
yellowfive@57
|
126 end
|
yellowfive@57
|
127
|
yellowfive@57
|
128 Amr.db = LibStub("AceDB-3.0"):New("AskMrRobotDb2", defaults)
|
yellowfive@57
|
129
|
yellowfive@57
|
130 Amr.db.RegisterCallback(Amr, "OnProfileChanged", "RefreshConfig")
|
yellowfive@57
|
131 Amr.db.RegisterCallback(Amr, "OnProfileCopied", "RefreshConfig")
|
yellowfive@57
|
132 Amr.db.RegisterCallback(Amr, "OnProfileReset", "RefreshConfig")
|
yellowfive@57
|
133 end
|
yellowfive@57
|
134
|
yellowfive@57
|
135 function Amr:OnInitialize()
|
yellowfive@57
|
136
|
yellowfive@57
|
137 initializeDb()
|
yellowfive@57
|
138
|
yellowfive@57
|
139 Amr:RegisterChatCommand("amr", "SlashCommand")
|
yellowfive@57
|
140
|
yellowfive@57
|
141 _icon:Register(Amr.ADDON_NAME, _amrLDB, self.db.profile.minimap)
|
yellowfive@57
|
142
|
yellowfive@57
|
143 -- listen for inter-addon communication
|
yellowfive@57
|
144 self:RegisterComm(Amr.ChatPrefix, "OnCommReceived")
|
yellowfive@57
|
145 end
|
yellowfive@57
|
146
|
yellowfive@57
|
147 local _enteredWorld = false
|
yellowfive@57
|
148 local _pendingInit = false
|
yellowfive@57
|
149
|
yellowfive@57
|
150 function finishInitialize()
|
yellowfive@57
|
151
|
yellowfive@57
|
152 -- record region, the only thing that we still can't get from the log file
|
yellowfive@57
|
153 Amr.db.global.Region = Amr.RegionNames[GetCurrentRegion()]
|
yellowfive@57
|
154
|
yellowfive@57
|
155 -- make sure that some initialization is deferred until after PLAYER_ENTERING_WORLD event so that data we need is available;
|
yellowfive@57
|
156 -- also delay this initialization for a few extra seconds to deal with some event spam that is otherwise hard to identify and ignore when a player logs in
|
yellowfive@57
|
157 Amr.Wait(5, function()
|
yellowfive@57
|
158 Amr:InitializeVersions()
|
yellowfive@57
|
159 Amr:InitializeGear()
|
yellowfive@57
|
160 Amr:InitializeExport()
|
yellowfive@57
|
161 Amr:InitializeCombatLog()
|
yellowfive@57
|
162 Amr:InitializeTeamOpt()
|
yellowfive@57
|
163 end)
|
yellowfive@57
|
164 end
|
yellowfive@57
|
165
|
yellowfive@57
|
166 function onPlayerEnteringWorld()
|
yellowfive@57
|
167
|
yellowfive@57
|
168 _enteredWorld = true
|
yellowfive@57
|
169
|
yellowfive@57
|
170 if _pendingInit then
|
yellowfive@57
|
171 finishInitialize()
|
yellowfive@57
|
172 _pendingInit = false
|
yellowfive@57
|
173 end
|
yellowfive@57
|
174 end
|
yellowfive@57
|
175
|
yellowfive@57
|
176 function Amr:OnEnable()
|
yellowfive@57
|
177
|
yellowfive@57
|
178 -- listen for changes to the snapshot enable state, and always make sure it is enabled if using the core AskMrRobot addon
|
yellowfive@57
|
179 self:RegisterMessage("AMR_SNAPSHOT_STATE_CHANGED", function(eventName, isEnabled)
|
yellowfive@57
|
180 if not isEnabled then
|
yellowfive@57
|
181 -- immediately re-enable on any attempt to disable
|
yellowfive@57
|
182 Amr.Serializer:EnableSnapshots()
|
yellowfive@57
|
183 end
|
yellowfive@57
|
184 end)
|
yellowfive@57
|
185 self.Serializer:EnableSnapshots()
|
yellowfive@57
|
186
|
yellowfive@57
|
187 -- update based on current configuration whenever enabled
|
yellowfive@57
|
188 self:RefreshConfig()
|
yellowfive@57
|
189
|
yellowfive@57
|
190 -- if we have fully entered the world, do initialization; otherwise wait for PLAYER_ENTERING_WORLD to continue
|
yellowfive@57
|
191 if not _enteredWorld then
|
yellowfive@57
|
192 _pendingInit = true
|
yellowfive@57
|
193 else
|
yellowfive@57
|
194 _pendingInit = false
|
yellowfive@57
|
195 finishInitialize()
|
yellowfive@57
|
196 end
|
yellowfive@57
|
197 end
|
yellowfive@57
|
198
|
yellowfive@57
|
199 function Amr:OnDisable()
|
yellowfive@57
|
200 -- disabling is not supported
|
yellowfive@57
|
201 end
|
yellowfive@57
|
202
|
yellowfive@57
|
203
|
yellowfive@57
|
204 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
205 -- Slash Commands
|
yellowfive@57
|
206 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
207 local _slashMethods = {
|
yellowfive@57
|
208 hide = "Hide",
|
yellowfive@57
|
209 show = "Show",
|
yellowfive@57
|
210 toggle = "Toggle",
|
yellowfive@57
|
211 equip = "EquipGearSet", -- parameter is "primary" or "secondary", or no parameter to toggle
|
yellowfive@57
|
212 version = "PrintVersions",
|
yellowfive@57
|
213 wipe = "Wipe",
|
yellowfive@57
|
214 undowipe = "UndoWipe",
|
yellowfive@61
|
215 reset = "Reset",
|
yellowfive@57
|
216 test = "Test"
|
yellowfive@57
|
217 }
|
yellowfive@57
|
218
|
yellowfive@57
|
219 function Amr:SlashCommand(input)
|
yellowfive@57
|
220 input = string.lower(input)
|
yellowfive@57
|
221 local parts = {}
|
yellowfive@57
|
222 for w in input:gmatch("%S+") do
|
yellowfive@57
|
223 table.insert(parts, w)
|
yellowfive@57
|
224 end
|
yellowfive@57
|
225
|
yellowfive@57
|
226 if #parts == 0 then return end
|
yellowfive@57
|
227
|
yellowfive@57
|
228 local func = _slashMethods[parts[1]]
|
yellowfive@57
|
229 if not func then return end
|
yellowfive@57
|
230
|
yellowfive@57
|
231 local funcArgs = {}
|
yellowfive@57
|
232 for i = 2, #parts do
|
yellowfive@57
|
233 table.insert(funcArgs, parts[i])
|
yellowfive@57
|
234 end
|
yellowfive@57
|
235
|
yellowfive@57
|
236 Amr[func](Amr, unpack(funcArgs))
|
yellowfive@57
|
237 end
|
yellowfive@57
|
238
|
yellowfive@57
|
239
|
yellowfive@57
|
240 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
241 -- Configuration
|
yellowfive@57
|
242 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
243
|
yellowfive@57
|
244 -- refresh all state based on the current values of configuration options
|
yellowfive@57
|
245 function Amr:RefreshConfig()
|
yellowfive@57
|
246
|
yellowfive@57
|
247 self:UpdateMinimap()
|
yellowfive@57
|
248 self:RefreshOptionsUi()
|
yellowfive@57
|
249 self:RefreshLogUi()
|
yellowfive@57
|
250 end
|
yellowfive@57
|
251
|
yellowfive@57
|
252 function Amr:UpdateMinimap()
|
yellowfive@57
|
253
|
yellowfive@57
|
254 if self.db.profile.minimap.hide or not Amr:IsEnabled() then
|
yellowfive@57
|
255 _icon:Hide(Amr.ADDON_NAME)
|
yellowfive@57
|
256 else
|
yellowfive@57
|
257 -- change icon color if logging
|
yellowfive@57
|
258 if Amr:IsLogging() then
|
yellowfive@57
|
259 _amrLDB.icon = 'Interface\\AddOns\\AskMrRobot\\Media\\icon_green'
|
yellowfive@57
|
260 else
|
yellowfive@57
|
261 _amrLDB.icon = 'Interface\\AddOns\\AskMrRobot\\Media\\icon'
|
yellowfive@57
|
262 end
|
yellowfive@57
|
263
|
yellowfive@57
|
264 _icon:Show(Amr.ADDON_NAME)
|
yellowfive@57
|
265 end
|
yellowfive@57
|
266 end
|
yellowfive@57
|
267
|
yellowfive@57
|
268
|
yellowfive@57
|
269 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
270 -- Version Checking
|
yellowfive@57
|
271 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
272
|
yellowfive@57
|
273 -- version of addon being run by each person in the player's raid or group
|
yellowfive@57
|
274 Amr.GroupVersions = {}
|
yellowfive@57
|
275
|
yellowfive@57
|
276 local function toGroupVersionKey(realm, name)
|
yellowfive@57
|
277 realm = string.gsub(realm, "%s+", "")
|
yellowfive@57
|
278 return name .. "-" .. realm
|
yellowfive@57
|
279 end
|
yellowfive@57
|
280
|
yellowfive@57
|
281 -- prune out version information for players no longer in the current raid group
|
yellowfive@57
|
282 local function pruneVersionInfo()
|
yellowfive@57
|
283
|
yellowfive@57
|
284 local newVersions = {}
|
yellowfive@57
|
285 local units = Amr:GetGroupUnitIdentifiers()
|
yellowfive@57
|
286
|
yellowfive@57
|
287 for i, unitId in ipairs(units) do
|
yellowfive@57
|
288 local realm, name = Amr:GetRealmAndName(unitId)
|
yellowfive@57
|
289 if realm then
|
yellowfive@57
|
290 local key = toGroupVersionKey(realm, name)
|
yellowfive@57
|
291 newVersions[key] = Amr.GroupVersions[key]
|
yellowfive@57
|
292 end
|
yellowfive@57
|
293 end
|
yellowfive@57
|
294
|
yellowfive@57
|
295 Amr.GroupVersions = newVersions
|
yellowfive@57
|
296 end
|
yellowfive@57
|
297
|
yellowfive@57
|
298 -- send version information to other people in the same raid group
|
yellowfive@57
|
299 local function sendVersionInfo()
|
yellowfive@57
|
300
|
yellowfive@57
|
301 local realm = GetRealmName()
|
yellowfive@57
|
302 local name = UnitName("player")
|
yellowfive@57
|
303 local ver = GetAddOnMetadata(Amr.ADDON_NAME, "Version")
|
yellowfive@57
|
304
|
yellowfive@57
|
305 local msg = string.format("%s\n%s\n%s\n%s", Amr.MessageTypes.Version, realm, name, ver)
|
yellowfive@57
|
306 Amr:SendAmrCommMessage(msg)
|
yellowfive@57
|
307 end
|
yellowfive@57
|
308
|
yellowfive@57
|
309 local function onVersionInfoReceived(message)
|
yellowfive@57
|
310
|
yellowfive@57
|
311 -- message will be of format: realm\nname\nversion
|
yellowfive@57
|
312 local parts = {}
|
yellowfive@57
|
313 for part in string.gmatch(message, "([^\n]+)") do
|
yellowfive@57
|
314 table.insert(parts, part)
|
yellowfive@57
|
315 end
|
yellowfive@57
|
316
|
yellowfive@57
|
317 local key = toGroupVersionKey(parts[2], parts[3])
|
yellowfive@57
|
318 local ver = parts[4]
|
yellowfive@57
|
319
|
yellowfive@57
|
320 Amr.GroupVersions[key] = tonumber(ver)
|
yellowfive@57
|
321
|
yellowfive@57
|
322 -- make sure that versions are properly pruned in case this message arrived late and the player has since been removed from the group
|
yellowfive@57
|
323 pruneVersionInfo()
|
yellowfive@57
|
324 end
|
yellowfive@57
|
325
|
yellowfive@57
|
326 -- get the addon version another person in the player's raid/group is running, or 0 if they are not running the addon
|
yellowfive@57
|
327 function Amr:GetAddonVersion(realm, name)
|
yellowfive@57
|
328 local ver = Amr.GroupVersions[toGroupVersionKey(realm, name)]
|
yellowfive@57
|
329 return ver or 0
|
yellowfive@57
|
330 end
|
yellowfive@57
|
331
|
yellowfive@57
|
332 function Amr:PrintVersions()
|
yellowfive@57
|
333
|
yellowfive@57
|
334 if not IsInGroup() and not IsInRaid() then
|
yellowfive@57
|
335 self:Print(L.VersionChatNotGrouped)
|
yellowfive@57
|
336 return
|
yellowfive@57
|
337 end
|
yellowfive@57
|
338
|
yellowfive@57
|
339 local units = self:GetGroupUnitIdentifiers()
|
yellowfive@57
|
340
|
yellowfive@57
|
341 local msg = {}
|
yellowfive@57
|
342 table.insert(msg, L.VersionChatTitle)
|
yellowfive@57
|
343
|
yellowfive@57
|
344 for i, unitId in ipairs(units) do
|
yellowfive@57
|
345 local realm, name = self:GetRealmAndName(unitId)
|
yellowfive@57
|
346 if realm then
|
yellowfive@57
|
347 local key = toGroupVersionKey(realm, name)
|
yellowfive@57
|
348 local ver = Amr.GroupVersions[key]
|
yellowfive@57
|
349 if not ver then
|
yellowfive@57
|
350 table.insert(msg, key .. " |cFFFF0000" .. L.VersionChatNotInstalled .. "|r")
|
yellowfive@57
|
351 else
|
yellowfive@57
|
352 table.insert(msg, key .. " v" .. ver)
|
yellowfive@57
|
353 end
|
yellowfive@57
|
354 end
|
yellowfive@57
|
355 end
|
yellowfive@57
|
356
|
yellowfive@57
|
357 msg = table.concat(msg, "\n")
|
yellowfive@57
|
358 print(msg)
|
yellowfive@57
|
359 end
|
yellowfive@57
|
360
|
yellowfive@57
|
361 function Amr:InitializeVersions()
|
yellowfive@57
|
362 Amr:AddEventHandler("GROUP_ROSTER_UPDATE", pruneVersionInfo)
|
yellowfive@57
|
363 Amr:AddEventHandler("GROUP_ROSTER_UPDATE", sendVersionInfo)
|
yellowfive@57
|
364
|
yellowfive@57
|
365 -- request version information from anyone in my group upon initialization
|
yellowfive@57
|
366 if IsInGroup() or IsInRaid() then
|
yellowfive@57
|
367 Amr:SendAmrCommMessage(Amr.MessageTypes.VersionRequest)
|
yellowfive@57
|
368 end
|
yellowfive@57
|
369 end
|
yellowfive@57
|
370
|
yellowfive@57
|
371
|
yellowfive@57
|
372 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
373 -- Generic Helpers
|
yellowfive@57
|
374 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
375
|
yellowfive@57
|
376 local _waitTable = {}
|
yellowfive@57
|
377 local _waitFrame = nil
|
yellowfive@57
|
378
|
yellowfive@57
|
379 -- execute the specified function after the specified delay (in seconds)
|
yellowfive@57
|
380 function Amr.Wait(delay, func, ...)
|
yellowfive@57
|
381 if not _waitFrame then
|
yellowfive@57
|
382 _waitFrame = CreateFrame("Frame", "AmrWaitFrame", UIParent)
|
yellowfive@57
|
383 _waitFrame:SetScript("OnUpdate", function (self, elapse)
|
yellowfive@57
|
384 local count = #_waitTable
|
yellowfive@57
|
385 local i = 1
|
yellowfive@57
|
386 while(i <= count) do
|
yellowfive@57
|
387 local waitRecord = table.remove(_waitTable, i)
|
yellowfive@57
|
388 local d = table.remove(waitRecord, 1)
|
yellowfive@57
|
389 local f = table.remove(waitRecord, 1)
|
yellowfive@57
|
390 local p = table.remove(waitRecord, 1)
|
yellowfive@57
|
391 if d > elapse then
|
yellowfive@57
|
392 table.insert(_waitTable, i, { d-elapse, f, p })
|
yellowfive@57
|
393 i = i + 1
|
yellowfive@57
|
394 else
|
yellowfive@57
|
395 count = count - 1
|
yellowfive@57
|
396 f(unpack(p))
|
yellowfive@57
|
397 end
|
yellowfive@57
|
398 end
|
yellowfive@57
|
399 end)
|
yellowfive@57
|
400 end
|
yellowfive@57
|
401 table.insert(_waitTable, { delay, func, {...} })
|
yellowfive@57
|
402 return true
|
yellowfive@57
|
403 end
|
yellowfive@57
|
404
|
yellowfive@57
|
405 -- helper to iterate over a table in order by its keys
|
yellowfive@57
|
406 function Amr.spairs(t, order)
|
yellowfive@57
|
407 -- collect the keys
|
yellowfive@57
|
408 local keys = {}
|
yellowfive@57
|
409 for k in pairs(t) do keys[#keys+1] = k end
|
yellowfive@57
|
410
|
yellowfive@57
|
411 -- if order function given, sort by it by passing the table and keys a, b,
|
yellowfive@57
|
412 -- otherwise just sort the keys
|
yellowfive@57
|
413 if order then
|
yellowfive@57
|
414 table.sort(keys, function(a,b) return order(t, a, b) end)
|
yellowfive@57
|
415 else
|
yellowfive@57
|
416 table.sort(keys)
|
yellowfive@57
|
417 end
|
yellowfive@57
|
418
|
yellowfive@57
|
419 -- return the iterator function
|
yellowfive@57
|
420 local i = 0
|
yellowfive@57
|
421 return function()
|
yellowfive@57
|
422 i = i + 1
|
yellowfive@57
|
423 if keys[i] then
|
yellowfive@57
|
424 return keys[i], t[keys[i]]
|
yellowfive@57
|
425 end
|
yellowfive@57
|
426 end
|
yellowfive@57
|
427 end
|
yellowfive@57
|
428
|
yellowfive@57
|
429 function Amr.StartsWith(str, prefix)
|
yellowfive@57
|
430 if string.len(str) < string.len(prefix) then return false end
|
yellowfive@57
|
431 return string.sub(str, 1, string.len(prefix)) == prefix
|
yellowfive@57
|
432 end
|
yellowfive@57
|
433
|
yellowfive@57
|
434 -- helper to get the unit identifiers (e.g. to pass to GetUnitName) for all members of the player's current group/raid
|
yellowfive@57
|
435 function Amr:GetGroupUnitIdentifiers()
|
yellowfive@57
|
436
|
yellowfive@57
|
437 local units = {}
|
yellowfive@57
|
438 if IsInRaid() then
|
yellowfive@57
|
439 for i = 1,40 do
|
yellowfive@57
|
440 table.insert(units, "raid" .. i)
|
yellowfive@57
|
441 end
|
yellowfive@57
|
442 elseif IsInGroup() then
|
yellowfive@57
|
443 table.insert(units, "player")
|
yellowfive@57
|
444 for i = 1,4 do
|
yellowfive@57
|
445 table.insert(units, "party" .. i)
|
yellowfive@57
|
446 end
|
yellowfive@57
|
447 else
|
yellowfive@57
|
448 table.insert(units, "player")
|
yellowfive@57
|
449 end
|
yellowfive@57
|
450
|
yellowfive@57
|
451 return units
|
yellowfive@57
|
452 end
|
yellowfive@57
|
453
|
yellowfive@57
|
454 -- helper to get the realm and name from a unitId (e.g. "player" or "raid1")
|
yellowfive@57
|
455 function Amr:GetRealmAndName(unitId)
|
yellowfive@57
|
456
|
yellowfive@57
|
457 local name = GetUnitName(unitId, true)
|
yellowfive@57
|
458 if not name then return end
|
yellowfive@57
|
459
|
yellowfive@57
|
460 local realm = GetRealmName()
|
yellowfive@57
|
461 local splitPos = string.find(name, "-")
|
yellowfive@57
|
462 if splitPos ~= nil then
|
yellowfive@57
|
463 realm = string.sub(name, splitPos + 1)
|
yellowfive@57
|
464 name = string.sub(name, 1, splitPos - 1)
|
yellowfive@57
|
465 end
|
yellowfive@57
|
466
|
yellowfive@57
|
467 return realm, name
|
yellowfive@57
|
468 end
|
yellowfive@57
|
469
|
yellowfive@57
|
470 -- find the unitid of a player given the name and realm... this comes from the server so the realm will be in english...
|
yellowfive@57
|
471 -- TODO: more robust handling of players with same name but different realms in the same group on non-english clients
|
yellowfive@57
|
472 function Amr:GetUnitId(unitRealm, unitName)
|
yellowfive@57
|
473
|
yellowfive@57
|
474 local nameMatches = {}
|
yellowfive@57
|
475
|
yellowfive@57
|
476 local units = Amr:GetGroupUnitIdentifiers()
|
yellowfive@57
|
477 for i, unitId in ipairs(units) do
|
yellowfive@57
|
478 local realm, name = Amr:GetRealmAndName(unitId)
|
yellowfive@57
|
479 if realm then
|
yellowfive@57
|
480 -- remove spaces to ensure proper matches
|
yellowfive@57
|
481 realm = string.gsub(realm, "%s+", "")
|
yellowfive@57
|
482 unitRealm = string.gsub(unitRealm, "%s+", "")
|
yellowfive@57
|
483
|
yellowfive@57
|
484 if unitRealm == realm and unitName == name then return unitId end
|
yellowfive@57
|
485 if unitName == name then
|
yellowfive@57
|
486 table.insert(nameMatches, unitId)
|
yellowfive@57
|
487 end
|
yellowfive@57
|
488 end
|
yellowfive@57
|
489 end
|
yellowfive@57
|
490
|
yellowfive@57
|
491 -- only one player with same name, must be the player of interest
|
yellowfive@57
|
492 if #nameMatches == 1 then return nameMatches[1] end
|
yellowfive@57
|
493
|
yellowfive@57
|
494 -- could not find or ambiguous
|
yellowfive@57
|
495 return nil
|
yellowfive@57
|
496 end
|
yellowfive@57
|
497
|
yellowfive@57
|
498
|
yellowfive@57
|
499 -- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
|
yellowfive@57
|
500 -- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
|
yellowfive@57
|
501 local _scanTt
|
yellowfive@57
|
502 function Amr:GetScanningTooltip()
|
yellowfive@57
|
503 if not _scanTt then
|
yellowfive@57
|
504 _scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
|
yellowfive@57
|
505 _scanTt:SetOwner(UIParent, "ANCHOR_NONE")
|
yellowfive@57
|
506 end
|
yellowfive@57
|
507 return _scanTt
|
yellowfive@57
|
508 end
|
yellowfive@57
|
509
|
yellowfive@57
|
510 local function scanTooltipHelper(txt, ...)
|
yellowfive@57
|
511 for i = 1, select("#", ...) do
|
yellowfive@57
|
512 local region = select(i, ...)
|
yellowfive@57
|
513 if region and region:GetObjectType() == "FontString" then
|
yellowfive@57
|
514 local text = region:GetText() -- string or nil
|
yellowfive@57
|
515 print(text)
|
yellowfive@57
|
516 end
|
yellowfive@57
|
517 end
|
yellowfive@57
|
518 end
|
yellowfive@57
|
519
|
yellowfive@57
|
520 -- search the tooltip for txt, returns true if it is encountered on any line
|
yellowfive@57
|
521 function Amr:IsTextInTooltip(tt, txt)
|
yellowfive@57
|
522 local regions = { tt:GetRegions() }
|
yellowfive@57
|
523 for i, region in ipairs(regions) do
|
yellowfive@57
|
524 if region and region:GetObjectType() == "FontString" then
|
yellowfive@57
|
525 if region:GetText() == txt then
|
yellowfive@57
|
526 return true
|
yellowfive@57
|
527 end
|
yellowfive@57
|
528 end
|
yellowfive@57
|
529 end
|
yellowfive@57
|
530 return false
|
yellowfive@57
|
531 end
|
yellowfive@57
|
532
|
yellowfive@59
|
533 -- helper to determine if we can equip an item (it is already soulbound or account bound)
|
yellowfive@59
|
534 function Amr:CanEquip(bagId, slotId)
|
yellowfive@57
|
535 local tt = self:GetScanningTooltip()
|
yellowfive@57
|
536 tt:ClearLines()
|
yellowfive@57
|
537 if bagId then
|
yellowfive@57
|
538 tt:SetBagItem(bagId, slotId)
|
yellowfive@57
|
539 else
|
yellowfive@57
|
540 tt:SetInventoryItem("player", slotId)
|
yellowfive@57
|
541 end
|
yellowfive@59
|
542 if self:IsTextInTooltip(tt, ITEM_SOULBOUND) then return true end
|
yellowfive@59
|
543 if self:IsTextInTooltip(tt, ITEM_BNETACCOUNTBOUND) then return true end
|
yellowfive@59
|
544 if self:IsTextInTooltip(tt, ITEM_ACCOUNTBOUND) then return true end
|
yellowfive@57
|
545 end
|
yellowfive@57
|
546
|
yellowfive@57
|
547 -- helper to determine if an item has a unique constraint
|
yellowfive@57
|
548 function Amr:IsUnique(bagId, slotId)
|
yellowfive@57
|
549 local tt = self:GetScanningTooltip()
|
yellowfive@57
|
550 tt:ClearLines()
|
yellowfive@57
|
551 if bagId then
|
yellowfive@57
|
552 tt:SetBagItem(bagId, slotId)
|
yellowfive@57
|
553 else
|
yellowfive@57
|
554 tt:SetInventoryItem("player", slotId)
|
yellowfive@57
|
555 end
|
yellowfive@57
|
556 if self:IsTextInTooltip(tt, ITEM_UNIQUE_EQUIPPABLE) then return true end
|
yellowfive@57
|
557 if self:IsTextInTooltip(tt, ITEM_UNIQUE) then return true end
|
yellowfive@57
|
558 return false
|
yellowfive@57
|
559 end
|
yellowfive@57
|
560
|
yellowfive@57
|
561
|
yellowfive@57
|
562 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
563 -- Inter-Addon Communication
|
yellowfive@57
|
564 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
565 function Amr:SendAmrCommMessage(message, channel)
|
yellowfive@57
|
566 -- prepend version to all messages
|
yellowfive@57
|
567 local v = GetAddOnMetadata(Amr.ADDON_NAME, "Version")
|
yellowfive@57
|
568 message = v .. "\r" .. message
|
yellowfive@57
|
569
|
yellowfive@79
|
570 Amr:SendCommMessage(Amr.ChatPrefix, message, channel or (IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or "RAID"))
|
yellowfive@57
|
571 end
|
yellowfive@57
|
572
|
yellowfive@57
|
573 function Amr:OnCommReceived(prefix, message, distribution, sender)
|
yellowfive@57
|
574
|
yellowfive@57
|
575 local parts = {}
|
yellowfive@57
|
576 for part in string.gmatch(message, "([^\r]+)") do
|
yellowfive@57
|
577 table.insert(parts, part)
|
yellowfive@57
|
578 end
|
yellowfive@57
|
579
|
yellowfive@57
|
580 local ver = parts[1]
|
yellowfive@57
|
581 if ver then ver = tonumber(ver) end
|
yellowfive@57
|
582 if ver then
|
yellowfive@57
|
583 -- newest versions of the addon start all messages with a version number
|
yellowfive@57
|
584 message = parts[2]
|
yellowfive@57
|
585 end
|
yellowfive@57
|
586
|
yellowfive@57
|
587 -- we always allow version checks, even from old versions of the addon that aren't otherwise compatible
|
yellowfive@57
|
588 if Amr.StartsWith(message, Amr.MessageTypes.Version) then
|
yellowfive@57
|
589 -- version checking between group members
|
yellowfive@57
|
590 if Amr.StartsWith(message, Amr.MessageTypes.VersionRequest) then
|
yellowfive@57
|
591 sendVersionInfo()
|
yellowfive@57
|
592 else
|
yellowfive@57
|
593 onVersionInfoReceived(message)
|
yellowfive@57
|
594 end
|
yellowfive@57
|
595
|
yellowfive@57
|
596 return
|
yellowfive@57
|
597 end
|
yellowfive@57
|
598
|
yellowfive@57
|
599 -- any other kind of message is ignored if the version is too old
|
yellowfive@57
|
600 if not ver or ver < Amr.MIN_ADDON_VERSION then return end
|
yellowfive@57
|
601
|
yellowfive@57
|
602 if Amr.StartsWith(message, Amr.MessageTypes.Team) then
|
yellowfive@57
|
603 -- if fully initialized, process team optimizer messages
|
yellowfive@57
|
604 if Amr["ProcessTeamMessage"] then
|
yellowfive@57
|
605 Amr:ProcessTeamMessage(message)
|
yellowfive@57
|
606 end
|
yellowfive@57
|
607 else
|
yellowfive@57
|
608 -- if we are fully loaded, process a player snapshot when it is received (combat logging)
|
yellowfive@57
|
609 if Amr["ProcessPlayerSnapshot"] then
|
yellowfive@57
|
610 self:ProcessPlayerSnapshot(message)
|
yellowfive@57
|
611 end
|
yellowfive@57
|
612 end
|
yellowfive@57
|
613 end
|
yellowfive@57
|
614
|
yellowfive@57
|
615
|
yellowfive@57
|
616 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
617 -- Events
|
yellowfive@57
|
618 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
619 local _eventHandlers = {}
|
yellowfive@57
|
620
|
yellowfive@57
|
621 local function handleEvent(eventName, ...)
|
yellowfive@57
|
622 local list = _eventHandlers[eventName]
|
yellowfive@57
|
623 if list then
|
yellowfive@57
|
624 --print(eventName .. " handled")
|
yellowfive@57
|
625 for i, handler in ipairs(list) do
|
yellowfive@57
|
626 if type(handler) == "function" then
|
yellowfive@57
|
627 handler(select(1, ...))
|
yellowfive@57
|
628 else
|
yellowfive@57
|
629 Amr[handler](Amr, select(1, ...))
|
yellowfive@57
|
630 end
|
yellowfive@57
|
631 end
|
yellowfive@57
|
632 end
|
yellowfive@57
|
633 end
|
yellowfive@57
|
634
|
yellowfive@57
|
635 -- WoW and Ace seem to work on a "one handler" kind of approach to events (as far as I can tell from the sparse documentation of both).
|
yellowfive@57
|
636 -- This is a simple wrapper to allow adding multiple handlers to the same event, thus allowing better encapsulation of code from file to file.
|
yellowfive@57
|
637 function Amr:AddEventHandler(eventName, methodOrName)
|
yellowfive@57
|
638 local list = _eventHandlers[eventName]
|
yellowfive@57
|
639 if not list then
|
yellowfive@57
|
640 list = {}
|
yellowfive@57
|
641 _eventHandlers[eventName] = list
|
yellowfive@57
|
642 Amr:RegisterEvent(eventName, handleEvent)
|
yellowfive@57
|
643 end
|
yellowfive@57
|
644 table.insert(list, methodOrName)
|
yellowfive@57
|
645 end
|
yellowfive@57
|
646
|
yellowfive@57
|
647 Amr:AddEventHandler("PLAYER_ENTERING_WORLD", onPlayerEnteringWorld)
|
yellowfive@57
|
648
|
yellowfive@57
|
649
|
yellowfive@57
|
650 ----------------------------------------------------------------------------------------
|
yellowfive@57
|
651 -- Debugging
|
yellowfive@57
|
652 ----------------------------------------------------------------------------------------
|
yellowfive@69
|
653 function Amr:Test()
|
yellowfive@69
|
654
|
yellowfive@69
|
655 local s = "|cff0070dd|Hitem:127224:5337:0:0:0:0:0:0:100:105:512:22:2:615:656:100|h[Staff of Polarities]|h|r"
|
yellowfive@69
|
656 Amr.GetItemInfo(s, function(obj, name, link, quality, iLevel)
|
yellowfive@69
|
657 print(iLevel)
|
yellowfive@69
|
658 end)
|
yellowfive@69
|
659 end
|
yellowfive@69
|
660
|
yellowfive@57
|
661 --[[
|
yellowfive@57
|
662 function Amr:Test(val1, val2, val3)
|
yellowfive@57
|
663
|
yellowfive@57
|
664 local link = GetLootSlotLink(tonumber(val1))
|
yellowfive@57
|
665 local index = Amr:TestLootIndex(link)
|
yellowfive@57
|
666 print("loot index: " .. index)
|
yellowfive@57
|
667
|
yellowfive@57
|
668 if val2 then
|
yellowfive@57
|
669 local candidate = Amr:TestLootCandidate(link, val2, val3)
|
yellowfive@57
|
670 print("loot candidate: " .. candidate)
|
yellowfive@57
|
671
|
yellowfive@57
|
672 GiveMasterLoot(index, candidate)
|
yellowfive@57
|
673 end
|
yellowfive@57
|
674 end
|
yellowfive@57
|
675 ]] |