forked from Mirroar/TopFit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTopFit.lua
406 lines (351 loc) · 12.4 KB
/
TopFit.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
local addonName, ns = ...
LibStub('AceAddon-3.0'):NewAddon(ns, addonName, 'AceEvent-3.0')
-- create global Addon object
_G[addonName] = ns
-- TODO: this should not be
-- GLOBALS: TopFit, TopFitDB
-- GLOBALS: _G, LibStub, C_Timer, SLASH_TopFit1, SLASH_TopFit2, SLASH_TopFit3, GameTooltip, DEFAULT_CHAT_FRAME, UIParent, NUM_BAG_SLOTS, InterfaceOptionsFrame_OpenToCategory, CreateFrame, ToggleFrame
-- GLOBALS: TOPFIT_ARMORTYPE_CLOTH, TOPFIT_ARMORTYPE_LEATHER, TOPFIT_ARMORTYPE_MAIL, TOPFIT_ARMORTYPE_PLATE, TOPFIT_ITEM_MOD_MAINHAND, TOPFIT_ITEM_MOD_OFFHAND
-- GLOBALS: GetEquipmentSetInfoByName, SaveEquipmentSet, GetRealmName, GetActiveSpecGroup, GetSpecializationInfo, GetAuctionItemSubClasses, CanUseEquipmentSets, UseEquipmentSet, IsEquippableItem, GetContainerNumSlots, GetContainerItemLink, GetInventoryItemLink, GetInventorySlotInfo
-- GLOBALS: setmetatable, getmetatable, type, pairs, assert, error, wipe, tinsert, next, select, tonumber, tContains
--TODO: replace old ugly set_i set IDs with simple numbers - in profile.sets
function ns:GenerateSetName(name) --TODO: ideally move this function into set class
-- using substr because blizzard interface only allows 16 characters
-- although technically SaveEquipmentSet & co allow more
return (((name ~= nil) and (name.." "):sub(1, 12).."(TF)") or "TopFit") --TODO: clean up
end
function TopFit.ChatCommand(input)
if not input or input:trim() == "" or input:trim():lower() == "show" then
ns.ui.ToggleTopFitConfigFrame()
elseif input:trim():lower() == "options" or input:trim():lower() == "conf" or input:trim():lower() == "config" then
InterfaceOptionsFrame_OpenToCategory(addonName)
InterfaceOptionsFrame_OpenToCategory(addonName)
else
TopFit:Print(TopFit.locale.SlashHelp)
end
end
SLASH_TopFit1 = "/topfit"
SLASH_TopFit2 = "/tf"
SLASH_TopFit3 = "/fit"
SlashCmdList["TopFit"] = TopFit.ChatCommand
function ns:OnEnable()
ns:PrepareDatabase()
-- load common libraries
ns.Unfit = LibStub('Unfit-1.0')
ns.ItemLocations = LibStub('LibItemLocations')
-- create gametooltip for scanning
ns.scanTooltip = CreateFrame('GameTooltip', addonName..'ScanTooltip', UIParent, 'GameTooltipTemplate')
-- launcher ldb
local ldb = LibStub('LibDataBroker-1.1'):NewDataObject(addonName, {
type = 'launcher',
icon = 'Interface\\Icons\\Achievement_BG_trueAVshutout',
label = addonName,
OnClick = function(button, btn, up)
if btn == 'RightButton' then
InterfaceOptionsFrame_OpenToCategory(addonName)
InterfaceOptionsFrame_OpenToCategory(addonName)
else
ns.ui.ToggleTopFitConfigFrame()
end
end,
OnTooltipShow = function(tooltip)
tooltip:AddLine(addonName)
local c = GRAY_FONT_COLOR
tooltip:AddLine(ns.locale.OpenSetConfigTooltip, c.r, c.g, c.b)
tooltip:AddLine(ns.locale.OpenAddonConfigTooltip, c.r, c.g, c.b)
end
})
ns.minimapIcon = LibStub('LibDBIcon-1.0')
ns.minimapIcon:Register(addonName, ldb, self.db.profile.minimapIcon)
-- select current auto-update set by default
ns:SetSelectedSet()
-- create frame for OnUpdate
ns.updateFrame = CreateFrame("Frame")
-- create options
ns:createOptions()
-- cache tables
ns.itemsCache = {}
ns.scoresCache = {}
-- table for equippable item list
ns.equippableItems = {}
-- register needed events
ns:RegisterEvent('PLAYER_LEVEL_UP')
ns:RegisterEvent('ACTIVE_TALENT_GROUP_CHANGED')
ns:RegisterEvent('PLAYER_EQUIPMENT_CHANGED')
ns:RegisterEvent('BAG_UPDATE')
ns:RegisterEvent('BAG_UPDATE_DELAYED')
-- self:RegisterEvent('PLAYER_TALENT_UPDATE') -- TODO: currently unused
-- wait 50ms until we do our first calculation
C_Timer.After(0.05, function()
ns:updateItemsCache()
ns.CheckInventoryItems()
ns.CheckBagItems()
ns.ResetNewItems()
ns.canAutoCalculate = true
end)
-- container for plugin information and frames
ns.plugins = {}
-- initialize delete set popup
StaticPopupDialogs["TOPFIT_DELETESET"] = {
text = CONFIRM_DELETE_EQUIPMENT_SET,
button1 = OKAY,
button2 = CANCEL,
OnAccept = function(self)
ns:DeleteSet(ns.currentlyDeletingSetID)
ns:SetSelectedSet()
end,
timeout = 0,
whileDead = true,
hideOnEscape = true,
preferredIndex = 3
}
-- we're done initializing
ns.initialized = true
-- initialize all registered plugins
if not ns.currentPlugins then ns.currentPlugins = {} end
for _, plugin in pairs(ns.currentPlugins) do
plugin:Initialize()
end
-- if enabled, automatically run tests
if ns.db.profile.autoRunTests and ns.testsLoaded then
wowUnit:StartTests(ns)
end
end
function ns.IsInitialized()
return ns.initialized
end
-- checks items that just entered the player's inventory to evaluate whether auto-calculation might be necessary
local function EvaluateNewItems(newItems)
local set = ns.GetCurrentAutoUpdateSet()
if not set then return end
-- new equippable item in inventory, check if it is actually better than anything currently available
for _, newItem in pairs(newItems) do
ns:Debug("New Item: "..newItem)
local itemTable = ns:GetCachedItem(newItem)
for _, slotID in pairs(itemTable.equipLocationsByType) do
-- try to get the currently used item from the player's equipment set
local setItem = set:GetItemInSlot(slotID)
local setItemTable = ns:GetCachedItem(setItem)
if setItem and setItemTable then
-- if either score or any cap is higher than currently equipped, calculate
if set:GetItemScore(newItem) > set:GetItemScore(setItem) then
ns:Debug('Higher Score!')
ns:RunAutoUpdate(true)
return
else
-- check caps
for stat, cap in pairs(set:GetHardCaps()) do
if (itemTable.totalBonus[stat] or 0) > (setItemTable.totalBonus[stat] or 0) then
ns:Debug('Higher Cap!')
ns:RunAutoUpdate(true)
return
end
end
end
else
-- no item found in set, good reason to upgrade!
TopFit:Debug('No Item in base set found!')
TopFit:RunAutoUpdate(true)
return
end
end
end
end
function ns:PLAYER_EQUIPMENT_CHANGED(event, ...)
ns.CheckInventoryItems(...)
end
function ns:BAG_UPDATE(event, ...)
-- update item location information
ns.CheckBagItems(...)
end
function ns:BAG_UPDATE_DELAYED(event, ...)
if not ns.canAutoCalculate then return end
-- update item list
--ns:updateItemsCache() --TODO: check if necessary
-- check inventory for new equippable items
local newEquip = ns.GetNewItems()
ns.ResetNewItems()
if #newEquip > 0 then
EvaluateNewItems(newEquip)
end
end
function ns:PLAYER_LEVEL_UP(event, ...)
--[[ remove cache info for heirlooms so they are rescanned
for itemLink, itemTable in pairs(ns.itemsCache) do
if itemTable.itemQuality == 7 then
ns.itemsCache[itemLink] = nil
ns.scoresCache[itemLink] = nil
end
end--]]
-- if an auto-update-set is set, update that as well
ns:ClearCache()
ns:RunAutoUpdate()
end
function ns:ACTIVE_TALENT_GROUP_CHANGED(event, ...)
ns:ClearCache()
ns:AutoEquipSet()
end
function ns:RunAutoUpdate(skipDelay)
local set = ns.GetCurrentAutoUpdateSet(true)
if not set then return end
ns.workSetList = ns.workSetList or {}
tinsert(ns.workSetList, set)
if not ns.autoUpdateTimerFrame then
ns.autoUpdateTimerFrame = CreateFrame("Frame")
end
-- because right on level up there seem to be problems finding the items for equipping, delay the actual update
if not skipDelay then
ns.delayCalculation = 0.5 -- delay in seconds until update
else
ns.delayCalculation = 0
end
ns.autoUpdateTimerFrame:SetScript("OnUpdate", function(self, delay)
if (ns.delayCalculation > 0) then
ns.delayCalculation = ns.delayCalculation - delay
else
ns.autoUpdateTimerFrame:SetScript("OnUpdate", nil)
ns:CalculateSets(true) -- calculate silently
end
end)
end
function ns:AutoEquipSet()
local set, setID = ns.GetCurrentAutoEquipSet()
if setID then
ns:SetSelectedSet(setID)
local equipSet = set:GetEquipmentSetName()
UseEquipmentSet(equipSet)
end
end
function ns:CreateEquipmentManagerSet(set)
if (CanUseEquipmentSets()) then
local setName = ns:GenerateSetName(set:GetName())
local texture = set:GetIconTexture():gsub("Interface\\Icons\\", "")
ns:Debug("Trying to create set: "..setName..", "..(texture or "nil"))
SaveEquipmentSet(setName, texture)
end
end
-----------------------------------------------------
-- database access functions
-----------------------------------------------------
function ns:SetSelectedSet(setID)
-- select current auto-equip set by default
if not setID then
local set, setCode = ns.GetCurrentAutoEquipSet(true)
if setCode then
setID = setCode
end
end
if not setID then
-- if still no set is selected, select first available set instead
for _, id in pairs(ns.GetSetList()) do
setID = id
break
end
end
ns.selectedSet = setID
if TopFitSetDropDown then
UIDropDownMenu_SetSelectedValue(TopFitSetDropDown, ns.selectedSet)
if ns.selectedSet then
local set = ns.GetSetByID(ns.selectedSet, true)
UIDropDownMenu_SetText(TopFitSetDropDown, set:GetName())
else
UIDropDownMenu_SetText(TopFitSetDropDown, ns.locale.NoSetTitle)
end
end
if TopFitSidebarCalculateButton then
if not setID then
TopFitSidebarCalculateButton:Disable()
else
TopFitSidebarCalculateButton:Enable()
end
end
ns.ui.UpdateForcedSlotIndicators() -- character frame ui
ns.ui.Update(true) -- topfit frame ui
end
-- get a list of all set IDs in the database
function ns.GetSetList(useTable)
local setList = useTable and wipe(useTable) or {}
for setID, _ in pairs(ns.db.profile.sets) do
tinsert(setList, setID)
end
return setList
end
ns.setObjectCache = {}
-- get a set object from the database
function ns.GetSetByID(setID, useGlobalInstance)
assert(setID and type(ns.db.profile.sets[setID]) ~= "nil", "GetSetByID: invalid set ID given")
if not useGlobalInstance then
return ns.Set.CreateFromSavedVariables(ns.db.profile.sets[setID])
else
if not ns.setObjectCache[setID] then
ns.setObjectCache[setID] = ns.Set.CreateFromSavedVariables(ns.db.profile.sets[setID], true)
end
return ns.setObjectCache[setID]
end
end
-- get the current auto-update-set
function ns.GetCurrentAutoUpdateSet(useGlobalInstance)
local currentSpec = GetSpecializationInfo(GetSpecialization() or 0)
local doubleSpecID = ns:GetPlayerDoubleSpec()
for _, setID in pairs(ns.GetSetList()) do
local set = ns.GetSetByID(setID, true) -- use global instances while searching because it's faster than creating new set objects
if (not currentSpec or set:GetAssociatedSpec() == currentSpec) and set:GetAutoUpdate() then
-- deal with having the same spec twice
local preferredSpecNum = set:GetPreferredSpecNumber()
if (not doubleSpecID or currentSpec == doubleSpecID) and (not preferredSpecNum or preferredSpecNum == GetSpecialization()) then
if useGlobalInstance then
return set, setID
else
return ns.GetSetByID(setID, false), setID
end
end
end
end
end
-- get the current auto-update-set
--TODO: merge this and the above function
function ns.GetCurrentAutoEquipSet(useGlobalInstance)
local currentSpec = GetSpecializationInfo(GetSpecialization() or 0)
local doubleSpecID = ns:GetPlayerDoubleSpec()
for _, setID in pairs(ns.GetSetList()) do
local set = ns.GetSetByID(setID, true) -- use global instances while searching because it's faster than creating new set objects
if (not currentSpec or set:GetAssociatedSpec() == currentSpec) and set:GetAutoEquip() then
-- deal with having the same spec twice
local preferredSpecNum = set:GetPreferredSpecNumber()
if (not doubleSpecID or currentSpec == doubleSpecID) and (not preferredSpecNum or preferredSpecNum == GetSpecialization()) then
if useGlobalInstance then
return set, setID
else
return ns.GetSetByID(setID, false), setID
end
end
end
end
end
-- takes itemID, itemTable or any value accepted by GetItemInfo and returns its itemID
function ns.GetItemID(item)
local sourceType = type(item)
if sourceType == 'number' then
return item
elseif sourceType == 'table' then
return item.itemID
elseif item then
local itemLink = select(2, GetItemInfo(item)) or item
local itemID = itemLink:match('item:(%d+)')
return tonumber(itemID)
end
end
-----------------------------------------------------
-- hook system
-----------------------------------------------------
-- invoke a hook for all currently registered plugins
function ns.InvokeAll(hookName, ...)
local results = {}
for _, plugin in pairs(ns.currentPlugins) do
if plugin[hookName] and type(plugin[hookName] == 'function') then
local result = {plugin[hookName](...)} -- call function, pack results into a table and save for returning
tinsert(results, result)
end
end
return results
end