├── Textures
├── bar.tga
├── border_legacy.tga
├── border_smooth.tga
└── border_svelte.tga
├── Modules
├── Frame
│ ├── load.xml
│ ├── XLoot_Frame.toc
│ └── localization.lua
├── Group
│ ├── load.xml
│ ├── XLoot_Group.toc
│ ├── localization.lua
│ └── Group.lua
├── Master
│ ├── Libs
│ │ └── embeds.xml
│ ├── load.xml
│ ├── XLoot_Master.toc
│ ├── localization.lua
│ └── Master.lua
├── Monitor
│ ├── load.xml
│ ├── XLoot_Monitor.toc
│ ├── localization.lua
│ ├── events.lua
│ └── Monitor.lua
├── Options
│ ├── load.xml
│ ├── Libs
│ │ └── embeds.xml
│ ├── XLoot_Options.toc
│ ├── localization.lua
│ └── Options.lua
└── Modules.xml
├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── Libs
└── Embeds.xml
├── XLoot.toc
├── SKIN_TWEAKS.lua
├── .pkgmeta
├── localization.lua
├── LICENSE.txt
├── XLoot.lua
├── helpers.lua
├── stacks.lua
└── skins.lua
/Textures/bar.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xuerian/XLoot/HEAD/Textures/bar.tga
--------------------------------------------------------------------------------
/Textures/border_legacy.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xuerian/XLoot/HEAD/Textures/border_legacy.tga
--------------------------------------------------------------------------------
/Textures/border_smooth.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xuerian/XLoot/HEAD/Textures/border_smooth.tga
--------------------------------------------------------------------------------
/Textures/border_svelte.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xuerian/XLoot/HEAD/Textures/border_svelte.tga
--------------------------------------------------------------------------------
/Modules/Frame/load.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Modules/Group/load.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Modules/Master/Libs/embeds.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Modules/Monitor/load.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Modules/Master/load.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/Options/load.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Something's not working
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Addon version:**
11 |
12 |
13 | **Game version:**
14 |
15 |
16 | **Description:**
17 |
18 |
19 | **Errors:**
20 | ```
21 | PASTE ERRORS HERE
22 | ```
23 |
--------------------------------------------------------------------------------
/Modules/Frame/XLoot_Frame.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 110000
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30401
5 | ## Title: XLoot Frame
6 | ## Notes: A customizable, skinnable loot frame.
7 | ## Version: @project-version@
8 | ## Author: Xuerian
9 | ## X-Category: Inventory
10 |
11 | ## Dependencies: XLoot
12 |
13 | load.xml
14 |
--------------------------------------------------------------------------------
/Modules/Master/XLoot_Master.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 100005
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30400
5 | ## Title: XLoot Master
6 | ## Notes: Configurable Master Looter interface
7 | ## Version: @project-version@
8 | ## Author: Xuerian, Dridzt
9 | ## X-Category: Inventory
10 |
11 | ## Dependencies: XLoot
12 |
13 | load.xml
14 |
--------------------------------------------------------------------------------
/Modules/Options/Libs/embeds.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Modules/Group/XLoot_Group.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 100005
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30400
5 | ## Title: XLoot Group
6 | ## Notes: Customizable and skinnable loot roll, alert, and reroll frames
7 | ## Version: @project-version@
8 | ## Author: Xuerian
9 | ## X-Category: Inventory
10 |
11 | ## Dependencies: XLoot
12 |
13 | load.xml
14 |
--------------------------------------------------------------------------------
/Modules/Options/XLoot_Options.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 110000
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30401
5 | ## Title: XLoot Options
6 | ## Notes: Configuration interface for XLoot
7 | ## Version: @project-version@
8 | ## Author: Xuerian
9 | ## X-Category: Inventory
10 |
11 | ## Dependencies: XLoot
12 | ## LoadOnDemand: 1
13 |
14 | load.xml
15 |
--------------------------------------------------------------------------------
/Modules/Modules.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Modules/Monitor/XLoot_Monitor.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 110000
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30401
5 | ## Title: XLoot Monitor
6 | ## Notes: A graphical presentation of items looted by yourself and your group
7 | ## Version: @project-version@
8 | ## Author: Xuerian
9 | ## X-Category: Inventory
10 |
11 | ## Dependencies: XLoot
12 |
13 | load.xml
14 |
--------------------------------------------------------------------------------
/Libs/Embeds.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/XLoot.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 110000
2 | ## Interface-Classic: 11403
3 | ## Interface-BCC: 20502
4 | ## Interface-WoTLK: 30401
5 | ## Title: XLoot
6 | ## Notes: Core module for Loot and Looting-related UI improvements. Thank you to J.N., A.J.C., R.L., E.V., B.Y., J.C., R.P., R.R., and K.K. for your donations over the years.
7 | ## Version: @project-version@
8 | ## Author: Xuerian
9 | ## X-Category: Inventory
10 | ## X-Name: XLoot
11 |
12 | ## SavedVariables: XLootADB
13 | ## OptionalDeps: Ace3, LibStub, ButtonFacade, Masque, Pawn
14 |
15 | #@no-lib-strip@
16 | Libs\Embeds.xml
17 | #@end-no-lib-strip@
18 |
19 | localization.lua
20 | XLoot.lua
21 | skins.lua
22 | SKIN_TWEAKS.lua
23 | stacks.lua
24 | helpers.lua
25 |
26 | #@do-not-package@
27 | Modules\Modules.xml
28 | #@end-do-not-package@
29 |
30 |
--------------------------------------------------------------------------------
/SKIN_TWEAKS.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon
2 | local XLoot = select(2, ...)
3 |
4 | function XLoot:ApplySkinTweaks()
5 | XLoot:RegisterMasqueTweak('LiteStep', { size = 16, padding = 0 })
6 | XLoot:RegisterMasqueTweak('LiteStep - XLT', { size = 16, padding = 0 })
7 | XLoot:RegisterMasqueTweak('simpleSquare', { size = 12, row_spacing = 4 })
8 | XLoot:RegisterMasqueTweak('Caith', { size = 12, row_spacing = 4 })
9 | XLoot:RegisterMasqueTweak('Svelte Shadow', { size = 14 })
10 | XLoot:RegisterMasqueTweak('Square Shadow', { size = 16 })
11 | XLoot:RegisterMasqueTweak('Darion', { size = 10, row_spacing = 2, padding = 0 })
12 | XLoot:RegisterMasqueTweak('Darion Clean', { size = 10, row_spacing = 2, padding = 0 })
13 | -- I suggest you add your own addon which depends on XLoot and
14 | -- add your tweaks there, however you can do it by hand here.
15 | -- See the reference at the top of skins.lua
16 | -- XLoot:RegisterMasqueTweak('', { })
17 | end
--------------------------------------------------------------------------------
/.pkgmeta:
--------------------------------------------------------------------------------
1 | package-as: XLoot
2 |
3 | externals:
4 | Libs/LibStub:
5 | url: svn://svn.wowace.com/wow/libstub/mainline/trunk
6 | tag: latest
7 | Libs/CallbackHandler-1.0:
8 | url: svn://svn.wowace.com/wow/callbackhandler/mainline/trunk/CallbackHandler-1.0
9 | tag: latest
10 | Libs/AceDB-3.0:
11 | url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDB-3.0
12 | tag: latest
13 | Libs/AceAddon-3.0:
14 | url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceAddon-3.0
15 | tag: latest
16 | Modules/Options/Libs/AceGUI-3.0:
17 | url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceGUI-3.0
18 | tag: latest
19 | Modules/Options/Libs/AceConfig-3.0:
20 | url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceConfig-3.0
21 | tag: latest
22 | Modules/Options/Libs/AceDBOptions-3.0:
23 | url: svn://svn.wowace.com/wow/ace3/mainline/trunk/AceDBOptions-3.0
24 | tag: latest
25 |
26 | ignore:
27 | - .pkgmeta
28 |
29 | move-folders:
30 | XLoot/Modules/Frame: XLoot_Frame
31 | XLoot/Modules/Group: XLoot_Group
32 | XLoot/Modules/Monitor: XLoot_Monitor
33 | XLoot/Modules/Master: XLoot_Master
34 | XLoot/Modules/Options: XLoot_Options
35 |
--------------------------------------------------------------------------------
/Modules/Monitor/localization.lua:
--------------------------------------------------------------------------------
1 | -- See: http://wow.curseforge.com/addons/xloot/localization/ to create or fix translations
2 | local locales = {
3 | enUS = {
4 | anchor = "Loot Monitor",
5 | },
6 | -- Possibly localized
7 | ptBR = {
8 |
9 | },
10 | frFR = {
11 |
12 | },
13 | deDE = {
14 |
15 | },
16 | koKR = {
17 |
18 | },
19 | esMX = {
20 |
21 | },
22 | ruRU = {
23 |
24 | },
25 | zhCN = {
26 |
27 | },
28 | esES = {
29 |
30 | },
31 | zhTW = {
32 |
33 | },
34 | }
35 |
36 | -- Automatically inserted translations
37 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
38 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
39 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
40 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
41 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
42 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
43 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
44 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
45 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
46 |
47 | XLoot:Localize("Monitor", locales)
48 |
--------------------------------------------------------------------------------
/Modules/Group/localization.lua:
--------------------------------------------------------------------------------
1 | -- See: http://wow.curseforge.com/addons/xloot/localization/ to create or fix translations
2 | local locales = {
3 | enUS = {
4 | anchor = "Group Rolls",
5 | alert_anchor = "Loot Popups",
6 | undecided = "Undecided",
7 | debug_warning = "|c22ff0000XLoot Group: Entering debugging mode. THIS BREAKS LOOTING UNTIL YOU RELOG OR /RELOAD.",
8 | },
9 | -- Possibly localized
10 | ptBR = {
11 |
12 | },
13 | frFR = {
14 |
15 | },
16 | deDE = {
17 |
18 | },
19 | koKR = {
20 |
21 | },
22 | esMX = {
23 |
24 | },
25 | ruRU = {
26 |
27 | },
28 | zhCN = {
29 |
30 | },
31 | esES = {
32 |
33 | },
34 | zhTW = {
35 |
36 | },
37 | }
38 |
39 | -- Automatically inserted translations
40 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
41 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
42 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
43 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
44 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
45 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
46 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
47 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
48 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
49 |
50 | XLoot:Localize("Group", locales)
51 |
--------------------------------------------------------------------------------
/Modules/Master/localization.lua:
--------------------------------------------------------------------------------
1 | -- See: http://wow.curseforge.com/addons/xloot/localization/ to create or fix translations
2 | local locales = {
3 | enUS = {
4 | ML_RANDOM = "Raid Roll",
5 | ML_SELF = "Self Loot",
6 | ML_BANKER = "Banker",
7 | ML_DISENCHANTER = "Disenchanter",
8 | RECIPIENTS = "Special Recipients",
9 | SPECIALROLLS = "Special Rolls",
10 | BINDING_BANKER = "Set Banker",
11 | BINDING_DISENCHANTER = "Set Disenchanter",
12 | ITEM_AWARDED = "%s awarded: %s",
13 | },
14 | -- Possibly localized
15 | ptBR = {
16 |
17 | },
18 | frFR = {
19 |
20 | },
21 | deDE = {
22 |
23 | },
24 | koKR = {
25 |
26 | },
27 | esMX = {
28 |
29 | },
30 | ruRU = {
31 |
32 | },
33 | zhCN = {
34 |
35 | },
36 | esES = {
37 |
38 | },
39 | zhTW = {
40 |
41 | },
42 | }
43 |
44 | -- Automatically inserted translations
45 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
46 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
47 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
48 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
49 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
50 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
51 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
52 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
53 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
54 |
55 | XLoot:Localize("Master", locales)
56 |
--------------------------------------------------------------------------------
/Modules/Frame/localization.lua:
--------------------------------------------------------------------------------
1 | -- See: http://wow.curseforge.com/addons/xloot/localization/ to create or fix translations
2 | local locales = {
3 | enUS = {
4 | linkall_threshold_missed = "No loot meets your quality threshold",
5 | button_link = "Link",
6 | button_close = "Close",
7 | button_auto = "A",
8 | button_auto_tooltip = "Add this item to XLoot's autoloot list\nSee /xloot for more info",
9 | bind_on_use_short = "BoU",
10 | bind_on_equip_short = "BoE",
11 | bind_on_pickup_short = "BoP",
12 |
13 | slot_name_error = "Could not get item name for loot slot %s , it may have already been looted. You can turn this message off in /xloot",
14 | slot_icon_error = "Could not get item icon for loot slot %s , it may have already been looted. You can turn this message off in /xloot",
15 |
16 | },
17 | -- Possibly localized
18 | ptBR = {
19 |
20 | },
21 | frFR = {
22 |
23 | },
24 | deDE = {
25 |
26 | },
27 | koKR = {
28 |
29 | },
30 | esMX = {
31 |
32 | },
33 | ruRU = {
34 |
35 | },
36 | zhCN = {
37 |
38 | },
39 | esES = {
40 |
41 | },
42 | zhTW = {
43 |
44 | },
45 | }
46 |
47 | -- Automatically inserted translations
48 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
49 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
50 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
51 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
52 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
53 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
54 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
55 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
56 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
57 |
58 | XLoot:Localize("Frame", locales)
59 |
--------------------------------------------------------------------------------
/localization.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon
2 | local XLoot = select(2, ...)
3 |
4 | local function CompileLocales(locales)
5 | local L = locales[GetLocale()] and locales[GetLocale()] or locales.enUS
6 | if L ~= locales.enUS then
7 | setmetatable(L, { __index = locales.enUS })
8 | for k, v in pairs(L) do
9 | if type(v) == 'table' then
10 | setmetatable(v, { __index = locales.enUS[k] })
11 | end
12 | end
13 | end
14 | return L
15 | end
16 |
17 | -- locales expects table: { enUS = {...}, ... }
18 | function XLoot:Localize(name, locales)
19 | -- We need to extract the root namespace due to how Curse is currently doing localizations.
20 | for _,t in pairs(locales) do
21 | if t[name] then
22 | for k, v in pairs(t[name]) do
23 | t[k] = v
24 | end
25 | t[name] = nil
26 | end
27 | end
28 | self["L_"..name] = CompileLocales(locales)
29 | end
30 |
31 | local locales = {
32 | enUS = {
33 | skin_svelte = "XLoot: Svelte",
34 | skin_legacy = "XLoot: Legacy",
35 | skin_smooth = "XLoot: Smooth",
36 | anchor_hide = "hide",
37 | anchor_hide_desc = "Lock this module in position\nThis will hide the anchor,\nbut it can be shown again from the options",
38 | },
39 | -- Possibly localized
40 | ptBR = {},
41 | frFR = {},
42 | deDE = {},
43 | koKR = {},
44 | esMX = {},
45 | ruRU = {},
46 | zhCN = {},
47 | esES = {},
48 | zhTW = {},
49 | }
50 |
51 | -- Automatically inserted translations
52 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
53 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
54 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
55 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
56 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
57 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
58 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
59 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
60 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
61 |
62 |
63 | XLoot.L = CompileLocales(locales)
64 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | -- XLoot (and modules) license
2 | Copyright (c) 2016 Xuerian (xuerian@gmail.com)
3 |
4 | All Rights Reserved unless otherwise explicitly stated.
5 |
6 | Redistribution of sofware or sourcecode in any form is strictly prohibited without prior permission from authors.
7 |
8 | Go ahead and ask me (Xuerian), I'm usually a friendly person =)
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
11 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
13 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
14 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
15 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
17 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
18 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
19 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
20 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 |
22 |
23 |
24 | -- Ace3 license
25 |
26 | Copyright (c) 2007, Ace3 Development Team
27 |
28 | All rights reserved.
29 |
30 | Redistribution and use in source and binary forms, with or without
31 | modification, are permitted provided that the following conditions are met:
32 |
33 | * Redistributions of source code must retain the above copyright notice,
34 | this list of conditions and the following disclaimer.
35 | * Redistributions in binary form must reproduce the above copyright notice,
36 | this list of conditions and the following disclaimer in the documentation
37 | and/or other materials provided with the distribution.
38 | * Redistribution of a stand alone version is strictly prohibited without
39 | prior written authorization from the Lead of the Ace3 Development Team.
40 | * Neither the name of the Ace3 Development Team nor the names of its contributors
41 | may be used to endorse or promote products derived from this software without
42 | specific prior written permission.
43 |
44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
46 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
47 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
48 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
49 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
50 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
51 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
52 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
53 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55 |
--------------------------------------------------------------------------------
/XLoot.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon: AceAddon
2 | local XLoot = LibStub("AceAddon-3.0"):NewAddon(select(2, ...), "XLoot")
3 | _G.XLoot = XLoot
4 | local L = XLoot.L
5 | local print, wprint = print, print
6 |
7 | -------------------------------------------------------------------------------
8 | -- Settings
9 | local defaults = {
10 | profile = {
11 | skin = "smooth",
12 | skin_anchors = false,
13 | }
14 | }
15 |
16 | -------------------------------------------------------------------------------
17 | -- Module helpers
18 |
19 | -- Return module localization with new module
20 | local _NewModule = XLoot.NewModule
21 | ---@return XLootModule module
22 | ---@return table localization
23 | function XLoot:NewModule(module_name, ...)
24 | local new = _NewModule(self, module_name, ...)
25 | return new, self["L_"..module_name]
26 | end
27 |
28 | local _GetModule = XLoot.GetModule
29 | function XLoot:GetModule(module_name, ...)
30 | return module_name == "Core" and XLoot or _GetModule(self, module_name, ...)
31 | end
32 |
33 | -- Set up basic event handler
34 | local function SetEventHandler(addon, frame)
35 | if not frame then
36 | frame = CreateFrame("Frame")
37 | addon.eframe = frame
38 | end
39 | frame:SetScript("OnEvent", function(self, event, ...)
40 | if addon[event] then
41 | addon[event](addon, ...)
42 | end
43 | end)
44 | end
45 |
46 | XLoot.slash_commands = {}
47 | function XLoot:SetSlashCommand(slash, func)
48 | local key = "XLOOT_"..slash
49 | _G["SLASH_"..key.."1"] = "/"..slash
50 | _G.SlashCmdList[key] = func
51 | end
52 |
53 | function XLoot:ShowOptionPanel(module)
54 | if not XLootOptions then
55 | C_AddOns.EnableAddOn("XLoot_Options")
56 | C_AddOns.LoadAddOn("XLoot_Options")
57 | end
58 | XLootOptions:OpenPanel(module)
59 | end
60 |
61 | function XLoot:ApplyOptions(in_options)
62 | self.opt = self.db.profile
63 | -- Update skin
64 | XLoot:SetSkin(self.opt.skin)
65 | for _,v in ipairs(XLoot.skinners) do
66 | v:Reskin()
67 | end
68 | -- Update all modules
69 | for k,v in pairs(XLoot.modules) do
70 | if v.db then
71 | v.opt = v.db.profile
72 | end
73 | if v.ApplyOptions then
74 | v:ApplyOptions(in_options)
75 | end
76 | end
77 | end
78 |
79 | -- Add shortcuts for modules
80 | ---@class XLootModule: AceAddon
81 | local XLootModule = {
82 | opt = {},
83 | InitializeModule = function(self, defaults, frame)
84 | local module_name = self:GetName()
85 | -- Set up DB namespace
86 | self.db = XLoot.db:RegisterNamespace(module_name, defaults)
87 | self.opt = self.db.profile
88 |
89 | function self.ShowOptions()
90 | XLoot:ShowOptionPanel(self)
91 | end
92 | -- Default slash command
93 | XLoot:SetSlashCommand(("XLoot"..module_name):lower(), self.ShowOptions)
94 | -- Set event handler
95 | self:SetEventHandler(frame)
96 | end,
97 | SetEventHandler = SetEventHandler,
98 | OnProfileChanged = XLoot.OnProfileChanged,
99 | }
100 | XLoot:SetDefaultModulePrototype(XLootModule)
101 |
102 | -------------------------------------------------------------------------------
103 | -- Prototype helper
104 |
105 | function XLoot.Prototype_New(self, new)
106 | local new = new or {}
107 | for k,v in pairs(self) do
108 | if k ~= "New" and k ~= "_New" then
109 | if new[k] ~= nil then
110 | new['_'..k] = new[k]
111 | end
112 | rawset(new, k, v)
113 | end
114 | end
115 | return new
116 | end
117 |
118 | function XLoot.NewPrototype()
119 | return { New = XLoot.Prototype_New, _New = XLoot.Prototype_New }
120 | end
121 |
122 | -------------------------------------------------------------------------------
123 | -- Addon init
124 |
125 | function XLoot:OnInitialize()
126 | -- Init DB
127 | self.db = LibStub("AceDB-3.0"):New("XLootADB", defaults, true)
128 | self.opt = self.db.profile
129 | self.db.RegisterCallback(self, "OnProfileChanged", "ApplyOptions")
130 | self.db.RegisterCallback(self, "OnProfileCopied", "ApplyOptions")
131 | self.db.RegisterCallback(self, "OnProfileReset", "ApplyOptions")
132 | -- Load skins, import Masque skins
133 | self:SkinsOnInitialize()
134 | end
135 |
136 | function XLoot:OnEnable()
137 | -- Check for old addons
138 | for _,name in ipairs({ "XLoot1.0", "XLootGroup", "XLootMaster", "XLootMonitor" }) do
139 | if C_AddOns.IsAddOnLoaded(name) then
140 | C_AddOns.DisableAddOn(name)
141 | wprint(("|c2244dd22XLoot|r now includes |c2244dd22%s|r - the old version will be disabled on next load, and no longer needs to be installed."):format(name))
142 | end
143 | end
144 |
145 | -- Create option stub
146 | if Settings then
147 | C_AddOns.EnableAddOn("XLoot_Options")
148 | C_AddOns.LoadAddOn("XLoot_Options")
149 | else
150 | local stub = CreateFrame("Frame", "XLootConfigPanel", UIParent)
151 | stub.name = "XLoot"
152 | stub:Hide()
153 | InterfaceOptions_AddCategory(stub)
154 | stub:SetScript("OnShow", function() self:ShowOptionPanel(self) end)
155 | end
156 | self:SetSlashCommand("xloot", function() self:ShowOptionPanel(self) end)
157 | end
158 |
159 | --@do-not-package@
160 | local AC = LibStub("AceConsole-2.0", true)
161 | if AC then print = function(...) AC:PrintLiteral(...) end end
162 | --@end-do-not-package@
163 |
--------------------------------------------------------------------------------
/helpers.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon
2 | local XLoot = select(2, ...)
3 | local buffer, print = {}, print
4 |
5 | local table_insert, table_concat, string_format = table.insert, table.concat, string.format
6 |
7 | local coin_table = {
8 | { GOLD_AMOUNT, 0, "ffd700" },
9 | { SILVER_AMOUNT, 0, "c7c7cf" },
10 | { COPPER_AMOUNT, 0, "eda55f" }
11 | }
12 | for i,v in ipairs(coin_table) do
13 | v[4] = string_format("|cff%s%s|r", v[3], v[1])
14 | end
15 | function XLoot.CopperToString(copper)
16 | coin_table[1][2] = floor(copper / 10000)
17 | coin_table[2][2] = mod(floor(copper / 100), 100)
18 | coin_table[3][2] = mod(copper, 100)
19 |
20 | local buffer = wipe(buffer)
21 | for i,v in ipairs(coin_table) do
22 | local n = v[2]
23 | if n and n > 0 then
24 | table_insert(buffer, string_format(v[4], n))
25 | end
26 | end
27 |
28 | return table_concat(buffer, ", ")
29 | end
30 |
31 | XLootTooltip = CreateFrame('GameTooltip', 'XLootTooltip', UIParent, 'GameTooltipTemplate')
32 | local tooltip = XLootTooltip
33 | tooltip:SetOwner(UIParent, "ANCHOR_NONE")
34 |
35 | local bind_types = {
36 | [ITEM_BIND_ON_PICKUP] = 'pickup',
37 | [ITEM_BIND_ON_EQUIP] = 'equip',
38 | [ITEM_BIND_ON_USE] = 'use'
39 | }
40 |
41 | local tooltip_lines = {
42 | XLootTooltipTextLeft2,
43 | XLootTooltipTextLeft3,
44 | XLootTooltipTextLeft4,
45 | XLootTooltipTextLeft5
46 | }
47 |
48 | function XLoot.GetItemBindType(link)
49 | tooltip:ClearLines()
50 | tooltip:SetHyperlink(link)
51 | for i=1, #tooltip_lines do
52 | local value = bind_types[tooltip_lines[i]:GetText()]
53 | if value then
54 | return value
55 | end
56 | end
57 | end
58 |
59 | function XLoot.CanEquipItem(link)
60 | if not C_Item.IsEquippableItem(link) then
61 | return false
62 | end
63 | tooltip:ClearLines()
64 | tooltip:SetHyperlink(link)
65 | for i=2, 5 do
66 | local line = _G["XLootTooltipTextRight"..i]
67 | if line and line:GetText() then
68 | local r, g, b = line:GetTextColor()
69 | local lr, lg, lb = _G["XLootTooltipTextLeft"..i]:GetTextColor()
70 | return (r > .8 and b > .8 and g > .8 and lr > .8 and lg > .8 and lb > .8) and true or false
71 | end
72 | end
73 | end
74 | function XLoot.IsItemUpgrade(link)
75 | if not XLoot.CanEquipItem(link) then
76 | return false
77 | end
78 | local id = string.match(link, "item:(%d+)")
79 | if PawnIsItemIDAnUpgrade and id and PawnIsItemIDAnUpgrade(id) then
80 | return true
81 | end
82 | return false
83 | end
84 | -- Tack role icon on to player name and return class colors
85 | local white = { r = 1, g = 1, b = 1 }
86 | local dimensions = {
87 | HEALER = '48:64',
88 | DAMAGER = '16:32',
89 | TANK = '32:48'
90 | }
91 | function XLoot.FancyPlayerName(name, class, opt)
92 | local c
93 | if _G.CUSTOM_CLASS_COLORS then
94 | c = _G.CUSTOM_CLASS_COLORS[class]
95 | elseif _G.RAID_CLASS_COLORS[class] then
96 | c = _G.RAID_CLASS_COLORS[class]
97 | else
98 | c = white
99 | end
100 | -- !CLASSIC
101 | local role = 'NONE'
102 | if UnitGroupRolesAssigned then
103 | role = UnitGroupRolesAssigned(name)
104 | end
105 | local short, realm = UnitName(name)
106 | if short then
107 | name = short
108 | end
109 | if realm and realm ~= "" then
110 | name = name.."*"
111 | end
112 | if role ~= 'NONE' and opt and opt.role_icon then
113 | name = string_format('\124TInterface\\LFGFRAME\\LFGROLE:12:12:-1:0:64:16:%s:0:16\124t%s', dimensions[role], name)
114 | end
115 | return name, c.r, c.g, c.b
116 | end
117 |
118 |
119 | local temp_list, template = {},
120 | [[local string_match = string.match
121 | return function(message)
122 | local pcall_status, m1, m2, m3, m4, m5 = pcall(string_match, message, [=[^%s$]=])
123 | assert(pcall_status, "Please report this on XLoot's curse page", message, [=[^%s$]=], m1)
124 | return %s
125 | end]]
126 |
127 | -- Return a inverted match string and corresponding list of ordered match slots (m1-m5)
128 | local match, gsub, insert = string.match, string.gsub, table.insert
129 | local function invert(pattern)
130 | local inverted, arglist = pattern, nil
131 | -- Escape magic characters
132 | inverted = gsub(inverted, "%(", "%%(")
133 | inverted = gsub(inverted, "%)", "%%)")
134 | inverted = gsub(inverted, "%-", "%%-")
135 | inverted = gsub(inverted, "%+", "%%+")
136 | inverted = gsub(inverted, "%.", "%%.")
137 | -- Account for reordered replacements
138 | local k = match(inverted, '%%(%d)%$')
139 | if k then
140 | local i, list = 1, wipe(temp_list)
141 | while k ~= nil do
142 | inverted = gsub(inverted, "(%%%d%$.)", "(.-)", 1)
143 | list[i] = 'm'..tostring(k)
144 | k, i = match(inverted, "%%(%d)%$"), i + 1
145 | end
146 | arglist = table.concat(list, ", ")
147 | -- Simple patterns
148 | else
149 | inverted = gsub(inverted, "%%d", "(%%d+)")
150 | inverted = gsub(inverted, "%%s", "(.-)")
151 | arglist = "m1, m2, m3, m4, m5"
152 | end
153 | return inverted, arglist
154 | end
155 |
156 | -- Match string against a pattern, caching the inverted pattern
157 | local invert_cache = {}
158 | function XLoot.Deformat(str, pattern)
159 | local func = invert_cache[pattern]
160 | if not func then
161 | local inverted, arglist = invert(pattern)
162 | func = loadstring(template:format(inverted, inverted, arglist))()
163 | invert_cache[pattern] = func
164 | end
165 | return func(str)
166 | end
167 | XLoot.InvertFormatString = invert
168 |
169 | --@do-not-package@
170 | -- Debug
171 | local AC = LibStub('AceConsole-2.0', true)
172 | if AC then print = function(...) AC:PrintLiteral(...) end end
173 | --@end-do-not-package@
174 |
--------------------------------------------------------------------------------
/stacks.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon
2 | local XLoot = select(2, ...)
3 | local lib = {}
4 | XLoot.Stack = lib
5 | local L = XLoot.L
6 | local print = print
7 |
8 | ---@class XLootAnchorPrototype: Button
9 | local AnchorPrototype = XLoot.NewPrototype()
10 |
11 | -- ANCHOR element
12 | do
13 | local backdrop = { bgFile = [[Interface\Tooltips\UI-Tooltip-Background]] }
14 |
15 | function AnchorPrototype:OnDragStart()
16 | if self.data.draggable ~= false then
17 | self:StartMoving()
18 | end
19 | end
20 |
21 | function AnchorPrototype:OnDragStop()
22 | self:StopMovingOrSizing()
23 | self.data.x = self:GetLeft()
24 | self.data.y = self:GetTop()
25 | end
26 |
27 | function AnchorPrototype:Show()
28 | self:SetClampedToScreen(true)
29 | self:Position()
30 | self.data.visible = true
31 | self:_Show()
32 | end
33 |
34 | function AnchorPrototype:Hide()
35 | self:SetClampedToScreen(false)
36 | if self.data.direction == 'up' then
37 | self:Position(self.data.x, self.data.y - 20)
38 | elseif self.data.direction then
39 | self:Position(self.data.x, self.data.y + 20)
40 | end
41 | self:GetTop() -- Force reflow
42 | self:GetBottom()
43 | self.data.visible = false
44 | self:_Hide()
45 | end
46 |
47 | function AnchorPrototype:Position(x, y)
48 | self:ClearAllPoints()
49 | self:SetPoint('TOPLEFT', UIParent, 'BOTTOMLEFT', x or self.data.x, y or self.data.y)
50 | self:SetHeight(20)
51 | -- self:SetWidth(self.label:GetStringWidth() + 100)
52 | self:SetWidth(175)
53 | end
54 |
55 | function AnchorPrototype:AnchorChild(child, to)
56 | child:ClearAllPoints()
57 | child:SetPoint(
58 | self.childPoint,
59 | to ~= self and to or self,
60 | self.parentPoint,
61 | self.offset,
62 | self.spacing
63 | )
64 | if self.data and self.data.scale then
65 | child:SetScale(child.scale_mod and self.data.scale * child.scale_mod or self.data.scale)
66 | elseif child.scale_mod then
67 | child:SetScale(child.scale_mod)
68 | end
69 | end
70 |
71 | function AnchorPrototype:UpdateSVData(svdata)
72 | if svdata then
73 | if self.data == svdata then
74 | local mod = self:GetScale() / svdata.scale
75 | svdata.x = svdata.x * mod
76 | svdata.y = svdata.y * mod
77 | end
78 | self.data = svdata
79 | self:SetScale(svdata.scale)
80 | self:Position()
81 | if svdata.visible then
82 | self:Show()
83 | else
84 | self:Hide()
85 | end
86 | self.offset = svdata.offset or 0
87 | self.spacing = svdata.spacing or 2
88 | local horizontal = svdata.alignment == 'right' and 'RIGHT' or 'LEFT'
89 | self.childPoint, self.parentPoint = 'BOTTOM'..horizontal, 'TOP'..horizontal
90 | if svdata.direction == 'down' then
91 | self.childPoint, self.parentPoint = self.parentPoint, self.childPoint
92 | self.spacing = -self.spacing
93 | end
94 | else
95 | self.data = {}
96 | end
97 | end
98 |
99 | local function Hide_OnClick(self)
100 | self.parent:Hide()
101 | end
102 |
103 | local function Hide_OnEnter(self)
104 | GameTooltip:SetOwner(self, 'ANCHOR_TOPLEFT')
105 | GameTooltip:SetText(L.anchor_hide_desc)
106 | GameTooltip:Show()
107 | self.label:SetTextColor(1, 1, 1)
108 | end
109 |
110 | local function Hide_OnLeave(self)
111 | GameTooltip:Hide()
112 | self.label:SetTextColor(.4, .4, .4)
113 | end
114 |
115 | function lib:CreateAnchor(text, svdata)
116 | local anchor = CreateFrame('Button', nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
117 | AnchorPrototype:New(anchor)
118 | anchor:SetBackdrop(backdrop)
119 | anchor:SetBackdropColor(0, 0, 0, 0.7)
120 | anchor:SetMovable(true)
121 | anchor:SetClampedToScreen(true)
122 | anchor:RegisterForDrag('LeftButton')
123 | anchor:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
124 | anchor:SetScript('OnDragStart', anchor.OnDragStart)
125 | anchor:SetScript('OnDragStop', anchor.OnDragStop)
126 | anchor:SetAlpha(0.8)
127 |
128 | local hide = CreateFrame('Button', nil, anchor)
129 | hide:SetHighlightTexture([[Interface\Buttons\UI-Panel-MinimizeButton-Highlight]])
130 | hide:SetPoint('RIGHT', -3, 0)
131 | hide:SetHeight(16)
132 | hide:SetHitRectInsets(10, 10, 1, -2)
133 | hide:SetScript('OnClick', Hide_OnClick)
134 | hide:SetScript('OnEnter', Hide_OnEnter)
135 | hide:SetScript('OnLeave', Hide_OnLeave)
136 | hide.parent = anchor
137 | anchor.hide = hide
138 |
139 | hide.label = hide:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall')
140 | hide.label:SetPoint('TOP')
141 | hide.label:SetPoint('BOTTOM')
142 | hide.label:SetText(L.anchor_hide)
143 | Hide_OnLeave(hide) -- Set text color
144 | hide:SetWidth(hide.label:GetStringWidth()+30)
145 |
146 | local label = anchor:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall')
147 | label:SetPoint('CENTER', -5, 0)
148 | label:SetJustifyH('CENTER')
149 | label:SetText(text)
150 | anchor.label = label
151 |
152 | anchor:SetHeight(20)
153 | anchor:SetWidth(label:GetStringWidth() + 100)
154 |
155 | anchor:UpdateSVData(svdata)
156 |
157 | return anchor
158 | end
159 | end
160 | -- END ANCHOR element
161 |
162 | local function AnchorScale(self, scale)
163 | self:SetScale(scale)
164 | if self.children then
165 | for _, child in pairs(self.children) do
166 | child:SetScale(scale)
167 | end
168 | end
169 | end
170 |
171 | local table_insert = table.insert
172 | local function AcquireChild(self)
173 | for _, f in ipairs(self.children) do
174 | if not f.active then
175 | return f
176 | end
177 | end
178 | local child = self:Factory()
179 | table_insert(self.children, child)
180 | return child, true
181 | end
182 |
183 | -- STATIC STACK
184 | do
185 | local function Push(self)
186 | local child, new = AcquireChild(self)
187 | if new then
188 | local n = #self.children
189 | self:AnchorChild(child, n > 1 and self.children[n-1] or nil)
190 | end
191 | child:Show()
192 | child.active = true
193 | return child
194 | end
195 |
196 | local function Pop(self, child)
197 | child:Hide()
198 | child.active = false
199 | if child.Popped then
200 | child:Popped()
201 | end
202 | end
203 |
204 | local function Restack(self)
205 | local children = self.children
206 | for i,child in ipairs(self.children) do
207 | child:ClearAllPoints()
208 | self:AnchorChild(child, i == 1 and self or children[i-1])
209 | end
210 | end
211 |
212 | function lib:CreateStaticStack(factory, ...)
213 | local anchor = self:CreateAnchor(...)
214 | anchor.children = {}
215 | anchor.Factory = factory
216 | anchor.Push = Push
217 | anchor.Pop = Pop
218 | anchor.Scale = AnchorScale
219 | anchor.Restack = Restack
220 | return anchor
221 | end
222 | end
223 |
224 | --@do-not-package@
225 | -- DYNAMIC STACK
226 | -- do
227 | -- local function Acquire(self)
228 | -- local child = AcquireChild(self)
229 |
230 | -- local fade_in = child:CreateAnimationGroup()
231 | -- local anim = fade_in:CreateAnimation('Alpha')
232 | -- anim:SetDuration(1)
233 | -- anim:SetChange(0)
234 | -- anim:SetSmoothing("OUT")
235 | -- child.fade_in = fade_in
236 |
237 | -- local fade_out = child:CreateAnimationGroup()
238 | -- fade_out:SetScript('OnFinished', FadeFinish)
239 | -- local anim = fade_out:CreateAnimation('Alpha')
240 | -- anim:SetDuration(self.fade_time)
241 | -- anim:SetChange(-1)
242 | -- anim:SetSmoothing("OUT")
243 | -- child.fade_out = fade_out
244 |
245 | -- return child
246 | -- end
247 |
248 | -- local function FadeOutFinish(self)
249 | -- print('finished')
250 | -- local child = self:GetParent()
251 | -- child.achor:Clear(self)
252 | -- child:SetAlpha(1)
253 | -- end
254 |
255 | -- local function Init(self)
256 | -- local child = Acquire(self)
257 | -- child.anchor = self
258 | -- return child
259 | -- end
260 |
261 | -- local function expire_sort(a, b)
262 | -- local ae, be = a.bar.expires, b.bar.expires
263 | -- if ae > be then
264 | -- return b
265 | -- end
266 | -- return a
267 | -- end
268 |
269 | -- local table_insert = table.insert
270 | -- local function Push(self, child)
271 | -- child = child or Init(self)
272 | -- child.active = true
273 | -- table_insert(self.active, child)
274 | -- child:Show()
275 | -- child:SetAlpha(0)
276 | -- child.fade_in:Play()
277 | -- self.eframe:Show()
278 | -- return child
279 | -- end
280 |
281 | -- local function Pop(self, child)
282 | -- child.fade_out:Play()
283 | -- end
284 |
285 | -- local table_remove = table.remove
286 | -- local function Clear(self, child)
287 | -- child.active = false
288 | -- child.fading = nil
289 | -- for i,test in pairs(self.active) do
290 | -- if child == test then
291 | -- table_remove(self.active, i)
292 | -- end
293 | -- end
294 | -- if #self.active == 0 then
295 | -- self.eframe:Hide()
296 | -- end
297 | -- end
298 |
299 | -- local timer = 0
300 | -- local function OnUpdate(self, elapsed)
301 | -- timer = timer + elapsed
302 | -- if timer > .1 then
303 | -- timer = 0
304 | -- local anchor = self.anchor
305 | -- local time, focus, fade_time = GetTime(), GetMouseFocus(), anchor.fade_time
306 | -- for k,child in pairs(anchor.active) do
307 | -- if child.bar.expires < time then
308 | -- if child == focus or (child.parent and child.parent == focus) then
309 | -- child.fade_out:Stop()
310 | -- --child:SetAlpha(1)
311 | -- else
312 | -- if not child.fade_out:IsPlaying() then
313 | -- child.fade_out:Play()
314 | -- end
315 | -- end
316 | -- end
317 | -- end
318 | -- end
319 | -- end
320 |
321 | -- function lib:CreateDynamicStack(factory, ...)
322 | -- local anchor = self:CreateAnchor(...)
323 | -- anchor.active = {}
324 | -- anchor.children = {}
325 | -- anchor.Factory = factory
326 | -- anchor.Push = Push
327 | -- anchor.Pop = Pop
328 | -- anchor.Scale = AnchorScale
329 | -- anchor.fade_time = 5
330 |
331 | -- local eframe = CreateFrame('Frame')
332 | -- eframe:SetScript('OnUpdate', OnUpdate)
333 | -- eframe:Hide()
334 | -- eframe.anchor = anchor
335 | -- anchor.eframe = eframe
336 |
337 | -- return anchor
338 | -- end
339 | -- end
340 | --[=[
341 | local function AnchorButton(anchor, up, down, highlight, click)
342 | local button = CreateFrame('Button', nil, anchor)
343 | button:SetHeight(20)
344 | button:SetWidth(20)
345 | button:SetNormalTexture(up)
346 | button:SetHighlightTexture(highlight)
347 | button:SetPushedTexture(down)
348 | button:SetScript('OnClick', click)
349 | return button
350 | end
351 |
352 | local function ExpandClick(self)
353 |
354 | end
355 |
356 | local function CollapseClick(self)
357 |
358 | end
359 |
360 | local function AppearanceClick(self)
361 | if not lib.ScaleFrame then
362 | local frame = CreateFrame('Frame', 'XStackScaleFrame', UIParent)
363 | frame:SetWidth(250)
364 |
365 | local scale = CreateFrame('Slider')
366 |
367 | lib.ScaleFrame = frame
368 | end
369 | end
370 |
371 | function lib:CreateStackAnchor(...)
372 | local anchor = self:CreateAnchor(...)
373 |
374 | local expand = AnchorButton(anchor,
375 | [[Interface\BUTTONS\UI-Panel-ExpandButton-Up]],
376 | [[Interface\BUTTONS\UI-Panel-ExpandButton-Down]],
377 | [[Interface\BUTTONS\UI-Panel-MinimizeButton-Highlight]],
378 | ExpandClick)
379 |
380 | local collapse = AnchorButton(anchor,
381 | [[Interface\BUTTONS\UI-Panel-CollapseButton-Up]],
382 | [[Interface\BUTTONS\UI-Panel-CollapseButton-Down]],
383 | [[Interface\BUTTONS\UI-Panel-MinimizeButton-Highlight]],
384 | CollapseClick)
385 |
386 | local appearance = AnchorButton(anchor,
387 | [[Interface\BUTTONS\UI-Panel-ExpandButton-Up]],
388 | [[Interface\BUTTONS\UI-Panel-ExpandButton-Down]],
389 | [[Interface\BUTTONS\UI-Panel-MinimizeButton-Highlight]],
390 | AppearanceClick)
391 |
392 | end
393 |
394 | local function Attach(self, f, to)
395 |
396 | end
397 |
398 | local function Push(self, ...)
399 |
400 | end
401 |
402 | local function Pop(self, f)
403 |
404 | end
405 |
406 | function lib:NewStack(anchor)
407 | local stack = {
408 | anchor = anchor,
409 | Push = Push,
410 | Pop = Pop,
411 | Attach = Attach,
412 |
413 | }
414 |
415 | return stack
416 | end
417 | -- ]=]
418 | -- END STACKS
419 |
420 |
421 | -- Debug
422 | local AC = LibStub('AceConsole-2.0', true)
423 | if AC then print = function(...) AC:PrintLiteral(...) end end
424 | --@end-do-not-package@
425 |
--------------------------------------------------------------------------------
/Modules/Monitor/events.lua:
--------------------------------------------------------------------------------
1 | local lib = LibStub:NewLibrary("LootEvents", "1.2")
2 | if not lib then return nil end
3 | local print = print
4 |
5 | local GetItemInfo = C_Item and C_Item.GetItemInfo or GetItemInfo
6 |
7 | --[[// Usage
8 | Callbacks recieve (event, chat_event, ...)
9 | LootEvents:RegisterLootCallback(func)
10 | event: 'item'
11 | player_name, item_link, num_items
12 | event: 'coin'
13 | player_name, total_copper, coin_string
14 | event: 'currency'
15 | currency, num_currency
16 | event: 'crafted'
17 | item_link, num_items
18 |
19 | LootEvents:RegisterGroupCallback(func)
20 | Triggers func when "Group" loot mode events are recieved
21 | event: 'selected'
22 | item_link, player_name, roll_type
23 | event: 'rolled'
24 | item_link, player_name, roll_type, roll_number
25 | event: 'won'
26 | item_link, player_name
27 | event: 'allpass'
28 | item_link
29 | --]]
30 |
31 | -- Callback handling
32 | lib.callbacks = lib.callbacks or { loot = {}, group = {} }
33 | local lootcb, groupcb = lib.callbacks.loot, lib.callbacks.group
34 |
35 | local need_loot = next(lootcb) and true or false
36 | local need_group = next(groupcb) and true or false
37 |
38 | function lib:RegisterLootCallback(func)
39 | table.insert(lootcb, func)
40 | need_loot = true
41 | end
42 |
43 | function lib:RegisterGroupCallback(func)
44 | table.insert(groupcb, func)
45 | need_group = true
46 | end
47 |
48 | local current_pattern = nil
49 | local function trigger_loot(event, ...)
50 | for _, func in ipairs(lootcb) do
51 | func(event, current_pattern, ...)
52 | end
53 | end
54 |
55 | local activerolls = 0
56 | local function trigger_group(event, ...)
57 | for _, func in ipairs(groupcb) do
58 | func(event, current_pattern, ...)
59 | end
60 | if event == 'won' or event == 'allpass' then
61 | activerolls = max(0, activerolls - 1)
62 | end
63 | end
64 |
65 |
66 |
67 | -- Catch latent rolls
68 | if IsInGroup() then
69 | for i=1,200 do
70 | local time = GetLootRollTimeLeft(i)
71 | if time > 0 then
72 | activerolls = activerolls + 1
73 | end
74 | end
75 | end
76 |
77 | local Deformat = XLoot.Deformat
78 |
79 | local loot_patterns, group_patterns, unsortedloot, currentsort
80 |
81 | -- Chatmsg handler
82 | local sort, group = table.sort
83 | local function sort_func(a, b)
84 | return a[group] < b[group]
85 | end
86 |
87 | local function Handler(text)
88 | if need_group and activerolls > 0 then
89 | -- Move through the patterns one by one, match against the message
90 | for k, v in ipairs(group_patterns) do
91 | local m1, m2, m3, m4 = Deformat(text, v[1])
92 | -- Match was found, call the handler with all captured values
93 | if m1 then
94 | current_pattern = v[3]
95 | return v[2](m1, m2, m3, m4)
96 | end
97 | end
98 | end
99 |
100 | if need_loot then
101 | -- Match string against our patterns
102 | for i, v in ipairs(loot_patterns) do
103 | local m1, m2, m3, m4 = Deformat(text, v[1])
104 | -- Match found, add to counter and call pattern's handler
105 | if m1 then
106 | current_pattern = v[1]
107 | return v[2](m1, m2, m3, m4)
108 | end
109 | end
110 | end
111 | end
112 |
113 | -- Invert gold patterns
114 | local function invert(pstr)
115 | return pstr:gsub("%%d", "(%1+)")
116 | end
117 | local cg = invert(GOLD_AMOUNT)
118 | local cs = invert(SILVER_AMOUNT)
119 | local cc = invert(COPPER_AMOUNT)
120 |
121 | -- Parse coin strings (Really?)
122 | local function ParseCoinString(tstr)
123 | local g = tstr:match(cg) or 0
124 | local s = tstr:match(cs) or 0
125 | local c = tstr:match(cc) or 0
126 | return g*10000+s*100+c
127 | end
128 |
129 | -- Event handling
130 | if lib.frame then
131 | lib.frame:SetScript("OnEvent", function() return nil end)
132 | end
133 | lib.frame = CreateFrame("Frame")
134 | local f = lib.frame
135 | local events = {}
136 | f:SetScript("OnEvent", function(_, e, ...) if events[e] then events[e](...) end end)
137 | local function event(e, func)
138 | f:RegisterEvent(e)
139 | events[e] = func
140 | end
141 |
142 | event("CHAT_MSG_LOOT", Handler)
143 | event("CHAT_MSG_MONEY", Handler)
144 | event("CHAT_MSG_CURRENCY", Handler)
145 |
146 | -- Incriment and deincement rolls to only match while there is a roll happening
147 | event("START_LOOT_ROLL", function()
148 | activerolls = activerolls + 1
149 | end)
150 | -- 2 second delay needed?
151 | local max = math.max
152 | event("CANCEL_LOOT_ROLL", function()
153 | --activerolls = max(0, activerolls - 1)
154 | end)
155 |
156 | local player = UnitName('player')
157 |
158 |
159 | --// LOOT PATTERNS
160 | loot_patterns = { }
161 | do
162 | local function handler(str, func)
163 | assert(_G[str], "String does not exist", str)
164 | table.insert(loot_patterns, { _G[str], func, str })
165 | end
166 |
167 | -- Loot triggers
168 | local function loot(who, what, num)
169 | trigger_loot('item', who, what, num or 1)
170 | end
171 |
172 | local function loot_self(what, num)
173 | trigger_loot('item', player, what, num or 1)
174 | end
175 |
176 | local function coin(who, str)
177 | trigger_loot('coin', who, ParseCoinString(str), str)
178 | end
179 |
180 | local function coin_self(str)
181 | trigger_loot('coin', player, ParseCoinString(str), str)
182 | end
183 |
184 | -- Add item patterns
185 | handler('LOOT_ITEM_PUSHED_SELF_MULTIPLE', loot_self)
186 | handler('LOOT_ITEM_PUSHED_SELF', loot_self)
187 | handler('LOOT_ITEM_SELF_MULTIPLE', loot_self)
188 |
189 | -- Account for Russian locale using the same string for LOOT_MONEY as LOOT_ITEM_SELF
190 | if GetLocale() == "ruRU" then
191 | handler('LOOT_ITEM_SELF', function(what)
192 | if not what:match("|H") then -- No link, match coins
193 | coin_self(what)
194 | else
195 | loot_self(what)
196 | end
197 | end)
198 | else
199 | handler('LOOT_ITEM_SELF', loot_self)
200 | handler('YOU_LOOT_MONEY_GUILD', coin_self)
201 | handler('YOU_LOOT_MONEY', coin_self)
202 | end
203 | handler('LOOT_ITEM_MULTIPLE', loot)
204 | handler('LOOT_ITEM', loot)
205 |
206 |
207 | -- Add coin patterns
208 | handler('LOOT_MONEY', coin)
209 | handler('LOOT_MONEY_SPLIT_GUILD', coin_self)
210 | handler('LOOT_MONEY_SPLIT', coin_self)
211 |
212 | -- Currency patterns
213 | local function currency(link, num)
214 | trigger_loot('currency', link:match('currency:(%d+)'), num or 1)
215 | end
216 | handler('CURRENCY_GAINED_MULTIPLE', currency)
217 | handler('CURRENCY_GAINED', currency)
218 |
219 | -- Self crafting
220 | local function crafted(what, num)
221 | trigger_loot('crafted', what, num or 1)
222 | end
223 | handler('LOOT_ITEM_CREATED_SELF_MULTIPLE', crafted)
224 | handler('LOOT_ITEM_CREATED_SELF', crafted)
225 | handler('LOOT_ITEM_REFUND_MULTIPLE', loot_self)
226 | handler('LOOT_ITEM_REFUND', loot_self)
227 | handler('LOOT_MONEY_REFUND', coin_self)
228 | handler('LOOT_ITEM_WHILE_PLAYER_INELIGIBLE', loot)
229 |
230 | end
231 |
232 |
233 | --// GROUP PATTERNS
234 | group_patterns = { }
235 | do
236 | -- Base handler function. Checks argument types, adds to handler table (Presorted)
237 | -- The pattern is stored as the first key, and the function to be called in the second
238 | local function handler(str, func)
239 | table.insert(group_patterns, { _G[str], func, str })
240 | end
241 |
242 | -- Roll selected handler (You select Greed for XXXX, etc)
243 | local function selected(str, rtype, own)
244 | handler(str, function(who, item)
245 | if own then
246 | item, who = who, player
247 | end
248 | trigger_group('selected', item, who, rtype)
249 | end)
250 | end
251 |
252 | -- Rolled handler (XXXX roll - # by xxxxx, etc)
253 | local function rolled(str, rtype, own)
254 | handler(str, function(roll, item, who)
255 | trigger_group('rolled', item, (own or who == YOU) and player or who, rtype, tonumber(roll))
256 | end)
257 | end
258 |
259 | -- Add handlers using construct functions, ordered specifically
260 | selected('LOOT_ROLL_DISENCHANT', 'disenchant')
261 | selected('LOOT_ROLL_GREED', 'greed')
262 | selected('LOOT_ROLL_NEED', 'need')
263 | selected('LOOT_ROLL_DISENCHANT_SELF', 'disenchant', true)
264 | selected('LOOT_ROLL_GREED_SELF', 'greed', true)
265 | selected('LOOT_ROLL_NEED_SELF', 'need', true)
266 | selected('LOOT_ROLL_PASSED_AUTO', 'pass')
267 | selected('LOOT_ROLL_PASSED_AUTO_FEMALE', 'pass')
268 | selected('LOOT_ROLL_PASSED_SELF_AUTO', 'pass', true)
269 | handler('LOOT_ROLL_ALL_PASSED', function(item) trigger_group('allpass', item) end)
270 | selected('LOOT_ROLL_PASSED_SELF', 'pass', true)
271 | selected('LOOT_ROLL_PASSED', 'pass')
272 | rolled('LOOT_ROLL_ROLLED_DE', 'disenchant')
273 | rolled('LOOT_ROLL_ROLLED_GREED', 'greed')
274 | rolled('LOOT_ROLL_ROLLED_NEED', 'need')
275 | rolled('LOOT_ROLL_ROLLED_NEED_ROLE_BONUS', 'need')
276 | handler('LOOT_ROLL_YOU_WON', function(item) trigger_group('won', item, player) end)
277 | handler('LOOT_ROLL_WON', function(who, item) trigger_group('won', item, who == YOU and player or who) end)
278 | end
279 |
280 | --@do-not-package@
281 | local test = false
282 | local test_verbose = false
283 | local AC = LibStub('AceConsole-2.0', true)
284 |
285 | if AC then print = function(...) AC:PrintLiteral(...) end end
286 |
287 | local items = {
288 | -- { 52722 },
289 | -- { 31304 },
290 | -- { 37254 },
291 | -- { 13262 },
292 | -- { 15487 },
293 | { 72120 },
294 | { 76137 },
295 | -- { 2589 }
296 | }
297 |
298 | for i,v in ipairs(items) do
299 | GetItemInfo(v[1])
300 | end
301 |
302 | XLoot:SetSlashCommand("xe", function(msg)
303 | for i=1,4 do
304 | Handler((LOOT_ITEM_SELF):format(select(2, GetItemInfo(items[random(1, #items)][1]))))
305 | end
306 | Handler((YOU_LOOT_MONEY):format("1 Gold, 2 Silver, 3 Copper"))
307 | end)
308 |
309 | local locales = {
310 | zhTW = {
311 | -- item = "[崑萊碎肉塊]",
312 | strings = {
313 | LOOT_ITEM = "%s獲得戰利品:%s。",
314 | LOOT_ITEM_CREATED_SELF = "你製造了:%s。",
315 | LOOT_ITEM_CREATED_SELF_MULTIPLE = "你製造了:%sx%d。",
316 | LOOT_ITEM_MULTIPLE = "%s獲得戰利品:%sx%d。",
317 | LOOT_ITEM_PUSHED_SELF = "你獲得了物品:%s。",
318 | LOOT_ITEM_PUSHED_SELF_MULTIPLE = "你獲得物品:%sx%d。",
319 | LOOT_ITEM_REFUND = "退回給你的費用為:%s。",
320 | LOOT_ITEM_REFUND_MULTIPLE = "退回給你的費用為:%sx%d。",
321 | LOOT_ITEM_SELF = "你拾取了物品:%s。",
322 | LOOT_ITEM_SELF_MULTIPLE = "你獲得戰利品:%sx%d。",
323 | LOOT_ITEM_WHILE_PLAYER_INELIGIBLE = "%s獲得戰利品:|TInterface\\Common\\Icon-NoLoot:13:13:0:0|t%s",
324 | LOOT_MONEY = "%s拾取了%s。",
325 | LOOT_MONEY_REFUND = "退回給你的費用為%s。",
326 | LOOT_MONEY_SPLIT = "你分到%s。",
327 | LOOT_MONEY_SPLIT_GUILD = "你分到%s。(%s存放在公會銀行)",
328 | YOU_LOOT_MONEY = "你拾取了%s",
329 | YOU_LOOT_MONEY_GUILD = "你拾取了%s(%s存放在公會銀行)",
330 | }
331 | }
332 | }
333 | local locale_mt = {
334 | __index = {
335 | item = "[Linen Cloth]",
336 | player = "Xuerian",
337 | money = "1 Gold",
338 | guild = "Some guild",
339 | number = 5
340 | }
341 | }
342 |
343 | local function test_table_from_locale(t)
344 | return {
345 | LOOT_ITEM_CREATED_SELF = {t.item},
346 | LOOT_ITEM_CREATED_SELF_MULTIPLE = {t.item, t.number},
347 | LOOT_ITEM = {t.player, t.item},
348 | LOOT_ITEM_MULTIPLE = {t.player, t.item, t.number},
349 | LOOT_ITEM_PUSHED_SELF = {t.item},
350 | LOOT_ITEM_PUSHED_SELF_MULTIPLE = {t.item, t.number},
351 | LOOT_ITEM_REFUND = {t.item},
352 | LOOT_ITEM_REFUND_MULTIPLE = {t.item, t.number},
353 | LOOT_ITEM_SELF = {t.item},
354 | LOOT_ITEM_SELF_MULTIPLE = {t.item, t.number},
355 | LOOT_ITEM_WHILE_PLAYER_INELIGIBLE = {t.player, t.item},
356 | LOOT_MONEY = {t.player, t.money},
357 | LOOT_MONEY_REFUND = {t.money},
358 | LOOT_MONEY_SPLIT = {t.money},
359 | LOOT_MONEY_SPLIT_GUILD = {t.money, t.guild},
360 | YOU_LOOT_MONEY = {t.money},
361 | YOU_LOOT_MONEY_GUILD = {t.money, t.guild}
362 | }
363 | end
364 |
365 | local function tprint(...)
366 | if test_verbose then
367 | print(...)
368 | end
369 | end
370 |
371 | -- Test each locale
372 | local function inv(pat) return select(1, XLoot.InvertFormatString(pat)) end
373 | local fmt = string.format
374 | for locale, t in pairs(locales) do
375 | setmetatable(t, locale_mt)
376 | -- setmetatable(t.strings, string_mt)
377 | local test_table = test_table_from_locale(t)
378 | local pass, fail = 0, 0
379 | -- Test each string
380 | for k, p in pairs(t.strings) do
381 | -- Fake string
382 | local test = p:format(unpack(test_table[k]))
383 | -- Emulate Handle(text)
384 | local m1, m2, m3, m4, matched
385 | for i, v in ipairs(loot_patterns) do
386 | if t.strings[v[3]] then
387 | m1, m2, m3, m4 = Deformat(test, t.strings[v[3]])
388 | if m1 then
389 | matched = v[3]
390 | break
391 | end
392 | else
393 | -- cprint("[INCOMPLETE] string missing", v[3], locale)
394 | end
395 | end
396 | -- String results
397 | if matched == k then
398 | tprint(fmt("[PASS] matched %s (%s) against %s", matched, inv(t.strings[k]), test), m1, m2, m3, m4)
399 | pass = pass + 1
400 | else
401 | if matched then
402 | tprint(fmt("[FAIL] expected %s, matched %s (%s vs %s) against %s", k, matched, inv(t.strings[matched]), inv(t.strings[k]), test), m1, m2, m3)
403 | else
404 | tprint(fmt("[FAIL] no match for %s (%s) against %s", k, inv(t.strings[k]), test))
405 | end
406 | fail = fail + 1
407 | end
408 | end
409 | -- Locale results
410 | if pass == 0 then
411 | print("[FAIL ALL]", locale)
412 | elseif fail ~= 0 then
413 | print(("[FAIL %s/%s]"):format(fail, fail+pass), locale)
414 | else
415 | print("[PASS]", locale)
416 | end
417 | end
418 |
419 | --@end-do-not-package@
420 |
--------------------------------------------------------------------------------
/skins.lua:
--------------------------------------------------------------------------------
1 | ---@class XLootAddon
2 | local XLoot = select(2, ...)
3 | local lib = {
4 | skins = {},
5 | masque_tweaks = {},
6 | }
7 | XLoot.Skin = lib
8 | local L = XLoot.L
9 | local print = print
10 |
11 | -- Custom skins
12 | -- To add your own, simply make XLoot a dependency then call XLoot:RegisterSkin(name, skin_table)
13 | -- skin_table being a table with these keys:
14 | -- [required] texture - Path to texture
15 | -- [optional] name - Shown in UI, name provided to RegisterSkin used by default
16 | -- [optional] size, size_highlight
17 | -- [optional] row_spacing
18 | -- [optional] frame_color_*, loot_color_* -- See above
19 | -- [optional] color_mod - Multiplier for border quality colors
20 | -- [optional] padding, padding_highlight - Padding refers to how far outward (or inward) the border should be offset from the frame
21 |
22 | -- Tweaking Masque skins
23 | -- To keep from having to change this file, call XLoot:RegisterMasqueTweak(skin_name, skin_table)
24 | -- following the same rules as custom skins
25 | -- Quick or temporary tweaks can be added to SKIN_TWEAKS.lua
26 |
27 | -- Base skin template
28 | lib.base = {
29 | bar_texture = [[Interface\AddOns\XLoot\Textures\bar]],
30 | color_mod = .75,
31 | row_spacing = 2,
32 | }
33 |
34 | -- Skin registration
35 | function XLoot:RegisterSkin(skin_name, skin_table)
36 | setmetatable(skin_table, { __index = lib.base })
37 | skin_table.key = skin_name
38 | lib.skins[skin_name] = skin_table
39 | end
40 |
41 | -- Masque tweaks
42 | function XLoot:RegisterMasqueTweak(masque_name, tweak_table)
43 | lib.masque_tweaks[masque_name] = tweak_table
44 | -- Apply to existing skins
45 | if lib.skins[masque_name] then
46 | for k, v in pairs(tweak_table) do
47 | lib.skins[masque_name][k] = v
48 | end
49 | end
50 | end
51 |
52 | -- Skinning
53 | local function subtable_insert(t, k, v)
54 | if not t[k] then
55 | t[k] = {}
56 | end
57 | table.insert(t[k], v)
58 | end
59 |
60 | do
61 | local base = {
62 | padding = 2,
63 | size = 16,
64 | layer = 'ARTWORK',
65 | mode = 'BLEND',
66 | r = 1,
67 | g = 1,
68 | b = 1,
69 | a = 1
70 | }
71 | local types = {
72 | highlight = {
73 | mode = 'ADD',
74 | layer = 'HIGHLIGHT',
75 | texture = [[Interface\Buttons\ButtonHilight-Square]],
76 | size = 8,
77 | padding = 0,
78 | r = .8,
79 | g = .8,
80 | b = .8,
81 | a = .8
82 | }
83 | }
84 |
85 | local mt = {
86 | __index = function(t, k)
87 | local ttype = rawget(t, 'type')
88 | if ttype and types[ttype] and types[ttype][k] then
89 | return types[ttype][k]
90 | end
91 | return base[k] or nil
92 | end
93 | }
94 |
95 | local function meta(t)
96 | return setmetatable(t, mt)
97 | end
98 |
99 | local function update_borders(frame, options, borders, r, g, b, a)
100 | local padding = options.padding
101 | local size = options.size/2
102 | r = r or options.r
103 | g = g or options.g
104 | b = b or options.b
105 | a = a or options.a
106 | for pos, tex in ipairs(borders) do
107 | -- Set texture options
108 | tex:SetDrawLayer(options.layer)
109 | tex:SetTexture(options.texture)
110 | tex:SetBlendMode(options.mode)
111 | tex:SetWidth(size)
112 | tex:SetHeight(size)
113 | tex:SetVertexColor(r, g, b, a)
114 |
115 | -- Position texture
116 | tex:ClearAllPoints()
117 | if pos == 1 then
118 | tex:SetTexCoord(0, 1/6, 0, 1/6)
119 | tex:SetPoint("TOPLEFT", frame, "TOPLEFT", -padding, padding)
120 | elseif pos == 2 then
121 | tex:SetTexCoord(5/6, 1, 0, 1/6)
122 | tex:SetPoint("TOPRIGHT", frame, "TOPRIGHT", padding, padding)
123 | elseif pos == 3 then
124 | tex:SetTexCoord(0, 1/6, 5/6, 1)
125 | tex:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", -padding, -padding)
126 | elseif pos == 4 then
127 | tex:SetTexCoord(5/6, 1, 5/6, 1)
128 | tex:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", padding, -padding)
129 | elseif pos == 5 then
130 | tex:SetTexCoord(1/6, 5/6, 0, 1/6)
131 | tex:SetPoint("TOPLEFT", borders[1], "TOPRIGHT")
132 | tex:SetPoint("TOPRIGHT", borders[2], "TOPLEFT")
133 | elseif pos == 6 then
134 | tex:SetTexCoord(1/6, 5/6, 5/6, 1)
135 | tex:SetPoint("BOTTOMLEFT", borders[3], "BOTTOMRIGHT")
136 | tex:SetPoint("BOTTOMRIGHT", borders[4], "BOTTOMLEFT")
137 | elseif pos == 7 then
138 | tex:SetTexCoord(0, 1/6, 1/6, 5/6)
139 | tex:SetPoint("TOPLEFT", borders[1], "BOTTOMLEFT")
140 | tex:SetPoint("BOTTOMLEFT", borders[3], "TOPLEFT")
141 | elseif pos == 8 then
142 | tex:SetTexCoord(5/6, 1, 1/6, 5/6)
143 | tex:SetPoint("TOPRIGHT", borders[2], "BOTTOMRIGHT")
144 | tex:SetPoint("BOTTOMRIGHT", borders[4], "TOPRIGHT")
145 | end
146 | end
147 | end
148 |
149 | local function create_borders(frame, options)
150 | local borders = { }
151 | for i = 1, 8 do
152 | borders[i] = frame:CreateTexture()
153 | end
154 | update_borders(frame, options, borders)
155 | return borders
156 | end
157 |
158 | local backdrop = {
159 | bgFile = [[Interface\ChatFrame\ChatFrameBackground]], tile = true, tileSize = 16,
160 | insets = {left = 3, right = 3, top = 3, bottom = 3},
161 | }
162 |
163 | local backdrop_empty = {
164 | bgFile = ''
165 | }
166 |
167 | local bd_color = { 0, 0, 0, .9 }
168 | local g_color = { .5, .5, .5, .6 }
169 |
170 | -- Frame methods
171 | -- https://www.wowace.com/projects/xloot/issues/205 is not reproducible, but user gets error reliably
172 | local alphaworks = false
173 | local function SetBorderColor(self, r, g, b, a)
174 | for i, x in pairs(self._skin_borders) do
175 | x:SetVertexColor(r, g, b, a or 1)
176 | end
177 | end
178 |
179 | local function GetBorderColor(self)
180 | return self._skin_borders[1]:GetVertexColor()
181 | end
182 |
183 | local function SetGradientColor(self, r, g, b, a)
184 | self.gradient:SetGradient('VERTICAL', CreateColor(0.1, 0.1, 0.1, 0), CreateColor(r, g, b, a))
185 | end
186 |
187 | function lib:Gradient(frame)
188 | local g = frame:CreateTexture(nil, 'BORDER')
189 | frame.gradient = g
190 | g:SetTexture([[Interface\ChatFrame\ChatFrameBackground]])
191 | g:SetPoint('TOPLEFT', 3, -3)
192 | g:SetPoint('BOTTOMRIGHT', -3, 3)
193 | -- g:SetBlendMode'ADD'
194 | frame.SetGradientColor = SetGradientColor
195 | frame:SetGradientColor(unpack(g_color))
196 | end
197 |
198 | function lib:Backdrop(frame, opt_backdrop)
199 | frame:SetBackdrop(opt_backdrop or backdrop)
200 | frame:SetBackdropColor(unpack(bd_color))
201 | end
202 |
203 | -- Lib methods
204 | -- Basic skin
205 | function lib:Skin(frame, options)
206 | -- Store options
207 | frame._skin_options = meta(options)
208 |
209 | -- Apply backdrop
210 | if options.backdrop ~= false then
211 | self:Backdrop(frame, type(options.backdrop) == 'table' and options.backdrop or nil)
212 | end
213 |
214 | -- Gradient
215 | if options.gradient ~= false then
216 | self:Gradient(frame)
217 | end
218 |
219 | -- Borders
220 | frame._skin_borders = create_borders(frame, options)
221 | frame.SetBorderColor = SetBorderColor
222 | frame.GetBorderColor = GetBorderColor
223 | end
224 |
225 | function lib:SkinRaw(frame, options)
226 | return create_borders(frame, meta(options)), SetBorderColor
227 | end
228 |
229 | function lib:UpdateSkin(frame, options, r, g, b, a)
230 | frame._skin_options = meta(options)
231 | update_borders(frame, options, frame._skin_borders, r, g, b, a)
232 | end
233 |
234 | -- Highlights
235 | local function ShowHighlight(self, status)
236 | for _, tex in ipairs(self._highlights) do
237 | tex:Show()
238 | end
239 | end
240 |
241 | local function HideHighlight(self)
242 | for _, tex in ipairs(self._highlights) do
243 | tex:Hide()
244 | end
245 | end
246 |
247 | local function SetHighlightColor(self, r, g, b, a)
248 | for _, tex in ipairs(self._highlights) do
249 | tex:SetVertexColor(r, g, b, a)
250 | end
251 | end
252 |
253 | local function GetHighlightColor(self)
254 | return self._highlights[1]:GetVertexColor()
255 | end
256 |
257 | local highlight = { type = 'highlight' }
258 |
259 | -- Add highlight borders to a frame
260 | function lib:Highlight(frame, options)
261 | -- Default options
262 | options = meta(options or highlight)
263 |
264 | frame._highlights = create_borders(frame, options)
265 | frame._higlight_options = options
266 |
267 | frame.ShowHighlight = ShowHighlight
268 | frame.HideHighlight = HideHighlight
269 | frame.SetHighlightColor = SetHighlightColor
270 | frame.GetHighlightColor = GetHighlightColor
271 | if options.layer ~= 'HIGHLIGHT' then
272 | frame:HideHighlight()
273 | end
274 | end
275 |
276 | function lib:UpdateHighlight(frame, options, r, g, b, a)
277 | frame._higlight_options = meta(options)
278 | update_borders(frame, options, frame._highlights, r, g, b, a)
279 | end
280 | end
281 |
282 |
283 | -- Create a subset of skins to be applied
284 | do
285 | -- Merge current skin with set options
286 | local function compile(data, name)
287 | assert(data.sets[name], "Bad set name given to XLoot.Skin")
288 | -- Return cached
289 | if not data.compiled[name] then
290 | data.compiled[name] = {}
291 | elseif next(data.compiled[name]) then
292 | return data.compiled[name]
293 | end
294 | -- Generate to cache
295 | local out = data.compiled[name]
296 | local skin = (data.SKIN and lib.skins[data.SKIN]) and lib.skins[data.SKIN] or lib.current
297 | local set = data.sets[name]
298 | -- Copy defaults
299 | for k,v in pairs(lib.base) do
300 | out[k] = v
301 | end
302 | -- Copy skin data
303 | for k,v in pairs(skin) do
304 | out[k] = v
305 | end
306 | -- Extract data for highlights
307 | if skin.highlight
308 | and set.type
309 | and set.type == 'highlight'
310 | and type(skin.highlight) == 'table' then
311 | for k,v in pairs(skin.highlight) do
312 | out[k] = v
313 | end
314 | -- Highlights shouldn't inherit default texture
315 | if not skin.highlight.texture then
316 | out.texture = nil
317 | end
318 | end
319 | -- Apply set overrides
320 | for k,v in pairs(set) do
321 | out[k] = v
322 | end
323 | -- Apply metatable
324 | setmetatable(out, getmetatable(skin))
325 | return out
326 | end
327 |
328 | -- Re-compile and Re-apply all
329 | local function Reskin(self)
330 | local data = self._skin_data
331 | -- Clear cache
332 | for k,v in pairs(data.compiled) do
333 | wipe(v)
334 | end
335 | -- Update skins
336 | for set_name,frames in pairs(data.skinned) do
337 | for i,frame in ipairs(frames) do
338 | lib:UpdateSkin(frame, compile(data, set_name), frame:GetBorderColor())
339 | end
340 | end
341 | -- Update highlights
342 | for set_name,frames in pairs(data.highlighted) do
343 | for i,frame in ipairs(frames) do
344 | lib:UpdateHighlight(frame, compile(data, set_name), frame:GetHighlightColor())
345 | end
346 | end
347 | return compile(data, data.default or next(data.sets))
348 | end
349 |
350 | local function Skin(self, frame, set_name)
351 | local data = self._skin_data
352 | set_name = set_name or data.default
353 | local skin = compile(data, set_name)
354 | lib:Skin(frame, skin)
355 | subtable_insert(data.skinned, set_name, frame)
356 | return skin
357 | end
358 |
359 | local function Highlight(self, frame, set_name)
360 | local data = self._skin_data
361 | set_name = set_name or data.default
362 | lib:Highlight(frame, compile(data, set_name))
363 | subtable_insert(data.highlighted, set_name, frame)
364 | end
365 |
366 | -- Embed required functions and create data set to skin multiple similar frames
367 | XLoot.skinners = {}
368 | function XLoot:MakeSkinner(target, sets, default_set)
369 | if not default_set and not sets.default then
370 | sets.default = {}
371 | end
372 | target._skin_data = {
373 | skinned = {},
374 | highlighted = {},
375 | compiled = {},
376 | sets = sets,
377 | default = default_set or "default"
378 | }
379 | target.Reskin = Reskin
380 | target.Skin = Skin
381 | target.Highlight = Highlight
382 | table.insert(XLoot.skinners, target)
383 | return target
384 | end
385 | end
386 |
387 | -------------------------------------------------------------------------------
388 | -- Default skins
389 | local svelte = {
390 | name = ('|c2244dd22%s|r'):format(L.skin_svelte),
391 | texture = [[Interface\AddOns\XLoot\Textures\border_svelte]],
392 | }
393 | local legacy = {
394 | name = ('|c2244dd22%s|r'):format(L.skin_legacy),
395 | row_spacing = 3,
396 | texture = [[Interface\AddOns\XLoot\Textures\border_legacy]],
397 | size = 16,
398 | highlight = {
399 | size = 12
400 | },
401 | color_mod = .85,
402 | }
403 | local smooth = {
404 | name = ('|c2244dd22%s|r'):format(L.skin_smooth),
405 | row_spacing = 3,
406 | texture = [[Interface\AddOns\XLoot\Textures\border_smooth]],
407 | size = 14,
408 | padding = 1,
409 | highlight = {
410 | size = 6,
411 | padding = -1
412 | },
413 | color_mod = .9,
414 | }
415 |
416 | -- Register default skins
417 | XLoot:RegisterSkin('svelte', svelte)
418 | XLoot:RegisterSkin('legacy', legacy)
419 | XLoot:RegisterSkin('smooth', smooth)
420 |
421 | -------------------------------------------------------------------------------
422 | -- Index Masque skins later so we definitely catch all of them
423 |
424 | function XLoot:SkinsOnInitialize()
425 | -- Masque skins
426 | local Masque = LibStub('Masque', true) or LibStub('LibButtonFacade', true)
427 | if Masque and Masque.GetSkins then
428 | -- Add available skins
429 | local Masque_Skins = Masque:GetSkins()
430 | if type(Masque_Skins) == 'table' then
431 | for k, v in pairs(Masque_Skins) do
432 | if k ~= 'Blizzard' and v.Normal.Texture then
433 | local skin = { }
434 | -- Guess at dimensions
435 | if v.Icon.Height and v.Normal.Height then
436 | skin.padding = (v.Normal.Height - v.Icon.Height) / 4
437 | skin.row_spacing = v.Icon.Height / 16
438 | skin.size = v.Icon.Height / 2
439 | end
440 | skin.texture = v.Normal.Texture
441 | skin.name = ('|c22dddd22Masque:|r %s'):format(k)
442 | -- Apply existing tweak
443 | if lib.masque_tweaks[k] then
444 | for mk,mv in pairs(lib.masque_tweaks[k]) do
445 | skin[mk] = mv
446 | end
447 | end
448 | -- Register
449 | XLoot:RegisterSkin(k, skin)
450 | end
451 | end
452 | else -- Warn about outdated Masque
453 | print("XLoot: Use of masque skins requires the beta version of Masque.")
454 | end
455 | end
456 |
457 | XLoot:ApplySkinTweaks()
458 |
459 | -- Activate current skin
460 | self:SetSkin(self.db.profile.skin)
461 | end
462 |
463 | -------------------------------------------------------------------------------
464 | -- Skin access
465 |
466 | function XLoot:SetSkin(name)
467 | lib.current = lib.skins[lib.skins[name] and name or 'smooth']
468 | end
469 |
470 | --@do-not-package@
471 | local AC = LibStub('AceConsole-2.0', true)
472 | if AC then print = function(...) AC:PrintLiteral(...) end end
473 | --@end-do-not-package@
474 |
--------------------------------------------------------------------------------
/Modules/Master/Master.lua:
--------------------------------------------------------------------------------
1 | -- Create module
2 | local addon, L = XLoot:NewModule("Master")
3 | XLootMaster = addon
4 | -- Grab locals
5 | local print, wipe, match = print, table.wipe, string.match
6 | local RAID_CLASS_COLORS = CUSTOM_CLASS_COLORS or _G.RAID_CLASS_COLORS
7 | local hexColors = {}
8 | local classesInRaid, class_players, classes_english = {}, {}, {}
9 | local player_indices, index_name = {}, {}
10 | local randoms = {}
11 | local me = UnitName('player')
12 | local my_index, banker_index, disenchanter_index
13 | local candidate, color, lclass, className, slot, info, opt, eframe
14 | -- Libraries
15 | local LD
16 |
17 | -------------------------------------------------------------------------------
18 | -- Settings
19 | local defaults = {
20 | profile = {
21 | menu_roll = true,
22 | menu_disenchant = true,
23 | menu_disenchanters = "",
24 | menu_bank = true,
25 | menu_bankers = "",
26 | menu_self = true,
27 | confirm_qualitythreshold = MASTER_LOOT_THREHOLD,
28 | award_qualitythreshold = 2,
29 | award_channel = 'AUTO',
30 | award_channel_secondary = 'NONE',
31 | award_guildannounce = false,
32 | award_special = true,
33 | }
34 | }
35 |
36 | -------------------------------------------------------------------------------
37 | -- Module init
38 | local eframe = CreateFrame("Frame")
39 | function addon:OnInitialize()
40 | self:InitializeModule(defaults, eframe)
41 | opt = self.db.profile
42 | XLootMaster.opt = opt
43 | XLoot:SetSlashCommand("xlml", self.SlashHandler)
44 | end
45 |
46 | function addon:OnEnable()
47 | for i,class in ipairs(CLASS_SORT_ORDER) do
48 | classes_english[class] = true
49 | end
50 |
51 | for k, v in pairs(RAID_CLASS_COLORS) do
52 | hexColors[k] = "|c" .. v.colorStr
53 | end
54 | hexColors["UNKNOWN"] = string.format("|cff%02x%02x%02x", 0.6*255, 0.6*255, 0.6*255)
55 |
56 | if CUSTOM_CLASS_COLORS then
57 | local function update()
58 | for k, v in pairs(CUSTOM_CLASS_COLORS) do
59 | hexColors[k] = "|c" .. v.colorStr
60 | end
61 | end
62 | CUSTOM_CLASS_COLORS:RegisterCallback(update)
63 | update()
64 | end
65 | end
66 |
67 | -- Utility functions
68 | local function printall(...)
69 | if DEFAULT_CHAT_FRAME then
70 | DEFAULT_CHAT_FRAME:AddMessage(string.join(", ",tostringall(...)))
71 | end
72 | end
73 | addon.printall = printall
74 |
75 | local function dump(val)
76 | UIParentLoadAddOn("Blizzard_DebugTools")
77 | _G['xlminfostruct'] = val
78 | DevTools_DumpCommand('xlminfostruct')
79 | _G['xlminfostruct'] = nil
80 | end
81 | addon.dump = dump
82 |
83 | local function OutChannel(channel)
84 | if channel == "NONE" then return end
85 | local out = channel
86 | if channel == "AUTO" then
87 | if IsInRaid() then
88 | if UnitIsGroupLeader("player") or UnitIsGroupAssistant("player") or IsEveryoneAssistant() then
89 | out = "RAID_WARNING"
90 | elseif IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
91 | out = "INSTANCE_CHAT"
92 | else
93 | out = "RAID"
94 | end
95 | elseif IsInGroup() then
96 | if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
97 | out = "INSTANCE_CHAT"
98 | else
99 | out = "PARTY"
100 | end
101 | else
102 | out = "SAY"
103 | end
104 | end
105 | return out
106 | end
107 |
108 | -- Addon functions
109 | function addon.AnnounceAward(data)
110 | if data.quality >= opt.award_qualitythreshold then
111 | if data.special and not opt.award_special then return end
112 | local out = OutChannel(opt.award_channel)
113 | local text = (L.ITEM_AWARDED):format(data.pname,data.link)
114 | if data.special then
115 | text = text .. " ("..data.special..")"
116 | end
117 | if out then
118 | pcall(SendChatMessage, text, out)
119 | end
120 | local secondary = OutChannel(opt.award_channel_secondary)
121 | if secondary then
122 | pcall(SendChatMessage, text, secondary)
123 | end
124 | if opt.award_guildannounce and IsInGuild() then
125 | SendChatMessage(text, "GUILD")
126 | end
127 | end
128 | end
129 |
130 | function addon.GiveLoot(frame, special)
131 | local slot = LootFrame.selectedSlot
132 | local quality = LootFrame.selectedQuality
133 | local itemname = LootFrame.selectedItemName
134 | local id = frame.value
135 | local link = GetLootSlotLink(slot)
136 | local pname = index_name[id]
137 |
138 | local data = { slot = slot, link = link, special = special, quality = quality, pname = pname, id = id }
139 | local dialog
140 |
141 | if ( quality >= opt.confirm_qualitythreshold ) then
142 | dialog = StaticPopup_Show("CONFIRM_XLOOT_DISTRIBUTION", ITEM_QUALITY_COLORS[quality].hex..itemname..FONT_COLOR_CODE_CLOSE, pname)
143 | if dialog then
144 | dialog.data = data
145 | end
146 | else
147 | addon.AnnounceAward(data)
148 | GiveMasterLoot(slot, id)
149 | end
150 | CloseDropDownMenus()
151 | end
152 |
153 | function addon.SpawnRoll(frame)
154 | DoMasterLootRoll(frame.value)
155 | end
156 |
157 | function addon.RaidRoll(frame, players)
158 | -- Bail when loot frame is closed, avoiding nil error. Should be done better.
159 | if not GetLootSlotLink(LootFrame.selectedSlot) then return nil end
160 | local to = #players
161 | local out = OutChannel("AUTO")
162 | if to >=1 then
163 | eframe:RegisterEvent("CHAT_MSG_SYSTEM")
164 | if out then
165 | SendChatMessage("Raid Roll:"..GetLootSlotLink(LootFrame.selectedSlot),out) -- localize this
166 | local k,names = 1,""
167 | for i=1,to do
168 | names = (k==1) and (i..":"..index_name[i]) or (names..", "..i..":"..index_name[i])
169 | if i==to or k==3 then
170 | SendChatMessage(names,out)
171 | names = ""
172 | end
173 | k = k<3 and k+1 or 1
174 | end
175 | end
176 | RandomRoll(1,to)
177 | end
178 | end
179 |
180 | function addon:CHAT_MSG_SYSTEM(...)
181 | local who, roll, from, to = XLoot.Deformat(..., RANDOM_ROLL_RESULT)
182 | if who == me then
183 | eframe.value = tonumber(roll)
184 | eframe:UnregisterEvent("CHAT_MSG_SYSTEM")
185 | addon.GiveLoot(eframe)
186 | end
187 | end
188 |
189 | function addon.AddMenuTitle(title)
190 | info.isTitle = nil
191 | info.text = title
192 | info.textHeight = 12
193 | info.notCheckable = 1
194 | info.disabled = 1
195 | info.disablecolor = YELLOW_FONT_COLOR_CODE
196 | info.notClickable = 1
197 | UIDropDownMenu_AddButton(info)
198 | info.notClickable = nil
199 | info.disablecolor = nil
200 | end
201 |
202 | function addon.AddMenuSeparator()
203 | info.disabled = 1
204 | info.text = ""
205 | info.hasArrow = nil
206 | UIDropDownMenu_AddButton(info, level)
207 | end
208 |
209 | function addon.BuildPartyMenu(level)
210 | -- In a party
211 | if level == 1 then
212 | for i=1, MAX_PARTY_MEMBERS+1, 1 do
213 | candidate,lclass,className = GetMasterLootCandidate(slot,i)
214 | index_name[i] = candidate
215 | if candidate then
216 | -- Add candidate button
217 | info.text = candidate
218 | info.colorCode = hexColors[className] or hexColors["UNKNOWN"]
219 | info.textHeight = 12
220 | info.value = i
221 | info.notCheckable = 1
222 | info.hasArrow = nil
223 | info.isTitle = nil
224 | info.disabled = nil
225 | info.func = addon.GiveLoot
226 | UIDropDownMenu_AddButton(info)
227 | end
228 | end
229 | end
230 | end
231 |
232 | function addon.BuildRaidMenuRecipients(level)
233 | if level == 1 then
234 | if (my_index and opt.menu_self) or (banker_index and opt.menu_bank) or (disenchanter_index and opt.menu_disenchant) then
235 | -- XLootMaster.AddMenuSeparator()
236 | info.disabled = nil
237 | info.isTitle = nil
238 | info.text = L.RECIPIENTS
239 | info.colorCode = YELLOW_FONT_COLOR_CODE
240 | info.textHeight = 12
241 | info.hasArrow = 1
242 | info.notCheckable = 1
243 | info.value = "RECIPIENTS"
244 | info.func = nil
245 | info.disabled = nil
246 | UIDropDownMenu_AddButton(info)
247 | end
248 | elseif level == 2 then
249 | if UIDROPDOWNMENU_MENU_VALUE == "RECIPIENTS" then -- special recipients submenu
250 | if my_index then
251 | candidate,lclass,className = GetMasterLootCandidate(slot,my_index)
252 | if candidate and candidate == me then
253 | info.colorCode = hexColors[className] or hexColors["UNKNOWN"]
254 | info.isTitle = nil
255 | info.textHeight = 12
256 | info.value = my_index
257 | info.notCheckable = 1
258 | info.text = L.ML_SELF
259 | info.func = addon.GiveLoot
260 | info.arg1 = L.ML_SELF
261 | info.icon = "Interface\\GossipFrame\\VendorGossipIcon"
262 | UIDropDownMenu_AddButton(info,level)
263 | end
264 | end
265 | if banker_index and opt.menu_bank then
266 | candidate,lclass,className = GetMasterLootCandidate(slot,banker_index)
267 | if candidate and addon.listPriority(candidate, opt.menu_bankers) then
268 | info.colorCode = "|cffffffff"
269 | info.isTitle = nil
270 | info.textHeight = 12
271 | info.value = banker_index
272 | info.notCheckable = 1
273 | info.text = L.ML_BANKER.." ("..candidate..")"
274 | info.func = addon.GiveLoot
275 | info.arg1 = L.ML_BANKER
276 | info.icon = "Interface\\Minimap\\Tracking\\Banker"
277 | UIDropDownMenu_AddButton(info,level)
278 | end
279 | end
280 | if disenchanter_index and opt.menu_disenchant then
281 | candidate,lclass,className = GetMasterLootCandidate(slot,disenchanter_index)
282 | if candidate and addon.listPriority(candidate, opt.menu_disenchanters) then
283 | info.colorCode = "|cffffffff"
284 | info.isTitle = nil
285 | info.textHeight = 12
286 | info.value = disenchanter_index
287 | info.notCheckable = 1
288 | info.text = L.ML_DISENCHANTER.." ("..candidate..")"
289 | info.func = addon.GiveLoot
290 | info.arg1 = L.ML_DISENCHANTER
291 | info.icon = "Interface\\Buttons\\UI-GroupLoot-DE-Up"
292 | UIDropDownMenu_AddButton(info,level)
293 | end
294 | end
295 | end
296 | end
297 | end
298 |
299 | function addon.BuildMenuSpecialRolls(level)
300 | if level == 1 then
301 | info.isTitle = nil
302 | info.text = L.SPECIALROLLS
303 | info.colorCode = YELLOW_FONT_COLOR_CODE
304 | info.textHeight = 12
305 | info.hasArrow = 1
306 | info.notCheckable = 1
307 | info.value = "SPECIALROLLS"
308 | info.func = nil
309 | info.disabled = nil
310 | UIDropDownMenu_AddButton(info)
311 | elseif level == 2 then
312 | if UIDROPDOWNMENU_MENU_VALUE == "SPECIALROLLS" then -- special rolls submenu
313 | info.colorCode = "|cffffffff"
314 | info.isTitle = nil
315 | info.textHeight = 12
316 | info.value = slot
317 | info.notCheckable = 1
318 | info.hasArrow = nil
319 | info.text = REQUEST_ROLL
320 | info.func = addon.SpawnRoll
321 | info.icon = "Interface\\Buttons\\UI-GroupLoot-Dice-Up"
322 | UIDropDownMenu_AddButton(info,level)
323 |
324 | if IsInRaid() and next(randoms) and opt.menu_roll then
325 | info.colorCode = "|cffffffff"
326 | info.isTitle = nil
327 | info.textHeight = 12
328 | info.value = randoms[math.random(1, #randoms)]
329 | info.notCheckable = 1
330 | info.text = L.ML_RANDOM
331 | info.func = addon.RaidRoll
332 | info.arg1 = randoms
333 | info.icon = "Interface\\Buttons\\UI-GroupLoot-Coin-Up"
334 | UIDropDownMenu_AddButton(info,level)
335 | end
336 | end
337 | end
338 | end
339 |
340 | function addon.normalize_toon_list(str)
341 | str = str:gsub("%s+",",")
342 | str = str:gsub("%p+",",")
343 | str = str:lower()
344 | return ","..str..","
345 | end
346 |
347 | function addon.listPriority(name, list)
348 | list = addon.normalize_toon_list(list)
349 | return select(1,list:find(addon.normalize_toon_list(name)))
350 | end
351 |
352 | function addon.BuildRaidMenu(level)
353 | -- In a raid
354 | if level == 1 then
355 | wipe(player_indices)
356 | wipe(index_name)
357 | wipe(classesInRaid)
358 | wipe(class_players)
359 | wipe(randoms)
360 | local disenchant_rank, bank_rank = 10000, 10000
361 | my_index, banker_index, disenchanter_index = nil,nil,nil
362 | for i = 1, MAX_RAID_MEMBERS do
363 | candidate,lclass,className = GetMasterLootCandidate(slot,i)
364 | if candidate then
365 | classesInRaid[className] = lclass
366 | table.insert(randoms, i)
367 | if candidate == me then
368 | my_index = i
369 | end
370 | local br = addon.listPriority(candidate, opt.menu_bankers)
371 | if br and br < bank_rank then
372 | banker_index = i
373 | bank_rank = br
374 | end
375 | local dr = addon.listPriority(candidate, opt.menu_disenchanters)
376 | if dr and dr < disenchant_rank then
377 | disenchanter_index = i
378 | disenchant_rank = dr
379 | end
380 | player_indices[candidate] = i
381 | index_name[i] = candidate
382 | if not class_players[className] then class_players[className] = {} end
383 | table.insert(class_players[className],candidate)
384 | end
385 | end
386 | for i, class in ipairs(CLASS_SORT_ORDER) do
387 | local cname = classesInRaid[class]
388 | if cname then
389 | info.isTitle = nil
390 | info.text = cname
391 | info.colorCode = hexColors[class] or hexColors["UNKNOWN"]
392 | info.textHeight = 12
393 | info.hasArrow = 1
394 | info.notCheckable = 1
395 | info.value = class
396 | info.func = nil
397 | info.disabled = nil
398 | UIDropDownMenu_AddButton(info)
399 | end
400 | end
401 |
402 | addon.BuildRaidMenuRecipients(level)
403 |
404 | elseif level == 2 then
405 | -- raid class menu
406 | if classes_english[UIDROPDOWNMENU_MENU_VALUE] then -- classes submenus
407 | if next(class_players[UIDROPDOWNMENU_MENU_VALUE]) then
408 | table.sort(class_players[UIDROPDOWNMENU_MENU_VALUE])
409 | for _,cand in ipairs(class_players[UIDROPDOWNMENU_MENU_VALUE]) do
410 | -- Add candidate button
411 | info.text = cand
412 | info.colorCode = hexColors[UIDROPDOWNMENU_MENU_VALUE] or hexColors["UNKNOWN"]
413 | info.textHeight = 12
414 | info.value = player_indices[cand]
415 | info.notCheckable = 1
416 | info.disabled = nil
417 | info.func = addon.GiveLoot
418 | UIDropDownMenu_AddButton(info,level)
419 | end
420 | end
421 | end
422 |
423 | addon.BuildRaidMenuRecipients(level)
424 | addon.BuildMenuSpecialRolls(level)
425 |
426 | end
427 | end
428 |
429 | function addon.DropdownInit()
430 | slot = LootFrame.selectedSlot or 0
431 | info = UIDropDownMenu_CreateInfo()
432 |
433 | if UIDROPDOWNMENU_MENU_LEVEL == 1 then
434 |
435 | addon.AddMenuTitle(GIVE_LOOT)
436 | if ( IsInRaid() ) then
437 | addon.BuildRaidMenu(UIDROPDOWNMENU_MENU_LEVEL)
438 | else
439 | addon.BuildPartyMenu(UIDROPDOWNMENU_MENU_LEVEL)
440 | end
441 | -- XLootMaster.AddMenuSeparator()
442 | addon.BuildMenuSpecialRolls(UIDROPDOWNMENU_MENU_LEVEL)
443 |
444 | elseif UIDROPDOWNMENU_MENU_LEVEL == 2 then
445 |
446 | addon.BuildRaidMenu(UIDROPDOWNMENU_MENU_LEVEL)
447 |
448 | end
449 | end
450 |
451 | local GroupLootDropDown = CreateFrame("Frame", nil, UIParent, "UIDropDownMenuTemplate")
452 | UIDropDownMenu_Initialize(GroupLootDropDown, addon.DropdownInit, "MENU")
453 | hooksecurefunc("MasterLooterFrame_Show", function(frame)
454 | if frame == LootFrame.selectedLootButton then
455 | ToggleDropDownMenu(1, nil, GroupLootDropDown, LootFrame.selectedLootButton, 0, 0);
456 | MasterLooterFrame:Hide()
457 | end
458 | end)
459 |
460 | BINDING_HEADER_XLOOTMASTER = "XLootMaster"
461 |
462 | StaticPopupDialogs["CONFIRM_XLOOT_DISTRIBUTION"] = {
463 | text = CONFIRM_LOOT_DISTRIBUTION,
464 | button1 = YES,
465 | button2 = NO,
466 | OnAccept = function(self,data)
467 | addon.AnnounceAward(data)
468 | GiveMasterLoot(data.slot, data.id);
469 | end,
470 | timeout = 0,
471 | hideOnEscape = 1,
472 | preferredIndex = 3,
473 | }
474 |
475 | function addon.SlashHandler(msg)
476 | addon.ShowOptions()
477 | end
478 |
479 |
480 |
481 |
--------------------------------------------------------------------------------
/Modules/Monitor/Monitor.lua:
--------------------------------------------------------------------------------
1 | -- Create module
2 | local addon, L = XLoot:NewModule("Monitor")
3 |
4 | XLootMonitor = CreateFrame("Frame", "XLootMonitor", UIParent)
5 | XLootMonitor.addon = addon
6 |
7 | -- Grab locals
8 | local print, opt, eframe, anchor = print
9 | local CopperToString, FancyPlayerName = XLoot.CopperToString, XLoot.FancyPlayerName
10 | local table_insert, table_remove = table.insert, table.remove
11 | local me = UnitName("player")
12 |
13 | local GetItemInfo = C_Item and C_Item.GetItemInfo or GetItemInfo
14 |
15 | -------------------------------------------------------------------------------
16 | -- Settings
17 |
18 | local defaults = {
19 | profile = {
20 | anchor = {
21 | direction = "up",
22 | alignment = "left",
23 | visible = true,
24 | scale = 1.0,
25 | draggable = true,
26 | offset = 0,
27 | spacing = 2,
28 | x = UIParent:GetWidth() * .75,
29 | y = UIParent:GetHeight() * .15,
30 | },
31 | name_width = 50,
32 | gradients = false,
33 |
34 | threshold_own = 2,
35 | threshold_other = 3,
36 | show_coin = false,
37 | show_currency = true,
38 | show_crafted = false,
39 |
40 | show_totals = false,
41 | totals_delay = 0.5,
42 | use_altoholic = true,
43 | show_ilvl = false,
44 |
45 | fade_own = 10,
46 | fade_other = 5,
47 |
48 | font = STANDARD_TEXT_FONT,
49 | font_size_loot = 12,
50 | font_size_quantity = 10,
51 | font_size_ilvl = 8,
52 | font_flag = "OUTLINE",
53 | }
54 | }
55 |
56 |
57 | ----------------------------------------------------------------------
58 | -- Helpers
59 |
60 | local numberize = function(v)
61 | if v <= 9999 then return v end
62 | if v >= 1000000 then
63 | local value = string.format("%.1fm", v/1000000)
64 | return value
65 | elseif v >= 10000 then
66 | local value = string.format("%.0fk", v/1000)
67 | return value
68 | end
69 | end
70 |
71 | -------------------------------------------------------------------------------
72 | -- Module init
73 |
74 | function addon:OnInitialize()
75 | eframe = CreateFrame("Frame")
76 | self:InitializeModule(defaults, eframe)
77 | XLoot:SetSlashCommand("xlm", self.SlashHandler)
78 | opt = self.db.profile
79 | end
80 |
81 | function addon:OnEnable()
82 | -- Register for loot events
83 | LibStub("LootEvents"):RegisterLootCallback(self.LOOT_EVENT)
84 | eframe:RegisterEvent("MODIFIER_STATE_CHANGED")
85 | -- Set up skins
86 | XLoot:MakeSkinner(self, {
87 | default = { gradient = opt.gradients },
88 | anchor = { r = .4, g = .4, b = .4, a = .6, gradient = false },
89 | anchor_pretty = { r = .6, g = .6, b = .6, a = .8 },
90 | item = { backdrop = false, gradient = opt.gradients },
91 | item_highlight = { type = "highlight", layer = "overlay" },
92 | row_highlight = { type = "highlight" }
93 | })
94 | -- Set up anchor
95 | anchor = XLoot.Stack:CreateStaticStack(self.CreateRow, L.anchor, opt.anchor)
96 | self:Skin(anchor, XLoot.opt.skin_anchors and 'anchor_pretty' or 'anchor')
97 | end
98 |
99 | function addon:ApplyOptions()
100 | opt = self.opt
101 | anchor:UpdateSVData(opt.anchor)
102 | addon:Restack()
103 | end
104 |
105 | local events = {}
106 | function events.item(player, link, num)
107 | if link and link:match("|Hitem:") then -- Proper items
108 | local name, _, quality, level, _, _, _, _, _, icon = GetItemInfo(link)
109 | if not name or type(quality) ~= "number" then
110 | print(name and "Quality is not a number" or "Name is nil")
111 | return false
112 | end
113 | if (player == me and opt.threshold_own or opt.threshold_other) > quality then
114 | return -- Doesn't meet threshold requirements
115 | end
116 | local r, g, b = C_Item.GetItemQualityColor(quality)
117 | local nr, ng, nb
118 | if player ~= me then
119 | player, nr, ng, nb = FancyPlayerName(player, select(2, UnitClass(player)), opt)
120 | else
121 | player = nil
122 | end
123 | local row = addon:AddRow(icon, (player and opt.fade_other or opt.fade_own), r, g, b)
124 | local num = tonumber(num) or 1
125 | row:SetTexts(player, num > 1 and ("%sx%d"):format(link, num) or link, nil, nr, ng, nb)
126 | if opt.show_totals then
127 | row.timeToTotal = opt.totals_delay
128 | end
129 | if opt.show_ilvl and level > 1 then
130 | local ilvl = GetDetailedItemLevelInfo(link)
131 | row.ilvl:SetText(ilvl)
132 | end
133 | row.item = link
134 | elseif link and link:match("|Hbattlepet:") then -- Battlepets. Really?
135 | -- local _, speciesID, level, breedQuality, maxHealth, power, speed, battlePetID = strsplit(":", link)
136 | else
137 | -- print("Unknown or invalid link type")
138 | -- return false
139 | end
140 | end
141 |
142 | function events.coin(coin_string, copper)
143 | if opt.show_coin then
144 | addon:AddRow(C_CurrencyInfo.GetCoinIcon(copper), opt.fade_own, .5, .5, .5, .5, .5, .5):SetTexts(nil, CopperToString(copper))
145 | end
146 | end
147 |
148 | function events.currency(id, num)
149 | -- Not sure what event is causing us to capture nothing for (%d+), this isn't ideal
150 | if id ~= nil and opt.show_currency then
151 | local num = tonumber(num) or 1
152 | local c = C_CurrencyInfo.GetCurrencyInfo(id)
153 | if c then
154 | addon:AddRow(c.iconFileID, opt.fade_own, 1, 1, 1, 1, 1, 1):SetTexts(nil, num > 1 and ("%s x%d"):format(c.name, num) or c.name, c.quantity)
155 | end
156 | end
157 | end
158 |
159 | function events.crafted(link, num)
160 | if opt.show_crafted then
161 | events.item(me, link, num)
162 | end
163 | end
164 |
165 | function addon.LOOT_EVENT(event, pattern, ...)
166 | if events[event] and events[event](...) == false then
167 | print("XLoot Monitor: Error handling event", event, pattern, ...)
168 | end
169 | end
170 |
171 | local mouse_focus
172 | function addon:MODIFIER_STATE_CHANGED(self, modifier, state)
173 | if mouse_focus and MouseIsOver(mouse_focus) then
174 | mouse_focus:ShowTooltip()
175 | end
176 | end
177 |
178 | local pool, stack, active = {}, {}, false
179 | stack[0] = anchor
180 |
181 | local timer = 0
182 | function addon.EframeUpdate(self, elapsed)
183 | timer = timer + elapsed
184 | for i,row in ipairs(stack) do
185 | -- Deferred total calculation due to GetItemCount reliability
186 | local ttt = row.timeToTotal
187 | if ttt then
188 | ttt = ttt - elapsed
189 | if ttt <= 0 then
190 | ttt = nil
191 | local total
192 | if opt.use_altoholic and Altoholic then
193 | total = Altoholic:GetItemCount(Altoholic:GetIDFromLink(row.item))
194 | else
195 | total = C_Item.GetItemCount(row.item)
196 | end
197 | if total and total > 1 then
198 | row.total:SetText(numberize(total))
199 | end
200 | end
201 | row.timeToTotal = ttt
202 | end
203 | -- Animation
204 | local remaining = row.expires - timer
205 | if remaining < 0 then
206 | row:SetAlpha(0)
207 | addon:RemoveRow(row)
208 | elseif remaining <= .5 then
209 | row:SetAlpha(remaining * 2)
210 | else
211 | local since = timer - row.started
212 | if since <= .5 then
213 | row:SetAlpha(since * 2)
214 | end
215 | end
216 | end
217 | end
218 |
219 | function addon:RemoveRow(row)
220 | row:Hide()
221 | for i,v in ipairs(stack) do
222 | if v == row then
223 | table_remove(stack, i)
224 | table_insert(pool, row)
225 | if stack[i] then
226 | anchor:AnchorChild(stack[i], stack[i-1])
227 | end
228 | break
229 | end
230 | end
231 |
232 | -- Disable OnUpdate
233 | if #stack == 0 then
234 | active, timer = false, 0
235 | eframe:SetScript("OnUpdate", nil)
236 | end
237 | end
238 |
239 | function addon:AddRow(icon, fade_time, ir, ig, ib, rr, rg, rb)
240 | -- Acquire
241 | local row = table_remove(pool)
242 | if not row then
243 | row = self.CreateRow()
244 | end
245 |
246 | -- Set up row
247 | row.icon:SetTexture(icon)
248 | row.icon_frame:SetBorderColor(ir, ig, ib)
249 | row:SetBorderColor(rr or ir, rg or ig, rb or ib)
250 | row.expires = timer + fade_time
251 | row.started = timer
252 | row.item = nil
253 |
254 | -- Anchor
255 | anchor:AnchorChild(row)
256 | table_insert(stack, 1, row)
257 | -- Reanchor second newest
258 | if stack[2] then
259 | anchor:AnchorChild(stack[2], row)
260 | end
261 | row:Show()
262 |
263 | -- Enable OnUpdate
264 | if not active then
265 | active = true
266 | eframe:SetScript("OnUpdate", self.EframeUpdate)
267 | end
268 | return row
269 | end
270 |
271 | function addon:Restack()
272 | for i,v in ipairs(stack) do
273 | anchor:AnchorChild(v, i > 1 and stack[i-1] or nil)
274 | v:ApplyOptions()
275 | end
276 | end
277 |
278 | -------------------------------------------------------------------------------
279 | -- Frame methods
280 | do
281 | local function ShowTooltip(self)
282 | if self.item then
283 | GameTooltip:SetOwner(self, 'ANCHOR_TOPLEFT', 28, 0)
284 | GameTooltip:SetHyperlink(self.item)
285 | CursorUpdate(self)
286 | end
287 | end
288 |
289 | local function OnEnter(self)
290 | mouse_focus = self
291 | if self._highlights then
292 | self.icon_frame:ShowHighlight()
293 | end
294 | self:ShowTooltip()
295 | end
296 |
297 | local function OnLeave(self)
298 | mouse_focus = nil
299 | if self._highlights then
300 | self.icon_frame:HideHighlight()
301 | end
302 | GameTooltip:Hide()
303 | ResetCursor()
304 | end
305 |
306 | local function OnClick(self, button)
307 | if IsModifiedClick() and self.item then
308 | HandleModifiedItemClick(self.item)
309 | end
310 | end
311 |
312 | local function FitToText(self)
313 | self:SetWidth(self.name:GetWidth() + self.text:GetWidth() + 8 + self.icon_frame:GetWidth())
314 | end
315 |
316 | local function SetTexts(self, name, text, total, nr, ng, nb, tr, tg, tb)
317 | self.name:SetText(name and name.." " or nil)
318 | self.text:SetText(text)
319 | self.ilvl:SetText()
320 | -- Show total after 0.5 seconds to get a valid count
321 | self.total:SetText()
322 | self.name:SetVertexColor(nr or 1, ng or 1, nb or 1)
323 | self.text:SetVertexColor(nr or 1, ng or 1, nb or 1)
324 | if name then
325 | self.name:SetWidth(opt.name_width)
326 | else
327 | self.name:SetWidth(0)
328 | end
329 | self:FitToText()
330 | end
331 |
332 | local widgets = {
333 | { "icon_frame", 0 },
334 | { "name", 2 },
335 | { "text", 0 },
336 | }
337 |
338 | local function ApplyOptions(self)
339 | local a, b, xmod = "LEFT", "RIGHT", 1
340 | if opt.anchor.alignment == "right" then
341 | a, b, xmod = b, a, -1
342 | end
343 | self.icon_frame:ClearAllPoints()
344 | self.icon_frame:SetPoint(a)
345 |
346 | self.name:ClearAllPoints()
347 | self.name:SetPoint(a, self.icon_frame, b, 2 * xmod, 0)
348 | self.name:SetJustifyH(a)
349 | self.name:SetFont(opt.font, opt.font_size_loot, opt.font_flag)
350 |
351 | self.text:ClearAllPoints()
352 | self.text:SetPoint(a, self.name, b)
353 | self.text:SetJustifyH(a)
354 | self.text:SetFont(opt.font, opt.font_size_loot, opt.font_flag)
355 |
356 | self.ilvl:SetFont(opt.font, opt.font_size_ilvl, opt.font_flag)
357 | self.total:SetFont(opt.font, opt.font_size_quantity, opt.font_flag)
358 |
359 | self:FitToText()
360 | end
361 |
362 | function addon.CreateRow()
363 | local frame = CreateFrame("Button", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
364 | frame:SetFrameLevel(anchor:GetFrameLevel())
365 | frame:SetHeight(24)
366 | frame:SetWidth(250)
367 |
368 | addon:Skin(frame)
369 | addon:Highlight(frame, "row_highlight")
370 | frame:SetHighlightColor(.8, .8, .8)
371 |
372 | frame:RegisterForClicks("LeftButtonUp", "RightButtonUp")
373 | frame:SetScript("OnClick", OnClick)
374 | frame:SetScript("OnEnter", OnEnter)
375 | frame:SetScript("OnLeave", OnLeave)
376 |
377 | -- Item icon (For skin border)
378 | local icon_frame = CreateFrame("Frame", nil, frame, BackdropTemplateMixin and "BackdropTemplate")
379 | icon_frame:SetWidth(28)
380 | icon_frame:SetHeight(28)
381 | addon:Skin(icon_frame, "item")
382 | addon:Highlight(icon_frame, "item_highlight")
383 | frame.icon_frame = icon_frame
384 |
385 | -- Item texture
386 | local icon = icon_frame:CreateTexture(nil, "BACKGROUND")
387 | icon:SetPoint("TOPLEFT", 3, -3)
388 | icon:SetPoint("BOTTOMRIGHT", -3, 3)
389 | icon:SetTexCoord(.07,.93,.07,.93)
390 | frame.icon = icon
391 |
392 | local ilvl = icon_frame:CreateFontString(nil, "OVERLAY")
393 | ilvl:SetPoint("BOTTOM", icon_frame, "BOTTOM", 0, 0)
394 | ilvl:SetJustifyH("CENTER")
395 | frame.ilvl = ilvl
396 |
397 | frame.name = frame:CreateFontString(nil, "OVERLAY")
398 |
399 | frame.text = frame:CreateFontString(nil, "OVERLAY")
400 |
401 | local total = icon_frame:CreateFontString(nil, "OVERLAY")
402 | total:SetPoint("CENTER", icon_frame, "CENTER", 0, 0)
403 | total:SetJustifyH("CENTER")
404 | frame.total = total
405 |
406 | frame.FitToText = FitToText
407 | frame.SetTexts = SetTexts
408 | frame.ShowTooltip = ShowTooltip
409 | frame.ApplyOptions = ApplyOptions
410 |
411 | frame:ApplyOptions()
412 |
413 | return frame
414 | end
415 | end
416 |
417 | function addon:UpdateAnchors(...)
418 | if opt.anchor.visible then
419 | anchor:Show()
420 | else
421 | anchor:Hide()
422 | end
423 | end
424 |
425 | function addon.SlashHandler(msg)
426 | if anchor:IsShown() then
427 | anchor:Hide()
428 | else
429 | anchor:Show()
430 | end
431 | end
432 |
433 | local items = {
434 | { 52722 },
435 | { 31304 },
436 | { 37254 },
437 | { 13262 },
438 | { 15487 },
439 | { 72120 },
440 | { 2589 }
441 | }
442 | local currencies = {
443 | 81,
444 | 1728
445 | }
446 | for i,v in ipairs(items) do
447 | GetItemInfo(v[1])
448 | end
449 |
450 | local players = {
451 | { UnitName("player"), select(2, UnitClass('player')) },
452 | { 'Player1', 'MAGE' },
453 | { 'Player2', 'PRIEST' },
454 | { 'Player3', 'WARRIOR' },
455 | { 'Player4', 'SHAMAN' }
456 | }
457 |
458 | local random = math.random
459 | local function random_player(is_me)
460 | return is_me and players[1][1] or players[random(2, 5)][1]
461 | end
462 |
463 | local function random_item_num(max)
464 | return random(1, 2) == 2 and random(1, max or 20) or 1
465 | end
466 |
467 | local function random_item_link()
468 | return select(2, GetItemInfo(items[random(1, #items)][1]))
469 | end
470 |
471 | local function test_item(event, is_me)
472 | addon.LOOT_EVENT('item', event, random_player(is_me), random_item_link(), random_item_num())
473 | end
474 |
475 | local function test_coin(event, is_me)
476 | addon.LOOT_EVENT('coin', event, random_player(is_me), random(1, 500000), "TODO")
477 | end
478 |
479 | local function test_currency(event)
480 | for _,id in ipairs(currencies) do
481 | addon.LOOT_EVENT('currency', event, id, random_item_num(5))
482 | end
483 | end
484 |
485 | local function test_crafted(event)
486 | addon.LOOT_EVENT('crafted', event, random_item_link(), random_item_num())
487 | end
488 |
489 | local function test_battlepet(event)
490 | print("Testing battlepet event")
491 | addon.LOOT_EVENT('item', "LOOT_ITEM_PUSHED_SELF", random_player(true), "|cff0070dd|Hbattlepet:868:1:3:158:10:12:0x0000000000000000|h[Pandaren Water Spirit]|h|r", 1)
492 | end
493 |
494 | local tests = {
495 | { test_item, "LOOT_ITEM" },
496 | { test_item, "LOOT_ITEM_SELF", true },
497 | { test_item, "LOOT_ITEM_MULTIPLE" },
498 | { test_item, "LOOT_ITEM_SELF_MULTIPLE", true },
499 | { test_item, "LOOT_ITEM_PUSHED_SELF", true },
500 | { test_item, "LOOT_ITEM_PUSHED_SELF_MULTIPLE", true },
501 | { test_coin, "LOOT_MONEY" },
502 | { test_coin, "LOOT_MONEY_SPLIT", true },
503 | { test_coin, "YOU_LOOT_MONEY", true },
504 | { test_currency, "CURRENCY_GAINED" },
505 | { test_currency, "CURRENCY_GAINED_MULTIPLE" },
506 | { test_crafted, "LOOT_ITEM_CREATED_SELF" },
507 | { test_crafted, "CURRENCY_GAINED_MULTIPLE" },
508 | { test_battlepet }
509 |
510 | }
511 |
512 | local queue, queueframe, tick = {}, CreateFrame("Frame"), 0
513 |
514 | local function queue_update(self, elapsed)
515 | tick = tick + elapsed
516 | if tick > 0.5 then
517 | tick = 0
518 | local time = GetTime()
519 | for k,v in pairs(queue) do
520 | if v[1] < time then
521 | if v[3] then
522 | print("Testing "..v[3])
523 | end
524 | v[2](select(3, unpack(v)))
525 | queue[k] = nil
526 | end
527 | end
528 | end
529 | end
530 |
531 | local qactive = false
532 |
533 | function XLootMonitor.TestSettings()
534 | local now = GetTime()
535 | -- for i=1,15 do
536 | -- table.insert(queue, { now + i, unpack(tests[random(1, #tests)]) })
537 | -- end
538 | if not qactive then
539 | queueframe:SetScript("OnUpdate", queue_update)
540 | qactive = true
541 | end
542 | for i,v in ipairs(tests) do
543 | table.insert(queue, { now + i * .5, unpack(v) })
544 | end
545 | end
546 |
547 | XLoot:SetSlashCommand("xlmd", XLootMonitor.TestSettings)
548 |
--------------------------------------------------------------------------------
/Modules/Options/localization.lua:
--------------------------------------------------------------------------------
1 | -- See: http://wow.curseforge.com/addons/xloot/localization/ to create or fix translations
2 | local locales = {
3 | enUS = {
4 | Core = {
5 | panel_title = "Global options",
6 | details = "Skin is applied to all XLoot modules. Most other settings currently require a /reload to be applied. Please open a ticket with any issues.\nTo turn off a single module, disable it like any normal addon.",
7 | skin = "Skin",
8 | skin_desc = "Select skin to use. Includes Masque skins",
9 | skin_anchors = "Apply to anchors",
10 | skin_anchors_desc = "Apply skin to anchors that XLoot uses",
11 | module_header = "Module options",
12 | },
13 | Frame = {
14 | panel_title = "Loot Frame",
15 | panel_desc = "Provides a adjustable loot frame",
16 | -- Group labels
17 | frame_options = "Frame settings",
18 | slot_options = "Loot slots",
19 | link_button = "Link all button",
20 | autolooting = "Auto-looting",
21 | colors = "Colors",
22 |
23 | -- Option labels
24 | autoloot_currency = "Auto loot currency",
25 | autoloot_currency_desc = "When to automatically loot currency",
26 | autoloot_quest = "Auto loot quest items",
27 | autoloot_quest_desc = "When to automatically loot quest items",
28 | autoloot_tradegoods = "Auto loot trade goods",
29 | autoloot_tradegoods_desc = "When to automatically loot any item of Trade Goods type",
30 | autoloot_all = "Auto loot everything",
31 | autoloot_list = "Auto loot listed items",
32 | autoloot_list_desc = "When to automatically loot listed items",
33 | autoloot_item_list = "Items to loot",
34 | -- frame_scale = "Frame scale",
35 | -- frame_alpha = "Frame alpha",
36 | frame_color_border = "Frame border color",
37 | frame_color_backdrop = "Frame backdrop color",
38 | frame_color_gradient = "Frame gradient color",
39 | frame_width_automatic = "Automatically expand frame",
40 | frame_width = "Frame width",
41 | old_close_button = "Use old close button",
42 | loot_highlight = "Highlight slots on mouseover",
43 | -- loot_alpha = "Slot alpha",
44 | loot_color_border = "Loot border color",
45 | loot_color_backdrop = "Loot backdrop color",
46 | loot_color_gradient = "Loot gradient color",
47 | loot_color_info = "Information text color",
48 | loot_collapse = "Collapse looted slots",
49 | loot_icon_size = "Loot icon size",
50 | loot_row_height = "Loot row height",
51 | quality_color_frame = "Color frame border by top quality",
52 | quality_color_slot = "Color loot border by quality",
53 | loot_texts_info = "Show detailed information",
54 | loot_texts_bind = "Show loot bind type",
55 | loot_texts_lock = "Show locked status",
56 | loot_buttons_auto = "Autoloot shortcut",
57 | loot_buttons_auto_desc = "A button to add any item to your auto-looting list (See below)\nOnly shown when the item would be autolooted",
58 | font_size_info = "Loot information",
59 | font_size_bottombuttons = "Linkall/Close",
60 | frame_snap = "Snap frame to mouse",
61 | frame_snap_offset_x = "Horizontal snap offset",
62 | frame_snap_offset_y = "Vertical snap offset",
63 | frame_grow_upwards = "Expand frame upwards",
64 | frame_draggable = "Loot frame draggable",
65 | linkall_threshold = "Minimum chat link quality",
66 | linkall_channel = "Default chat link channel",
67 | linkall_channel_secondary = "Secondary chat link channel",
68 | linkall_show = "Link button visibility",
69 | linkall_first_only = "Only link top item",
70 |
71 | autolooting_text = "XLoot's autolooting features act separately from the default UI. As such, if both are enabled, you may recieve warnings like 'that object is busy'. They are safe to ignore, but can be resolved by picking one autoloot method to use exclusively.",
72 |
73 | autolooting_list = "To automatically loot specific items, list them below.\n Example: Linen Cloth,Ashbringer,Copper Ore",
74 |
75 | autolooting_details = "XLoot will choose the highest setting when deciding to loot a slot. This allows, for example, auto looting everything while solo yet only quest items and money while in a group.",
76 |
77 | show_slot_errors = "Looting errors in chat",
78 | show_slot_errors_details = "Print a chat message when a loot item cannot be shown for some reason. Most of these should be able to be safely ignored.",
79 | },
80 | Group = {
81 | panel_title = "Group Loot",
82 | -- Group labels
83 | anchors = "Anchors",
84 | rolls = "Roll frames",
85 | other_frames = "Other frames",
86 | roll_tracking = "What rolls to show",
87 | alerts = "Loot alerts",
88 | extra_info = "Details",
89 |
90 | -- Header labels
91 | expiration = "Expiration (in seconds)",
92 |
93 | -- Option labels
94 | text_outline = "Outline text",
95 | text_outline_desc = "Draws a dark outline around text on roll frames",
96 | text_time = "Show time remaining",
97 | text_time_desc = "Displays seconds remaining to roll over item icon",
98 | text_ilvl = "Show item level",
99 | role_icon = "Show role icons",
100 | win_icon = "Show winning type icon",
101 | show_decided = "Show decided",
102 | show_undecided = "List waiting players",
103 | show_undecided_desc = "List players who have not chosen how to roll",
104 | hook_alert = "Modify loot alerts",
105 | hook_alert_desc = "('You won..' popups)\nAttach loot alerts to a movable anchor.\n\nDisabling this can improve compatibility with other loot addons. \n\n(Requires ReloadUI)",
106 | alert_skin = "Skin loot alert frames",
107 | alert_offset = "Vertical spacing",
108 | alert_background = "Show background",
109 | alert_icon_frame = "Show icon frame",
110 | hook_bonus = "Modify bonus rolls",
111 | hook_bonus_desc = "Attach bonus loot rolls to a movable anchor.\n\nDisabling this can improve compatibility with other loot addons. \n\n(Requires ReloadUI)",
112 | bonus_skin = "Skin bonus roll frame",
113 | roll_width = "Roll frame width",
114 | roll_button_size = "Roll button size",
115 | roll_anchor_visible = "Roll anchor visible",
116 | alert_anchor_visible = "Loot alerts anchor visible",
117 | alert_anchor_visible_desc = "Refers to 'You won..' popups",
118 | track_all = "Track all rolls",
119 | track_player_roll = "Track items you roll on",
120 | track_by_threshold = "Track items by minimum quality",
121 | expire_won = "Won rolls",
122 | expire_lost = "Lost/Passed rolls",
123 | preview_show = "Show Preview",
124 | equip_prefix = "Show equippable prefix",
125 | equip_prefix_desc = "Prefixes item names to indicate if a item can be equipped or is a upgrade. (Upgrade prefix requires the Pawn addon)",
126 | prefix_equippable = "Equippable prefix",
127 | prefix_upgrade = "Upgrade prefix",
128 |
129 | hook_warning_text = "Hooking the loot alert and bonus roll frames has rarely been reported to cause issues such as not seeing bonus rolls.\n\nBy enabling these options you acknowledge that you understand and accept that risk.\n",
130 | },
131 | Monitor = {
132 | panel_title = "Loot Monitor",
133 | -- Group labels
134 | testing = "Testing",
135 | anchor = "Anchor",
136 | thresholds = "Quality thresholds",
137 | fading = "Row fade times (in seconds)",
138 | details = "Details",
139 | -- Option labels
140 | test_settings = "Click to test settings",
141 | visible = "Anchor visible",
142 | show_crafted = "Crafted",
143 | show_totals = "Show total items in inventory",
144 | totals_delay = "Totals delay",
145 | totals_delay_desc = "Time to wait before asking the game how many items you have, as the item events do not reliably match up to inventory counts",
146 | use_altoholic = "Include bank (Altoholic)",
147 | font_size_ilvl = "Item level",
148 | name_width = "Player name width",
149 | gradients = "Gradients",
150 | },
151 | Master = {
152 | panel_title = "Loot Master",
153 | -- Group labels
154 | specialrecipients = "Special Recipients Menu",
155 | raidroll = "Special Rolls Menu",
156 | awardannounce = "Announce Item Distribution",
157 | -- Option labels
158 | confirm_qualitythreshold = "Minimum confirm quality",
159 | menu_roll = "Show raid roll",
160 | menu_disenchant = "Show disenchanter",
161 | menu_disenchanters = "Disenchant character names",
162 | menu_bank = "Show banker",
163 | menu_bankers = "Banker character names",
164 | menu_self = "Show self",
165 | award_qualitythreshold = "Minimum announce quality",
166 | award_channel = "Default chat announce channel",
167 | award_channel_secondary = "Secondary chat announce channel",
168 | award_guildannounce = "Echo in guild chat",
169 | award_special = "Announce special recipients",
170 | },
171 | font = "Font",
172 | font_sizes = "Sizes",
173 | font_size_loot = "Loot",
174 | font_size_quantity = "Quantity",
175 | font_flag = "Flag",
176 | desc_channel_auto = "Highest available",
177 | growth_direction = "Growth direction",
178 | alignment = "Alignment",
179 | scale = "Scale",
180 | width = "Width",
181 | alpha = "Opacity",
182 | spacing = "Spacing",
183 | offset = "Offset",
184 | visible = "Visible",
185 | padding = "Padding",
186 | items_others = "Others' items",
187 | items_own = "Own items",
188 | up = "Up",
189 | down = "Down",
190 | left = "Left",
191 | right = "Right",
192 | top = "Top",
193 | bottom = "Bottom",
194 | minimum_quality = "Minimum quality",
195 | when_never = "Never",
196 | when_solo = "Solo",
197 | when_always = "Always",
198 | when_auto = "Automatic",
199 | when_group = "In groups",
200 | when_party = "In parties",
201 | when_raid = "In raids",
202 | confirm_reset_profile = "This will reset all options for this profile. Are you sure?",
203 | profile = "Profile",
204 | message_reloadui_warning = "|c2244dd22%s|r: Changing |c2244dd22%s|r requires you to reload your UI before continuing to play: |c2244dd22/reload ui|r",
205 | },
206 | -- Possibly localized
207 | ptBR = {
208 |
209 | },
210 | frFR = {
211 |
212 | },
213 | deDE = {
214 |
215 | },
216 | koKR = {
217 |
218 | },
219 | esMX = {
220 |
221 | },
222 | ruRU = {
223 |
224 | },
225 | zhCN = {
226 |
227 | },
228 | esES = {
229 |
230 | },
231 | zhTW = {
232 |
233 | },
234 | }
235 |
236 | -- Automatically inserted translations
237 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
238 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
239 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
240 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
241 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
242 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
243 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
244 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
245 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Options")@
246 |
247 | -- Manually express subtables because apparently I'm the only one who thought to use namespaces the simple way
248 |
249 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
250 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
251 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
252 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
253 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
254 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
255 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
256 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
257 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Core")@
258 |
259 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
260 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
261 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
262 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
263 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
264 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
265 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
266 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
267 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Frame")@
268 |
269 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
270 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
271 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
272 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
273 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
274 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
275 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
276 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
277 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Group")@
278 |
279 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
280 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
281 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
282 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
283 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
284 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
285 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
286 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
287 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Monitor")@
288 |
289 | --@localization(locale="ptBR", format="lua_additive_table", table-name="locales.ptBR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
290 | --@localization(locale="frFR", format="lua_additive_table", table-name="locales.frFR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
291 | --@localization(locale="deDE", format="lua_additive_table", table-name="locales.deDE", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
292 | --@localization(locale="koKR", format="lua_additive_table", table-name="locales.koKR", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
293 | --@localization(locale="esMX", format="lua_additive_table", table-name="locales.esMX", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
294 | --@localization(locale="ruRU", format="lua_additive_table", table-name="locales.ruRU", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
295 | --@localization(locale="zhCN", format="lua_additive_table", table-name="locales.zhCN", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
296 | --@localization(locale="esES", format="lua_additive_table", table-name="locales.esES", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
297 | --@localization(locale="zhTW", format="lua_additive_table", table-name="locales.zhTW", handle-subnamespaces="subtable", handle-unlocalized="ignore", namespace="Master")@
298 |
299 | XLoot:Localize("Options", locales)
300 |
--------------------------------------------------------------------------------
/Modules/Options/Options.lua:
--------------------------------------------------------------------------------
1 | --[=[ This addon provides options for all modules.
2 | Options are preferrably defined as "BetterOptions" tables, which functionally resemble AceOptionsTables but are much more concise.
3 |
4 | The point of this abstraction layer is that I (Xuerian) wanted to use AceDB/AceConfig to present a more standard configuration dialog to users. I am, however, not satisfied with the conventions and limitations of it, so this is a attempt to provide both a more concise format (BetterOptions), and a more featureful intermediate options format (Finalize(...)) to support it.
5 |
6 |
7 | Methods:
8 | Finalize(module_data, option_table)
9 | - Compiles a AceOptionTable with extra features to a validating AceOptionTable by migrating extra data/metadata into option_metadata[current_option_table] so AceConfig doesn't brit a shick.
10 | > module_data expects a table where t.name == "ModuleName" and t.addon = AddonTable
11 | > option_table expects a "BetterOptions" option table
12 |
13 | BetterOptions.Compile(better_option_table)
14 | - Compiles BetterOptions tables to intermediate option tables which must be provided
15 | to either a supporting option system or Finalize() for use as a validating AceOptionsTable
16 | > better_option_table expects a "BetterOptions" option table
17 | Start by defining your module options here in addon:OnEnable below other module options with the call XLootOptions:RegisterOptions("ModuleName", table), inside a if XLoot:GetModule(ModuleName, true) block.
18 |
19 | XLootOptions:RegisterOptions(module_data, better_option_table)
20 | - Registers a BetterOptions table with XLootOptions
21 | > module_data and better_option_table follow BetterOptions.Compile and Finalize()
22 |
23 | XLootOptions:RegisterAceOptionTable("ModuleName", ace_option_table)
24 | - Registers a "normal" ace option table with no additional steps.
25 | - Must provide get and set methods at least in the root group(s), as default get and set rely on Finalize()
26 |
27 |
28 | Features/Finalize:
29 | - Fill missing localization from XLootOptions.L[module_data.name][key|key_desc]
30 | - Generate .values from {{ "key", "value" }, ...} .item tables and set them appropriately
31 | - Get/Set from db key/subkey instead of key via .subtable[, .subkey]
32 | - Propagate .defaults to child nodes
33 | - Default type "toggle"
34 | - "alpha" and "scale" types with automatic localization
35 | - .requires = key and .requires_inverse = key
36 |
37 | Features/BetterOptions:
38 | - Nested tables with implied ordering and table values:
39 | -- Basic structure: { "key", "type"[, arg1[, ...]] [, key = value[, ...]] }
40 | -- Examples:
41 | -- { "key", "toggle" }
42 | -- { "key", option = "blah" } -> { "key", "toggle", option = "blah" }
43 | -- { "key", "group", inline } -> key = { type = "group", inline = true }
44 | -- { "key", "execute", func } -> key = { type = "execute", func = func }
45 | -- { "key", "select", items }
46 | -- { "key", "color", hasAlpha (defaults to true) }
47 | -- { "key", "range", min, max, step, softMin, softMax, bigStep }
48 | -- Automatic types:
49 | -- Entry is just a "key": "toggle"
50 | -- "key"
51 | -- t[2] is unset: "toggle"
52 | -- { "key" }
53 | -- t[2] is a table: "select"
54 | -- { "key", {k, v} }
55 | Please note that inline and non-inline groups do not mix well for AceConfigDialog. -]=]
56 |
57 | -- Create module
58 | local addon, L = XLoot:NewModule("Options")
59 | addon.modules = {}
60 |
61 | -- Global
62 | _G.XLootOptions = addon
63 |
64 | -- Locals
65 | local print = print
66 |
67 | local function trigger(target, method, ...)
68 | local func = target[method]
69 | if type(func) == 'function' then
70 | func(target, ...)
71 | end
72 | end
73 |
74 | local function sizeof(t)
75 | local i = 0
76 | for k,v in pairs(t) do
77 | i=i+1
78 | end
79 | return i
80 | end
81 |
82 | -- Provide throttled updates
83 | local update_throttle, elapsed = CreateFrame("Frame"), 0
84 | update_throttle:Hide()
85 | update_throttle:SetScript("OnUpdate", function(self, delta)
86 | if elapsed > .1 then
87 | XLoot:ApplyOptions(true)
88 | elapsed = 0
89 | self:Hide()
90 | else
91 | elapsed = elapsed + delta
92 | end
93 | end)
94 |
95 | -------------------------------------------------------------------------------
96 | -- Module init
97 |
98 | function addon:OnEnable() -- Construct addon option tables here
99 |
100 | local option_metadata = {} -- Stores metadata for option entries outside of library-specific compiled option structure
101 | addon.option_metadata = option_metadata -- Until resulting AceOptionsTable can have .values upated, this is the only way to store a new .items table
102 |
103 | -------------------------------------------------------------------------------
104 | -- General config methods
105 |
106 | -- Find module options and requested key from AceConfigDialog info table
107 | -- returns:
108 | -- db -- Current settings table for option (May be a subtable)
109 | -- k -- Current settings key for option
110 | -- meta -- Config metatable for option
111 | -- full_db -- Full settings table for module
112 | local function path(info)
113 | local meta = option_metadata[info.option]
114 | local db = meta.module_data.addon.db.profile
115 | return meta.subtable and db[meta.subtable] or db, meta.subkey or info[#info], meta, db
116 | end
117 |
118 | -- Generic option getter
119 | local function get(info)
120 | local db, k, meta = path(info)
121 | if info.option.type == "color" then
122 | return unpack(db[k])
123 | elseif info.option.type == "select" and meta.items then
124 | for i,v in ipairs(meta.items) do
125 | if db[k] == v[1] then
126 | return i
127 | end
128 | end
129 | else
130 | return db[k]
131 | end
132 | end
133 |
134 | -- Generic option setter
135 | local function set(info, v, v2, v3, v4, ...)
136 | update_throttle:Show()
137 | local db, k, meta = path(info)
138 | if info.option.type == "color" then
139 | db[k][1] = v
140 | db[k][2] = v2
141 | db[k][3] = v3
142 | db[k][4] = v4
143 | elseif info.option.type == "select" and meta.items then
144 | db[k] = meta.items[v][1]
145 | else
146 | db[k] = v
147 | end
148 | if meta.module_data.OnChanged then
149 | meta.module_data.OnChanged(k, v, v2, v3, v4, ...)
150 | end
151 | if meta.must_reload_ui then
152 | print((L.message_reloadui_warning):format(meta.module_data.name, info.option.name))
153 | end
154 | end
155 |
156 | -- Anchor toggles
157 | local function set_anchor(...)
158 | set(...)
159 | local db, k, meta = path(...)
160 | trigger(meta.module_data.addon, "UpdateAnchors")
161 | end
162 |
163 | -- Select value generator
164 | local function values_from_items(info)
165 | local db, k, meta = path(info)
166 | local values = meta.values
167 | wipe(values)
168 | for i,v in ipairs(meta.items) do
169 | values[i] = v[2]
170 | end
171 | return values
172 | end
173 |
174 | -- Dependencies
175 | -- TODO: Recursive dependencies
176 | local function requires(info)
177 | local db, k, meta, full_db = path(info)
178 | return ((meta.requires and (not full_db[meta.requires]) or false)
179 | or (meta.requires_inverse and full_db[meta.requires_inverse] or false))
180 | end
181 |
182 | -------------------------------------------------------------------------------
183 | -- Streamlined options tables
184 |
185 | local BetterOptions = {}
186 | local table_remove = table.remove
187 |
188 | function BetterOptions.Compile(set)
189 | for i,v in ipairs(set) do
190 | local t, key = BetterOptions.any_type(v)
191 | t.order = i
192 | set[key] = t
193 | set[i] = nil
194 | end
195 | return set
196 | end
197 |
198 | local BetterOptionsTypes = {}
199 | BetterOptions.types = BetterOptionsTypes
200 | function BetterOptions.any_type(t)
201 | -- Simple
202 | if type(t) == 'string' then
203 | t = { t }
204 | end
205 |
206 | -- Shift required elements
207 | local key = table.remove(t, 1)
208 | t.type = table.remove(t, 1)
209 | -- Infer toggle by default
210 | if not t.type then
211 | t.type = "toggle"
212 | -- Infer select from table
213 | elseif type(t.type) == "table" then
214 | t.items, t.type = t.type, "select"
215 | -- Other positional arguments may be present
216 | table.insert(t, 1, "select")
217 | end
218 |
219 | -- Handle specific option types
220 | if BetterOptionsTypes[t.type] then
221 | BetterOptionsTypes[t.type](t)
222 | end
223 |
224 | -- Cleanup
225 | for i,v in ipairs(t) do
226 | t[i] = nil
227 | end
228 |
229 | return t, key
230 | end
231 |
232 | -- Many options are short enough that t[n] and t[n+1] can comfortably represent t.subtable and t.subkey
233 | function BetterOptions.infer_db_path(t, offset)
234 | offset = offset or 0
235 | t.subtable = t.subtable or t[1+offset]
236 | t.subkey = t.subkey or t[2+offset]
237 | end
238 |
239 |
240 | function BetterOptionsTypes.group(t)
241 | t.args = t.args or t[1]
242 | if t.inline == nil then
243 | t.inline = (t[2] == nil and true or t[2])
244 | end
245 |
246 | if t.args then
247 | BetterOptions.Compile(t.args)
248 | end
249 | end
250 |
251 | function BetterOptionsTypes.toggle(t)
252 | BetterOptions.infer_db_path(t)
253 | end
254 |
255 | function BetterOptionsTypes.select(t)
256 | t.items = t.items or t[1]
257 | BetterOptions.infer_db_path(t, 1)
258 | end
259 |
260 | function BetterOptionsTypes.alpha(t)
261 | t.min = 0.0
262 | t.max = 1.0
263 | t.step = 0.1
264 | BetterOptions.infer_db_path(t)
265 | end
266 |
267 | function BetterOptionsTypes.scale(t)
268 | t.min = 0.1
269 | t.max = 2.0
270 | t.step = 0.1
271 | BetterOptions.infer_db_path(t)
272 | end
273 |
274 | function BetterOptionsTypes.color(t)
275 | if t.hasAlpha == nil and t[1] ~= nil then
276 | t.hasAlpha = t[1]
277 | else
278 | t.hasAlpha = true
279 | end
280 | end
281 |
282 | function BetterOptionsTypes.range(t)
283 | t.min = t.min or t[1]
284 | t.max = t.max or t[2]
285 | t.step = t.step or t[3]
286 | t.softMin = t.softMin or t[4]
287 | t.softMax = t.softMax or t[5]
288 | t.bigStep = t.bigStep or t[6]
289 | end
290 |
291 | function BetterOptionsTypes.execute(t)
292 | t.func = t.func or t[1]
293 | end
294 |
295 | function BetterOptionsTypes.description(t)
296 | t.width = t.width or "full"
297 | t.fontSize = t.fontSize or "medium"
298 | end
299 |
300 | addon.BetterOptions = BetterOptions
301 | addon.BetterOptionsTypes = BetterOptionsTypes
302 |
303 | -------------------------------------------------------------------------------
304 | -- AceOptionsTable extension
305 |
306 | -- Flesh out AceOptionsTables for a given module
307 | -- Add features not directly supported
308 | local function Finalize(module_data, opts, key)
309 | local meta = option_metadata[opts]
310 | if not meta then
311 | meta = { module_data = module_data }
312 | option_metadata[opts] = meta
313 | end
314 | -- First call
315 | if not key then
316 | for k,v in pairs(opts) do
317 | Finalize(module_data, v, k)
318 | end
319 | -- Recursion
320 | else
321 | -- Automatically localized selects
322 | if opts.type == "alpha" or opts.type == "scale" then
323 | opts.name = opts.name or L[module_data.name][key] or L[key] or L[opts.type]
324 | opts.type = "range"
325 | end
326 |
327 | -- Fill in localized name/description
328 | opts.name = opts.name or L[module_data.name][key] or L[key] or key
329 | opts.desc = opts.desc or L[module_data.name][key.."_desc"]
330 |
331 | meta.subtable, meta.subkey = opts.subtable, opts.subkey
332 | opts.subtable, opts.subkey = nil, nil
333 |
334 | -- Dependencies
335 | if opts.requires or opts.requires_inverse then
336 | meta.requires, meta.requires_inverse = opts.requires, opts.requires_inverse
337 | opts.disabled = requires
338 | opts.requires, opts.requires_inverse = nil, nil
339 | end
340 |
341 | -- Reload UI warning
342 | if opts.must_reload_ui then
343 | meta.must_reload_ui = true
344 | opts.must_reload_ui = nil
345 | end
346 |
347 | -- Sorted select
348 | -- TODO: Set metatable on option table to update meta.items?
349 | if opts.type == "select" and opts.items then
350 | opts.values = values_from_items
351 | meta.values = {}
352 | meta.items = opts.items
353 | opts.items = nil
354 | end
355 |
356 | -- Traverse subgroup
357 | if opts.args then
358 | -- Apply subgroup defaults
359 | if opts.defaults then
360 | for argk, argv in pairs(opts.args) do
361 | for defk, defv in pairs(opts.defaults) do
362 | if argv[defk] == nil then
363 | argv[defk] = defv
364 | end
365 | end
366 | end
367 | opts.defaults = nil
368 | end
369 | -- Finalize subgroups
370 | for k,v in pairs(opts.args) do
371 | Finalize(module_data, v, k)
372 | end
373 |
374 | -- Default type "toggle"
375 | elseif not opts.type then
376 | opts.type = "toggle"
377 | end
378 | end
379 | return opts
380 | end
381 |
382 | -------------------------------------------------------------------------------
383 | -- Module config registration
384 |
385 | -- Global config header
386 | self.config = {
387 | type = "group",
388 | name = "XLoot",
389 | get = get,
390 | set = set,
391 | childGroups = "tab"
392 | }
393 |
394 | local function OnCoreChanged(k, v)
395 | if k == 'skin' then
396 | XLoot:ApplyOptions(true)
397 | end
398 | end
399 |
400 | local skins = {}
401 | local options = Finalize({ name = "Core", addon = XLoot, OnChanged = OnCoreChanged }, BetterOptions.Compile({
402 | { "details", "description" },
403 | { "skin", "select", values = function()
404 | wipe(skins)
405 | for k,v in pairs(XLoot.Skin.skins) do
406 | skins[k] = v.name
407 | end
408 | return skins
409 | end},
410 | { "skin_anchors", "toggle" },
411 | -- { "module_header", "header" },
412 | }))
413 | self.config.args = options
414 |
415 | function addon:RegisterAceOptionTable(module_name, option_table)
416 | -- Insert into options
417 | options[module_name] = {
418 | type = "group",
419 | name = L[module_name].panel_title,
420 | desc = L[module_name].panel_desc,
421 | args = option_table,
422 | order = sizeof(options) + 1,
423 | inline = false
424 | }
425 | end
426 |
427 | function addon:RegisterOptions(module_data, option_table)
428 | -- Have to finalize here because Finalize needs to know what module we're in
429 | -- There's probably a better way to do this.
430 | addon.modules[module_data.name] = module_data
431 | Finalize(module_data, BetterOptions.Compile(option_table))
432 | self:RegisterAceOptionTable(module_data.name, option_table)
433 | end
434 |
435 | -------------------------------------------------------------------------------
436 | -- Generic select values
437 |
438 | local item_qualities = {}
439 | do
440 | for i=0, #ITEM_QUALITY_COLORS do
441 | local hex = select(4, C_Item.GetItemQualityColor(i))
442 | table.insert(item_qualities, { i, ("|c%s%s"):format(hex, _G["ITEM_QUALITY"..tostring(i).."_DESC"]) })
443 | end
444 | end
445 |
446 | local directions = {
447 | { "up", L.up },
448 | { "down", L.down }
449 | }
450 |
451 | local leftright = {
452 | { "left", L.left },
453 | { "right", L.right },
454 | }
455 |
456 | -- Shared Media
457 | local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
458 |
459 | local fonts
460 | if LSM then
461 | fonts = {}
462 | for name, ttf in pairs(LSM:HashTable("font")) do
463 | table.insert(fonts, {ttf, name})
464 | end
465 | else
466 | fonts = {
467 | { STANDARD_TEXT_FONT, "Friz Quadrata TT" },
468 | { [[Fonts\MORPHEUS.ttf]], "Morpheus" },
469 | { [[Fonts\ARIALN.ttf]], "Arial Narrow" },
470 | { [[Fonts\SKURRI.ttf]], "Skurri" },
471 | }
472 | end
473 |
474 | local font_flag = {
475 | { "", "NONE" },
476 | { "OUTLINE", "OUTLINE" },
477 | { "THICKOUTLINE", "THICKOUTLINE" },
478 | { "MONOCHROME", "MONOCHROME" }
479 | }
480 |
481 | -------------------------------------------------------------------------------
482 | -- Module configs
483 |
484 | -- XLoot Frame
485 | if XLoot:GetModule("Frame", true) then
486 | local when_group = {
487 | { "never", L.when_never },
488 | { "solo", L.when_solo },
489 | { "always", L.when_always },
490 | { "group", L.when_group },
491 | { "party", L.when_party },
492 | { "raid", L.when_raid }
493 | }
494 |
495 | addon:RegisterOptions({ name = "Frame", addon = XLootFrame.addon }, {
496 | { "frame_options", "group", {
497 | { "frame_width_automatic", width = "double" },
498 | { "old_close_button" },
499 | { "frame_width", "range", 75, 300, 5, requires_inverse = "frame_width_automatic" },
500 | { "frame_scale", "scale" },
501 | { "frame_alpha", "alpha" },
502 | { "frame_snap" },
503 | { "frame_snap_offset_x", "range", -2000, 2000, 1, -250, 250, 10, requires = "frame_snap" },
504 | { "frame_snap_offset_y", "range", -2000, 2000, 1, -250, 250, 10, requires = "frame_snap" },
505 | { "frame_draggable" },
506 | { "frame_grow_upwards" },
507 | { "show_slot_errors" },
508 | }},
509 | { "slot_options", "group", {
510 | { "loot_texts_info", width = "double" },
511 | { "loot_texts_bind" },
512 | { "loot_highlight", width = "double", },
513 | { "loot_collapse" },
514 | { "loot_texts_lock", width = "double" },
515 | { "loot_buttons_auto" },
516 | { "loot_alpha", "alpha" },
517 | { "loot_icon_size", "range", 16, 64, 1, name = L.icon_size },
518 | { "loot_row_height", "range", 14, 64, 1 },
519 | { "loot_padding", "header", name = L.padding },
520 | { "loot_padding_top", "range", 0, 25, 1, name = L.top },
521 | { "loot_padding_left", "range", 0, 25, 1, name = L.left },
522 | { "loot_padding_right", "range", 0, 25, 1, name = L.right },
523 | { "loot_padding_bottom", "range", 0, 25, 1, name = L.bottom },
524 | }},
525 | { "link_button", "group", {
526 | { "linkall_show", when_group },
527 | { "linkall_threshold", item_qualities },
528 | { "linkall_channel", {
529 | { "SAY", CHAT_MSG_SAY },
530 | { "PARTY", CHAT_MSG_PARTY },
531 | { "GUILD", CHAT_MSG_GUILD },
532 | { "OFFICER", CHAT_MSG_OFFICER },
533 | { "RAID", CHAT_MSG_RAID },
534 | { "RAID_WARNING", RAID_WARNING },
535 | { 'INSTANCE_CHAT', INSTANCE_CHAT},
536 | }},
537 | { "linkall_channel_secondary", {
538 | { "SAY", CHAT_MSG_SAY },
539 | { "PARTY", CHAT_MSG_PARTY },
540 | { "GUILD", CHAT_MSG_GUILD },
541 | { "OFFICER", CHAT_MSG_OFFICER },
542 | { "RAID", CHAT_MSG_RAID },
543 | { "RAID_WARNING", RAID_WARNING },
544 | { 'INSTANCE_CHAT', INSTANCE_CHAT},
545 | { 'NONE', NONE },
546 | }},
547 | { "linkall_first_only" }
548 | }},
549 | { "autolooting", "group", {
550 | { "autolooting_text", "description" },
551 | { "autoloot_currency", when_group, "autoloots", "currency" },
552 | { "autoloot_quest", when_group, "autoloots", "quest" },
553 | { "autoloot_tradegoods", when_group, "autoloots", "tradegoods" },
554 | { "autoloot_all", when_group, "autoloots", "all" },
555 | { "autolooting_list", "description" },
556 | { "autoloot_list", when_group, "autoloots", "list" },
557 | { "autoloot_item_list", "input", width = "double" },
558 | { "autolooting_details", "description" },
559 | }},
560 | { "font", "group", {
561 | { "font", fonts },
562 | { "font_flag", font_flag },
563 | { "font_sizes", "header" },
564 | { "font_size_loot", "range", 4, 26, 1 },
565 | { "font_size_info", "range", 4, 26, 1 },
566 | { "font_size_quantity", "range", 4, 26, 1 },
567 | { "font_size_bottombuttons", "range", 4, 26, 1 },
568 | { "font_size_button_auto", "range", 4, 26, 1, requires = "loot_buttons_auto", name = L.Frame.loot_buttons_auto },
569 | }},
570 | { "colors", "group", {
571 | { "quality_color_frame", width = "full" },
572 | { "quality_color_slot", width = "full" },
573 | { "frame_color_border", "color", width = "double", requires_inverse = "quality_color_frame" },
574 | { "loot_color_border", "color", requires_inverse = "quality_color_slot" },
575 | { "frame_color_backdrop", "color", width = "double" },
576 | { "loot_color_backdrop", "color" },
577 | { "frame_color_gradient", "color", width = "double" },
578 | { "loot_color_gradient", "color" },
579 | { "loot_color_info", "color", width = "double", requires = "loot_texts_info" },
580 | { "loot_color_button_auto", "color", requires = "loot_buttons_auto", name = L.Frame.loot_buttons_auto},
581 | }}
582 | })
583 | end
584 |
585 | -- XLoot Group
586 | if XLoot:GetModule("Group", true) then
587 | addon:RegisterOptions({ name = "Group", addon = XLootGroup }, {
588 | { "anchors", "group", {
589 | { "roll_anchor_visible", "toggle", "roll_anchor", "visible", set = set_anchor },
590 | }},
591 | { "rolls", "group", {
592 | { "roll_direction", directions, "roll_anchor", "direction" , name = L.growth_direction },
593 | { "roll_scale", "scale", "roll_anchor", "scale" },
594 | { "roll_width", "range", 150, 700, 1, 150, 400, 10, name = L.width },
595 | { "roll_spacing", "range", -25, 25, 1, name = L.spacing, subtable = "roll_anchor", subkey = "spacing" },
596 | { "roll_offset", "range", -25, 25, 1, name = L.offset, subtable = "roll_anchor", subkey = "offset" },
597 | { "roll_button_size", "range", 16, 48, 1 },
598 |
599 | }},
600 | { "extra_info", "group", {
601 | { "equip_prefix" },
602 | { "prefix_equippable", "input", requires = "equip_prefix" },
603 | { "prefix_upgrade", "input" },
604 | { "show_time_remaining", name = L.Group.text_time },
605 | { "show_undecided" },
606 | { "role_icon" },
607 | { "win_icon" },
608 | { "text_ilvl" },
609 | }},
610 | { "font", "group", {
611 | { "font", fonts },
612 | { "font_flag", font_flag },
613 | }},
614 | { "roll_tracking", "group", {
615 | { "track_all", width = "double" },
616 | { "track_player_roll", requires_inverse = "track_all" },
617 | { "track_by_threshold", requires_inverse = "track_all", width = "double" },
618 | { "track_threshold", item_qualities, requires = "track_by_threshold", name = L.minimum_quality },
619 | { "expiration", "header" },
620 | { "expire_won", "range", 5, 30, 1 },
621 | { "expire_lost", "range", 5, 30, 1 },
622 | }},
623 | })
624 | end
625 |
626 | -- XLoot Monitor
627 | if XLoot:GetModule("Monitor", true) then
628 | addon:RegisterOptions({ name = "Monitor", addon = XLootMonitor.addon }, {
629 | { "testing", "group", {
630 | { "test_settings", "execute", func = XLootMonitor.TestSettings }
631 | }},
632 | { "anchor", "group", {
633 | { "visible", set = set_anchor, width = "double" },
634 | { "scale", "scale" },
635 | { "direction", directions, name = L.growth_direction },
636 | { "alignment", leftright, name = L.alignment },
637 | { "offsets", "header", name = '' },
638 | { "spacing", "range", -25, 25, 1, name = L.spacing, subtable = "anchor" },
639 | { "offset", "range", -25, 25, 1, name = L.offset, subtable = "anchor" },
640 | }, defaults = { subtable = "anchor" } },
641 | { "thresholds", "group", {
642 | { "threshold_own", item_qualities, name = L.items_own },
643 | { "threshold_other", item_qualities, name = L.items_others },
644 | }},
645 | { "filters", "group", {
646 | { "show_coin", name = MONEY },
647 | { "show_currency", name = CURRENCY },
648 | { "show_crafted" },
649 | }, name = FILTERS },
650 | { "fading", "group", {
651 | { "fade_own", "range", 1, 30, 1, name = L.items_own },
652 | { "fade_other", "range", 1, 30, 1, name = L.items_others },
653 | }},
654 | { "details", "group", {
655 | { "show_totals", width = "double" },
656 | { "use_altoholic", requires = "show_totals" },
657 | { "totals_delay", "range", 0.1, 1.0, 0.1 },
658 | { "name_width", "range", 25, 200, 5 },
659 | { "show_ilvl", name = L.Group.text_ilvl },
660 | }},
661 | { "font", "group", {
662 | { "font", fonts },
663 | { "font_flag", font_flag },
664 | { "font_sizes", "header" },
665 | { "font_size_loot", "range", 4, 26, 1 },
666 | { "font_size_quantity", "range", 4, 26, 1 },
667 | { "font_size_ilvl", "range", 4, 26, 1 },
668 | }},
669 | { "colors", "group", {
670 | { "gradients", must_reload_ui = true },
671 | }, name = L.Frame.colors },
672 | })
673 | end
674 |
675 | -- XLoot Master
676 | if XLoot:GetModule("Master", true) then
677 | -- Item quality dropdown generator
678 | local item_qualities = {}
679 | do
680 | for i, v in ipairs({ "ITEM_QUALITY2_DESC", "ITEM_QUALITY3_DESC", "ITEM_QUALITY4_DESC", "CANCEL" }) do -- we only care for the qualities available as ML filters
681 | local quality = tonumber(strmatch(v,"%d+"))
682 | if quality then
683 | local hex = select(4, C_Item.GetItemQualityColor(quality))
684 | item_qualities[i] = { quality, ('|c%s%s'):format(hex, _G[v]) }
685 | end
686 | end
687 | end
688 | table.insert(item_qualities, 1, { -1, ALWAYS })
689 | table.insert(item_qualities, { 10, NEVER })
690 | local channels = {
691 | { 'AUTO', L.desc_channel_auto },
692 | { 'SAY', CHAT_MSG_SAY },
693 | { 'PARTY', CHAT_MSG_PARTY },
694 | { 'RAID', CHAT_MSG_RAID },
695 | { 'INSTANCE_CHAT', INSTANCE_CHAT},
696 | { 'RAID_WARNING', RAID_WARNING },
697 | { 'OFFICER', CHAT_MSG_OFFICER },
698 | { 'NONE', NONE },
699 | }
700 | addon:RegisterOptions({ name = "Master", addon = XLootMaster }, {
701 | { "confirm_qualitythreshold", item_qualities },
702 | { "specialrecipients", "group", {
703 | { "menu_self" },
704 | { "menu_disenchant" },
705 | { "menu_disenchanters", "input", requires="menu_disenchant" },
706 | { "menu_bank" },
707 | { "menu_bankers", "input", requires="menu_bank" },
708 | }},
709 | { "raidroll", "group", {
710 | { "menu_roll" },
711 | }},
712 | { "awardannounce", "group", {
713 | { "award_qualitythreshold", item_qualities },
714 | { "award_channel", channels },
715 | { "award_channel_secondary", channels },
716 | { "award_guildannounce" },
717 | { "award_special" },
718 | }},
719 | })
720 | end
721 |
722 | --[=[ -- Generate reset staticpopup
723 | if not StaticPopupDialogs['XLOOT_RESETPROFILE'] then
724 | StaticPopupDialogs['XLOOT_RESETPROFILE'] = {
725 | preferredIndex = 3,
726 | text = L.confirm_reset_profile,
727 | button1 = ACCEPT,
728 | button2 = CANCEL,
729 | OnAccept = function() addon:ResetProfile() end,
730 | exclusive = true,
731 | timeout = 0,
732 | whileDead = true,
733 | hideOnEscape = true,
734 | }
735 | end--]=]
736 |
737 | if Settings then
738 | addon:Init()
739 | end
740 | end
741 |
742 | function addon:OnInitialize()
743 |
744 | end
745 |
746 | -------------------------------------------------------------------------------
747 | -- Panel methods
748 |
749 | local function PanelDefault(self)
750 | -- StaticPopup_Show("XLOOT_RESETPROFILE")
751 | addon:ResetProfile()
752 | end
753 |
754 | local function PanelOkay(self)
755 | end
756 |
757 | local function PanelCancel(self)
758 | -- Restore old options?
759 | end
760 |
761 | function addon:ResetProfile()
762 | XLoot.db:ResetProfile()
763 | LibStub("AceConfigRegistry-3.0"):NotifyChange("XLoot")
764 | end
765 |
766 | local init = false
767 | local AceConfigDialog, AceConfigRegistry = LibStub("AceConfigDialog-3.0"), LibStub("AceConfigRegistry-3.0")
768 |
769 | function addon:Init()
770 | -- One-time init
771 | if not init then
772 | init = true
773 | if not Settings then
774 | -- Remove bootstrap
775 | for i,frame in ipairs(INTERFACEOPTIONS_ADDONCATEGORIES) do
776 | if frame.name == "XLoot" then
777 | table.remove(INTERFACEOPTIONS_ADDONCATEGORIES, i)
778 | end
779 | end
780 | end
781 | -- Generate new panel
782 | AceConfigRegistry:RegisterOptionsTable("XLoot", self.config)
783 | local panel = AceConfigDialog:AddToBlizOptions("XLoot")
784 | XLoot.option_panel = panel
785 | panel.default = PanelDefault
786 | -- panel.okay = PanelOkay
787 | -- panel.cancel = PanelCancel
788 |
789 | local _OnShow = panel:GetScript("OnShow")
790 | local _OnHide = panel:GetScript("OnHide")
791 | panel:SetScript("OnShow", function(...)
792 | _OnShow(...)
793 | for name, module_data in pairs(self.modules) do
794 | trigger(module_data.addon, "OnOptionsShow", panel)
795 | end
796 | end)
797 | panel:SetScript("OnHide", function(...)
798 | _OnHide(...)
799 | for name, module_data in pairs(self.modules) do
800 | trigger(module_data.addon, "OnOptionsHide", panel)
801 | end
802 | end)
803 |
804 | -- Create profile panel
805 | AceConfigRegistry:RegisterOptionsTable("XLootProfile", LibStub("AceDBOptions-3.0"):GetOptionsTable(XLoot.db))
806 | XLoot.profile_panel = AceConfigDialog:AddToBlizOptions("XLootProfile", L.profile, "XLoot")
807 | XLoot.profile_panel.default = PanelDefault
808 | -- Force list to expand
809 | if not Settings then
810 | InterfaceAddOnsList_Update()
811 | end
812 | end
813 | end
814 |
815 | function addon:OpenPanel(module)
816 | addon:Init()
817 | -- Open panel
818 | if Settings then
819 | Settings.OpenToCategory("XLoot")
820 | else
821 | InterfaceOptionsFrame_OpenToCategory(XLoot.option_panel)
822 | end
823 | end
824 |
825 | --@do-not-package@
826 | -- function print(...)
827 | -- _G.UIParentLoadAddOn("Blizzard_DebugTools");
828 | -- _G.DevTools_Dump((...));
829 | -- _G.DevTools_Dump(select(2, ...));
830 | -- end
831 | local AC = LibStub('AceConsole-2.0', true)
832 |
833 | if AC then print = function(...) AC:PrintLiteral(...) end end
834 | --@end-do-not-package@
835 |
--------------------------------------------------------------------------------
/Modules/Group/Group.lua:
--------------------------------------------------------------------------------
1 | -- Create module
2 | local addon, L = XLoot:NewModule("Group")
3 | -- Prepare global
4 | XLootGroup = addon
5 | -- Grab locals
6 | local opt, anchor, alert_anchor, mouse_focus, Skinner
7 | local rolls = {}
8 | local RAID_CLASS_COLORS = CUSTOM_CLASS_COLORS or _G.RAID_CLASS_COLORS
9 | local GetLootRollItemInfo, GetLootRollItemLink, GetLootRollTimeLeft, RollOnLoot, UnitGroupRolesAssigned, print, string_format
10 | = GetLootRollItemInfo, GetLootRollItemLink, GetLootRollTimeLeft, RollOnLoot, UnitGroupRolesAssigned, print, string.format
11 | local HistoryGetItem, HistoryGetPlayerInfo, HistoryGetNumItems
12 | = C_LootHistory.GetItem, C_LootHistory.GetPlayerInfo, C_LootHistory.GetNumItems
13 | local CanEquipItem, IsItemUpgrade, FancyPlayerName = XLoot.CanEquipItem, XLoot.IsItemUpgrade, XLoot.FancyPlayerName
14 | local RollFramePrototype
15 |
16 | local BUILD_NUMBER = select(4, GetBuildInfo())
17 | local BUILD_HAS_DISENCHANT = BUILD_NUMBER >= 30300
18 | local BUILD_HAS_TRANSMOG_GREED = BUILD_NUMBER >= 49407
19 |
20 | local GetItemInfo = C_Item and C_Item.GetItemInfo or GetItemInfo
21 |
22 | -------------------------------------------------------------------------------
23 | -- Settings
24 |
25 | local defaults = {
26 | profile = {
27 | role_icon = true,
28 | win_icon = false,
29 | show_decided = true,
30 | show_undecided = false,
31 | show_time_remaining = false,
32 | text_ilvl = false,
33 |
34 | equip_prefix = true,
35 | prefix_equippable = "*",
36 | prefix_upgrade = "+",
37 |
38 | hook_alert = false,
39 | alert_skin = true,
40 | alert_alpha = 1,
41 | alert_scale = 1,
42 | alert_offset = 4,
43 | alert_background = false,
44 | alert_icon_frame = false,
45 |
46 | hook_bonus = false,
47 | bonus_skin = true,
48 |
49 | roll_button_size = 28,
50 | roll_width = 325,
51 |
52 | font = STANDARD_TEXT_FONT,
53 | font_flag = "OUTLINE",
54 |
55 | roll_anchor = {
56 | direction = 'up',
57 | spacing = 2,
58 | offset = 0,
59 | visible = true,
60 | draggable = true,
61 | scale = 1.0,
62 | x = UIParent:GetWidth() * .75,
63 | y = UIParent:GetHeight() * .4
64 | },
65 |
66 | alert_anchor = {
67 | visible = true,
68 | direction = 'up',
69 | draggable = true,
70 | scale = 1.0,
71 | x = AlertFrame:GetLeft(),
72 | y = AlertFrame:GetTop()
73 | },
74 |
75 | track_all = false,
76 | track_player_roll = false,
77 | track_by_threshold = true,
78 | track_threshold = 3,
79 |
80 | expire_won = 20,
81 | expire_lost = 10,
82 | shown_hook_warning = false
83 | }
84 | }
85 |
86 | opt = defaults.profile
87 |
88 | -------------------------------------------------------------------------------
89 | -- Module init
90 |
91 | local eframe = CreateFrame("Frame")
92 | function addon:OnInitialize()
93 | self:InitializeModule(defaults, eframe)
94 | opt = self.db.profile
95 | XLootGroup.opt = opt
96 | -- Extra slash command
97 | XLoot:SetSlashCommand("xlg", self.SlashHandler)
98 | end
99 |
100 | function addon:OnEnable()
101 | if BUILD_NUMBER >= 100000 then
102 | print("XLoot Group does not yet work on this version and will not be loaded")
103 | return
104 | end
105 | -- Register events
106 | eframe:RegisterEvent('START_LOOT_ROLL')
107 | eframe:RegisterEvent('MODIFIER_STATE_CHANGED')
108 |
109 | -- if BUILD_HAS_TRANSMOG_GREED or C_Item then
110 | -- eframe:RegisterEvent('LOOT_HISTORY_UPDATE_DROP')
111 | -- else
112 | eframe:RegisterEvent('LOOT_HISTORY_ROLL_CHANGED')
113 | eframe:RegisterEvent('LOOT_HISTORY_ROLL_COMPLETE')
114 | eframe:RegisterEvent('LOOT_ROLLS_COMPLETE')
115 | -- end
116 |
117 | -- Disable default frame
118 | UIParent:UnregisterEvent("START_LOOT_ROLL")
119 | UIParent:UnregisterEvent("CANCEL_LOOT_ROLL")
120 |
121 | -- Set up skins
122 | Skinner = {}
123 | XLoot:MakeSkinner(Skinner, {
124 | anchor = { r = .4, g = .4, b = .4, a = .6, gradient = false },
125 | anchor_pretty = { r = .6, g = .6, b = .6, a = .8 },
126 | row = { gradient = false },
127 | item = { backdrop = false },
128 | alert = { gradient = true },
129 | alert_item = { gradient = true, backdrop = false },
130 | bonus = { }
131 | }, 'row')
132 |
133 | -- Create Roll anchor
134 | anchor = XLoot.Stack:CreateStaticStack(function() return RollFramePrototype:New() end, L.anchor, opt.roll_anchor)
135 | anchor:SetFrameLevel(7)
136 | anchor:Scale(opt.roll_anchor.scale)
137 | addon.anchor = anchor
138 |
139 | -- Create alert anchor
140 | alert_anchor = XLoot.Stack:CreateAnchor(L.alert_anchor, opt.alert_anchor)
141 | alert_anchor:SetFrameLevel(7)
142 | addon.alert_anchor = alert_anchor
143 | -- DISABLED-PATCH: LEGION PRE-PATCH
144 | alert_anchor.Show = alert_anchor.Hide
145 | alert_anchor:Hide()
146 |
147 | -- Skin anchor
148 | Skinner:Skin(anchor, XLoot.opt.skin_anchors and 'anchor_pretty' or 'anchor')
149 | Skinner:Skin(alert_anchor, XLoot.opt.skin_anchors and 'anchor_pretty' or 'anchor')
150 |
151 | -- Row fader
152 | local fader = CreateFrame('Frame')
153 | local timer = 0
154 | fader:SetScript('OnUpdate', function(self, elapsed)
155 | if timer < 1 then
156 | timer = timer + elapsed
157 | else
158 | timer = 0
159 | local time = GetTime()
160 | -- Extend expiration for mouseovered frames
161 | if mouse_focus and mouse_focus.aexpire and (mouse_focus.aexpire - time) < 5 then
162 | mouse_focus.aexpire = time + 5
163 | end
164 | for i=#anchor.expiring,1,-1 do
165 | local frame = anchor.expiring[i]
166 | if frame.aexpire and time > frame.aexpire then
167 | anchor:Pop(frame)
168 | table.remove(anchor.expiring, i)
169 | end
170 | end
171 | if not next(anchor.expiring) then
172 | self:Hide()
173 | end
174 | end
175 | end)
176 | anchor.expiring = {}
177 | function anchor:Expire(frame, time)
178 | fader:Show()
179 | table.insert(self.expiring, frame)
180 | frame.aexpire = GetTime() + time
181 | end
182 |
183 | -- Find and show active rolls
184 | if IsInGroup() and (GetLootMethod() == 'group' or GetLootMethod() == 'needbeforegreed') then
185 | for i=1,300 do
186 | local time = GetLootRollTimeLeft(i)
187 | if time > 0 and time < 300000 then
188 | self:START_LOOT_ROLL(i, time, true)
189 | end
190 | end
191 | end
192 | end
193 |
194 | -------------------------------------------------------------------------------
195 | -- Frame helpers
196 |
197 | -------------------------------------------------------------------------------
198 | -- Event handlers
199 |
200 | addon.bars = {}
201 | local type_strings = {
202 | need = NEED,
203 | greed = GREED,
204 | disenchant = ROLL_DISENCHANT,
205 | pass = PASS
206 | }
207 | local rtypes = { [0] = 'pass', 'need', 'greed', 'disenchant' } -- Tekkub. Writing smaller addons than me since ever.
208 |
209 | function addon:START_LOOT_ROLL(id, length, uid, ongoing)
210 | local icon, name, count, quality, bop, need, greed, de, reason_need, reason_greed, reason_de, de_skill = GetLootRollItemInfo(id)
211 | -- LootFrame.lua includes this sanity check
212 | if name == nil then
213 | print('XLoot Group: Ignoring START_LOOT_ROLL with no name')
214 | return
215 | end
216 | local link = GetLootRollItemLink(id)
217 | local r, g, b = C_Item.GetItemQualityColor(quality)
218 |
219 | local start = length
220 | if ongoing then
221 | if quality == 2 then
222 | length = 60000
223 | else
224 | length = 180000
225 | end
226 | end
227 | length, start = length/1000, start/1000
228 |
229 | local frame = anchor:Push()
230 | rolls[id] = frame
231 |
232 | frame.need:Show()
233 | frame.greed:Show()
234 | if BUILD_HAS_DISENCHANT then
235 | frame.disenchant:Show()
236 | end
237 | frame.pass:Show()
238 | frame.text_status:Hide()
239 | frame.text_status:SetText()
240 | frame.text_time:SetShown(opt.show_time_remaining)
241 |
242 | frame.need.texture_special:SetTexture()
243 | frame.greed.texture_special:SetTexture()
244 |
245 | if opt.equip_prefix then
246 | local canequip, isupgrade = CanEquipItem(link), IsItemUpgrade(link)
247 | if canequip or isupgrade then
248 | name = string_format("|cFF%s%s|r%s", isupgrade and "44FF22" or "BBBBBB", isupgrade and opt.prefix_upgrade or opt.prefix_equippable, name)
249 | if isupgrade then
250 | (need and frame.need or frame.greed).texture_special:SetTexture([[Interface\AddOns\Pawn\Textures\UpgradeArrow.tga]])
251 | end
252 | end
253 | end
254 | frame.need:Toggle(need)
255 | frame.greed:Toggle(greed)
256 | frame.disenchant:Toggle(de)
257 |
258 | frame.need:SetText()
259 | frame.greed:SetText()
260 | frame.pass:SetText()
261 | frame.disenchant:SetText()
262 |
263 | frame.need.reason = reason_need ~= 0 and reason_need or nil
264 | frame.greed.reason = reason_greed ~= 0 and reason_greed or nil
265 | frame.disenchant.reason = reason_de ~= 0 and reason_de or nil
266 | frame.disenchant.skill = de_skill ~= 0 and de_skill or nil
267 |
268 | local bar = frame.bar
269 | bar.length = length
270 | bar.expires = GetTime() + start
271 |
272 | frame.link = link
273 | frame.rollid = id
274 | frame.rollended = nil
275 | frame.quality = quality
276 | frame.expires = bar.expires
277 | frame.over = nil
278 | frame.have_rolled = false
279 | frame.lead_type = 'pass'
280 |
281 | frame.text_bind:SetText(bop and '|cffff4422BoP' or '')
282 | frame.text_loot:SetText(name)
283 | local ilvl = GetDetailedItemLevelInfo(link)
284 | frame.text_ilvl:SetText(ilvl > 1 and ilvl or nil)
285 |
286 | frame.text_loot:SetVertexColor(r, g, b)
287 | frame.overlay:SetBorderColor(r, g, b)
288 | frame.icon_frame:SetBorderColor(r, g, b)
289 | bar:SetStatusBarColor(r, g, b, .7)
290 | frame.icon:SetTexture(icon)
291 |
292 | bar:SetMinMaxValues(0, length)
293 | bar:SetValue(start)
294 |
295 |
296 | return frame
297 | end
298 |
299 | local tidx = { [0] = 1, [3] = 2, [2] = 2, [1] = 3 }
300 | function addon:LOOT_HISTORY_ROLL_COMPLETE()
301 | -- Locate history item
302 | local hid, frame, rollid, players, done, _ = 1, nil, nil, nil, nil, nil
303 | while true do
304 | rollid, _, players, done = HistoryGetItem(hid)
305 | if not rollid or (rolls[rollid] and rolls[rollid].over) then
306 | return
307 | elseif done and rolls[rollid] then
308 | frame = rolls[rollid]
309 | break
310 | end
311 | hid = hid+1
312 | end
313 |
314 | -- Active frame found
315 | frame.over = true
316 | local top_type, top_roll, top_pid, top_is_me = 0, 0, nil, nil
317 | for j=1, players do
318 | local name, class, rtype, roll, is_winner, is_me = HistoryGetPlayerInfo(hid, j)
319 | -- roll = roll and roll or true
320 | if is_winner then
321 | top_pid = j
322 | top_is_me = is_me
323 | break
324 | elseif rtype ~= 0 and tidx[rtype] >= tidx[top_type] and (not roll or roll > top_roll) then
325 | top_type = rtype
326 | top_roll = roll
327 | top_pid = j
328 | end
329 | end
330 |
331 | -- Winner or lead
332 | if top_pid then
333 | local name, class = HistoryGetPlayerInfo(hid, top_pid)
334 | local player, r, g, b = FancyPlayerName(name, class, opt)
335 | if opt.win_icon then
336 | if top_type == 'need' then
337 | player = [[|TInterface\Buttons\UI-GroupLoot-Dice-Up:16:16:-1:-1|t]]..player
338 | elseif top_type == 'greed' then
339 | player = [[|TInterface\Buttons\UI-GroupLoot-Coin-Up:16:16:-1:-2|t]]..player
340 | elseif top_type == 'disenchant' then
341 | player = [[|TInterface\Buttons\UI-GroupLoot-DE-Up:16:16:-1:-1|t]]..player
342 | end
343 | end
344 | frame.text_status:SetText(player)
345 | frame.text_status:SetTextColor(r, g, b)
346 | frame.bar.expires = GetTime()
347 | anchor:Expire(frame, top_is_me and opt.expire_won or opt.expire_lost)
348 | else
349 | -- No winner/lead
350 | frame.text_status:SetText(string_format('%s: %s', PASS, ALL))
351 | frame.text_status:SetTextColor(.7, .7, .7)
352 | frame.bar.expires = GetTime()
353 | anchor:Expire(frame, opt.expire_lost)
354 | end
355 | -- Refresh tooltip
356 | if frame and mouse_focus == frame then
357 | frame:OnEnter()
358 | end
359 | end
360 | addon.LOOT_ROLLS_COMPLETE = addon.LOOT_HISTORY_ROLL_COMPLETE
361 |
362 | local rweights = { need = 3, greed = 2, disenchant = 2, pass = 1 }
363 | function addon:LOOT_HISTORY_ROLL_CHANGED(hid, pid)
364 | -- Acquire roll information and frame
365 | local rollid, link, players, done = HistoryGetItem(hid)
366 | local frame = rolls[rollid]
367 | if not frame or frame.rollid ~= rollid or not frame:IsShown() then
368 | return nil
369 | end
370 |
371 | -- Acquire player information
372 | local name, class, rtypeid, roll, winner, is_me = HistoryGetPlayerInfo(hid, pid)
373 | local rtype = rtypes[rtypeid]
374 |
375 | -- Transition or expire frame on player roll
376 | if is_me then
377 | if opt.track_all
378 | or (opt.track_player_roll and rtype ~= 'pass')
379 | or (opt.track_by_threshold and frame.quality >= opt.track_threshold) then
380 | frame.need:Hide()
381 | frame.greed:Hide()
382 | frame.disenchant:Hide()
383 | frame.pass:Hide()
384 | frame.text_status:Show()
385 | frame.have_rolled = true
386 | else
387 | anchor:Pop(frame)
388 | return
389 | end
390 | end
391 |
392 | -- Update post-player-roll status text
393 | if frame.have_rolled then
394 | local rtype = rtype == 'disenchant' and 'greed' or rtype
395 | -- Roll of leading type or higher
396 | if rweights[rtype] >= rweights[frame.lead_type] then
397 | frame.lead_type = rtype
398 | local bracket, mtype = 0, nil
399 | for i=1, players do
400 | local _, _, ptype, _, _, is_me = HistoryGetPlayerInfo(hid, i)
401 | local ptype = rtypes[ptype == 3 and 2 or ptype]
402 | if ptype == rtype then
403 | bracket = bracket + 1
404 | end
405 | if is_me then
406 | mtype = ptype
407 | end
408 | end
409 |
410 | local r, g, b = .7, .7, .7
411 | if mtype == rtype then
412 | r, g, b = .2, 1, .1
413 | elseif mtype and mtype ~= 0 then
414 | r, g, b = 1, .2, .1
415 | end
416 | frame.text_status:SetText(string_format('%s: %s', type_strings[rtype], bracket))
417 | frame.text_status:SetTextColor(r, g, b)
418 | end
419 |
420 | -- Update roll button counters
421 | else
422 | local bracket = 0
423 | for i=1, players do
424 | local _, _, thistype = HistoryGetPlayerInfo(hid, i)
425 | if thistype == rtypeid then
426 | bracket = bracket + 1
427 | end
428 | end
429 | frame[rtype]:SetText(bracket)
430 | end
431 |
432 | -- Refresh tooltip
433 | if frame and mouse_focus == frame then
434 | frame:OnEnter()
435 | end
436 | end
437 |
438 | function addon:MODIFIER_STATE_CHANGED()
439 | if mouse_focus and MouseIsOver(mouse_focus) and mouse_focus.OnEnter then
440 | mouse_focus:OnEnter()
441 | end
442 | end
443 |
444 | local alert_frames = {}
445 | function addon.AlertFrameHook(alert)
446 | -- Reskin toast
447 | local elements = alert_frames[alert]
448 | if not elements then
449 | elements = {}
450 | if not (opt.alert_background and opt.alert_icon_frame) then
451 | local name
452 | if alert.ItemName then
453 | name = alert.ItemName
454 | alert.Label:ClearAllPoints()
455 | alert.Label:SetPoint('TOPLEFT', alert.Icon, 'TOPRIGHT', 15, -5)
456 | elseif alert.BaseQualityItemName then
457 | name = alert.BaseQualityItemName
458 | alert.TitleText:ClearAllPoints()
459 | alert.TitleText:SetPoint('TOPLEFT', alert.Icon, 'TOPRIGHT', 15, -2)
460 | elseif alert.Amount then
461 | name = alert.Amount
462 | alert.Label:ClearAllPoints()
463 | alert.Label:SetPoint('TOPLEFT', alert.Icon, 'TOPRIGHT', 15, -2)
464 | end
465 | name:ClearAllPoints()
466 | name:SetPoint('LEFT', alert.Icon, 'RIGHT', 10, -6)
467 | end
468 | if opt.alert_skin then
469 | local overlay = CreateFrame('Frame', nil, alert, BackdropTemplateMixin and "BackdropTemplate")
470 | overlay:SetPoint('TOPLEFT', 11, -11)
471 | overlay:SetPoint('BOTTOMRIGHT', -11, 11)
472 | overlay:SetFrameLevel(alert:GetFrameLevel())
473 | elements.overlay = overlay
474 | Skinner:Skin(overlay, 'alert')
475 | if opt.alert_background then
476 | local backdrop = CreateFrame('Frame', nil, alert, BackdropTemplateMixin and "BackdropTemplate")
477 | backdrop:SetAllPoints(overlay)
478 | backdrop:SetFrameLevel(alert:GetFrameLevel()-1)
479 | overlay.gradient:SetParent(backdrop)
480 | end
481 |
482 | local icon_frame = CreateFrame('Frame', nil, alert, BackdropTemplateMixin and "BackdropTemplate")
483 | icon_frame:SetPoint('CENTER', alert.Icon, 'CENTER', 0, 0)
484 | icon_frame:SetWidth(alert.Icon:GetWidth() + 4)
485 | icon_frame:SetHeight(alert.Icon:GetHeight() + 4)
486 | elements.icon_frame = icon_frame
487 | Skinner:Skin(icon_frame, 'alert_item')
488 | end
489 |
490 | alert_frames[alert] = elements
491 | end
492 | alert.Background:SetShown(opt.alert_background)
493 | alert.IconBorder:SetShown(opt.alert_icon_frame)
494 | alert.BaseQualityBorder:SetShown(opt.alert_icon_frame)
495 | alert.UpgradeQualityBorder:SetShown(opt.alert_icon_frame)
496 | alert:SetAlpha(opt.alert_alpha)
497 | alert:SetScale(opt.alert_scale)
498 |
499 | -- Update toast
500 | if opt.alert_skin then
501 | local c
502 | if alert.hyperlink then
503 | local _, _, rarity = GetItemInfo(alert.hyperlink)
504 | c = ITEM_QUALITY_COLORS[rarity]
505 | else
506 | c = {r = 1, g = .8, b = 0.1}
507 | end
508 | if type(c) == "table" then -- Sanity check due to 5.4.1 reported error
509 | elements.overlay:SetGradientColor(c.r, c.g, c.b, .2)
510 | elements.icon_frame:SetGradientColor(c.r, c.g, c.b, .2)
511 | elements.overlay:SetBorderColor(c.r, c.g, c.b)
512 | elements.icon_frame:SetBorderColor(c.r, c.g, c.b)
513 | end
514 | end
515 | end
516 |
517 | local AlertFrameTables = {
518 | 'LOOT_WON_ALERT_FRAMES',
519 | 'LOOT_UPGRADE_ALERT_FRAMES',
520 | 'MONEY_WON_ALERT_FRAMES'
521 | }
522 |
523 | function addon.SlashHandler(msg)
524 | if msg == 'reset' then
525 | anchor:Position()
526 | alert_anchor:Position()
527 | elseif msg == 'opt' or msg == 'options' then
528 | addon.ShowOptions()
529 | else
530 | addon.ToggleAnchors()
531 | end
532 | end
533 |
534 | function addon:UpdateAnchors()
535 | anchor:SetShown(opt.roll_anchor.visible)
536 | alert_anchor:SetShown(opt.alert_anchor.visible)
537 | end
538 |
539 | function addon.ToggleAnchors()
540 | local state = anchor:IsShown()
541 | anchor:SetShown(not state)
542 | alert_anchor:SetShown(not state)
543 | end
544 |
545 | -------------------------------------------------------------------------------
546 | -- Frame creation
547 |
548 | do
549 | local sf = string.format
550 | -- Add a specific roll type to the tooltip
551 | local function RollLines(list, hid)
552 | for _,pid in pairs(list) do
553 | local name, class, rtype, roll, is_winner, is_me = HistoryGetPlayerInfo(hid, pid)
554 | -- TODO- ACCOUNT FOR MISSING PLAYERS BETTER
555 | if not name then return nil end
556 | local text, r, g, b, color = FancyPlayerName(name, class, opt)
557 | if roll ~= nil then
558 | if is_winner then
559 | color = '44ff22'
560 | elseif is_me then
561 | color = 'ff2244'
562 | else
563 | color = 'CCCCCC'
564 | end
565 | GameTooltip:AddLine(sf(' |cff%s%s|r %s', color, roll, text), r, g, b)
566 | else
567 | GameTooltip:AddLine(' '..text, r, g, b)
568 | end
569 | end
570 | end
571 |
572 | -- Add roll status or summary to tooltip
573 | local tneed, tgreed, tpass, trolls, tnone, table_sort
574 | = {}, {}, {}, {}, {}, table.sort
575 | local function rsort(a, b)
576 | a, b = trolls[a] or 0, trolls[b] or 0
577 | return a > b and true or false
578 | end
579 |
580 | local function AddIneligibleReason(button, r, g, b)
581 | if button.reason and _G["LOOT_ROLL_INELIGIBLE_REASON"..button.reason] then
582 | GameTooltip:AddLine(string_format(_G["LOOT_ROLL_INELIGIBLE_REASON"..button.reason], button.skill), r or .6, g or .6, b or .6)
583 | GameTooltip:Show()
584 | end
585 | end
586 |
587 | local function AddTooltipLines(self, show_all, show)
588 | -- Locate history item
589 | local rollid, hid = self.rollid, 1
590 | local hrollid, link, players, done
591 | while true do
592 | hrollid, link, players, done = HistoryGetItem(hid)
593 | if not hrollid then
594 | return
595 | elseif hrollid == rollid then
596 | break
597 | end
598 | hid = hid+1
599 | end
600 |
601 | -- Generate player lists
602 | local tneed, tgreed, tpass, tnone, trolls
603 | = wipe(tneed), wipe(tgreed), wipe(tpass), wipe(tnone), wipe(trolls)
604 | for pid=1, players do
605 | local _, _, rtype, roll = HistoryGetPlayerInfo(hid, pid)
606 | local t
607 | if rtype then
608 | if rtype == 0 then
609 | t = tpass
610 | elseif rtype == 1 then
611 | t = tneed
612 | elseif rtype == 2 or rtype == 3 then
613 | t = tgreed
614 | end
615 | trolls[pid] = roll
616 | else
617 | t = tnone
618 | end
619 | if t then
620 | t[#t+1] = pid
621 | end
622 | end
623 |
624 | table_sort(tneed, rsort)
625 | table_sort(tgreed, rsort)
626 | table_sort(tpass, rsort)
627 |
628 | -- Generate tooltip
629 | if show_all then
630 | GameTooltip:AddLine('.', 0, 0, 0)
631 | end
632 | if #tneed ~= 0 and (show_all or show == 1) then
633 | GameTooltip:AddLine(NEED, .2, 1, .1)
634 | RollLines(tneed, hid)
635 | end
636 | if #tgreed ~= 0 and (show_all or (show == 2 or show == 3)) then
637 | GameTooltip:AddLine(GREED, .1, .2, 1)
638 | RollLines(tgreed, hid)
639 | end
640 | if #tpass ~= 0 and (show_all or show == 0) then
641 | GameTooltip:AddLine(PASS, .7, .7, .7)
642 | RollLines(tpass, hid)
643 | end
644 | if show_all and opt.show_undecided then
645 | GameTooltip:AddLine(L.undecided, .7, .3, .2)
646 | RollLines(tnone, hid)
647 | end
648 |
649 | -- Force tooltip to refresh
650 | GameTooltip:Show()
651 | return true
652 | end
653 |
654 | ---------------------------------------------------------------------------
655 | -- Roll buttons
656 | ---------------------------------------------------------------------------
657 | local RollButtonPrototype = XLoot.NewPrototype()
658 | do
659 | function RollButtonPrototype:OnClick()
660 | RollOnLoot(self.parent.rollid, self.type)
661 | end
662 |
663 | function RollButtonPrototype:Toggle(status)
664 | if status then
665 | self:Enable()
666 | self:SetAlpha(1)
667 | else
668 | self:Disable()
669 | self:SetAlpha(.6)
670 | end
671 | SetDesaturation(self:GetNormalTexture(), not status)
672 | end
673 |
674 | function RollButtonPrototype:OnEnter()
675 | mouse_focus = self
676 | GameTooltip:SetOwner(self, 'ANCHOR_TOPLEFT')
677 | AddTooltipLines(self.parent, false, self.type)
678 | -- This isn't working for some stupid reason
679 | if GameTooltip:NumLines() == 0 then
680 | GameTooltip:SetText(self.label, unpack(self.label_colors))
681 | GameTooltip:Show()
682 | end
683 | -- This is for those people who think they should be able
684 | -- to roll on something, can't, and then come complain to me.
685 | if self.reason then
686 | AddIneligibleReason(self, 1, .2, 0)
687 | end
688 | end
689 |
690 | function RollButtonPrototype:OnLeave()
691 | mouse_focus = nil
692 | GameTooltip:Hide()
693 | end
694 |
695 | function RollButtonPrototype:SetText(text)
696 | if text and text > 0 then
697 | self.text:SetText(text)
698 | else
699 | self.text:SetText()
700 | end
701 | end
702 |
703 | local path = [[Interface\Buttons\UI-GroupLoot-%s-%s]]
704 | function RollButtonPrototype:New(parent, roll, label, tex, anchor_to, x, y, label_colors)
705 | local b = self:_New(CreateFrame('Button', nil, parent))
706 | b:SetPoint('LEFT', anchor_to, 'RIGHT', x, y)
707 | b:SetNormalTexture(path:format(tex, 'Up'))
708 | if tex ~= 'Pass' then
709 | b:SetHighlightTexture(path:format(tex, 'Highlight'))
710 | b:SetPushedTexture(path:format(tex, 'Down'))
711 | else
712 | b:SetHighlightTexture(path:format(tex, 'Up'))
713 | b:GetNormalTexture():SetVertexColor(0.8, 0.7, 0.7)
714 | b:GetHighlightTexture():SetAlpha(0.5)
715 | end
716 | b.parent = parent
717 |
718 | local texture_special = b:CreateTexture(nil, 'OVERLAY')
719 | texture_special:SetAllPoints(b)
720 | texture_special:SetAlpha(0.5)
721 | b.texture_special = texture_special
722 |
723 | local text = b:CreateFontString(nil, 'OVERLAY')
724 | text:SetPoint("CENTER", -x + 1, tex == 'DE' and -y +2 or -y)
725 | b.text = text
726 |
727 | b:SetScript('OnEnter', self.OnEnter)
728 | b:SetScript('OnLeave', self.OnLeave)
729 | b:SetScript('OnClick', self.OnClick)
730 | b:SetMotionScriptsWhileDisabled(true)
731 | b:Enable()
732 | b.type = roll
733 | b.label = label
734 | b.label_colors = label_colors
735 |
736 | b:ApplyOptions()
737 |
738 | return b
739 | end
740 |
741 | function RollButtonPrototype:ApplyOptions()
742 | self.text:SetFont(opt.font, 12, opt.font_flag)
743 | self:SetWidth(opt.roll_button_size)
744 | self:SetHeight(opt.roll_button_size)
745 | end
746 | end
747 |
748 | ---------------------------------------------------------------------------
749 | -- Roll frames
750 | ---------------------------------------------------------------------------
751 | RollFramePrototype = XLoot.NewPrototype()
752 | -- Events
753 | function RollFramePrototype:OnEnter()
754 | mouse_focus = self
755 | GameTooltip:SetOwner(self.icon_frame, 'ANCHOR_TOPLEFT', 28, 0)
756 | GameTooltip:SetHyperlink(self.link)
757 | if opt.show_decided or opt.show_undecided then
758 | AddTooltipLines(self, true)
759 | if self.need.reason then
760 | AddIneligibleReason(self.need, 1, .2, 0)
761 | end
762 | if self.greed.reason and self.greed.reason ~= self.need.reason then
763 | AddIneligibleReason(self.greed, .8, .1, 0)
764 | end
765 | if self.disenchant.reason then
766 | AddIneligibleReason(self.disenchant, .6, .05, 0)
767 | end
768 | end
769 | if IsShiftKeyDown() then
770 | GameTooltip_ShowCompareItem()
771 | end
772 | if IsModifiedClick('DRESSUP') then
773 | ShowInspectCursor()
774 | else
775 | ResetCursor()
776 | end
777 | end
778 |
779 | function RollFramePrototype:OnLeave()
780 | mouse_focus = nil
781 | GameTooltip:Hide()
782 | end
783 |
784 | function RollFramePrototype:OnClick(button)
785 | if IsControlKeyDown() then
786 | DressUpItemLink(self.link)
787 | elseif IsShiftKeyDown() then
788 | ChatEdit_InsertLink(self.link)
789 | end
790 | end
791 |
792 | -- Status bar update
793 | local max = math.max
794 | function RollFramePrototype:OnBarUpdate()
795 | local parent = self.parent
796 | if parent.over then
797 | self.spark:Hide()
798 | self:SetValue(0)
799 | parent.text_time:SetText()
800 | return
801 | end
802 | local time = GetTime()
803 | -- TODO: Remove?
804 | local status, result = pcall(GetLootRollTimeLeft, parent.rollid)
805 | if not status or result == 0 then
806 | local ended = parent.rollended
807 | if ended then
808 | if time - ended > 10 then
809 | anchor:Pop(parent)
810 | end
811 | else
812 | parent.rollended = time
813 | end
814 | end
815 | -- /TODO
816 | local remaining = self.expires - time
817 | if remaining < -4 then
818 | anchor:Pop(parent)
819 | else
820 | local now, length = max(remaining, -1), self.length
821 | self.spark:SetPoint('CENTER', self, 'LEFT', (now / length) * self:GetWidth(), 0)
822 | self:SetValue(now)
823 | self.spark:Show()
824 | if opt.show_time_remaining then
825 | if remaining >= 0 then
826 | parent.text_time:SetText(sf('%.0f', max(0, remaining)))
827 | parent.text_time:Show()
828 | else
829 | parent.text_time:Hide()
830 | end
831 | end
832 | end
833 | end
834 |
835 | function RollFramePrototype:Popped()
836 | rolls[self.rollid] = nil
837 | end
838 |
839 | -- Create roll frame
840 | function RollFramePrototype:New()
841 | -- Base frame
842 | local frame = self:_New(CreateFrame('Button', nil, UIParent))
843 | frame:SetFrameLevel(anchor:GetFrameLevel())
844 | frame:SetHeight(24)
845 | frame:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
846 | frame:SetScript('OnEnter', self.OnEnter)
847 | frame:SetScript('OnLeave', self.OnLeave)
848 | frame:SetScript('OnClick', self.OnClick)
849 |
850 | -- Overlay (For skin border)
851 | local overlay = CreateFrame('frame', nil, frame, BackdropTemplateMixin and "BackdropTemplate")
852 | overlay:SetFrameLevel(frame:GetFrameLevel())
853 | overlay:SetAllPoints()
854 | frame.overlay = overlay
855 | local skin = Skinner:Skin(overlay, 'row')
856 |
857 | -- Item icon (For skin border)
858 | local icon_frame = CreateFrame('Frame', nil, frame)
859 | icon_frame:SetPoint('LEFT', 0, 0)
860 | icon_frame:SetWidth(28)
861 | icon_frame:SetHeight(28)
862 | frame.icon_frame = icon_frame
863 | Skinner:Skin(icon_frame, 'item')
864 |
865 | -- Item texture
866 | local icon = icon_frame:CreateTexture(nil, 'BACKGROUND')
867 | icon:SetPoint('TOPLEFT', 3, -3)
868 | icon:SetPoint('BOTTOMRIGHT', -3, 3)
869 | icon:SetTexCoord(.07,.93,.07,.93)
870 | frame.icon = icon
871 |
872 | -- Timer bar
873 | local bar = CreateFrame('StatusBar', nil, frame)
874 | bar:SetFrameLevel(frame:GetFrameLevel())
875 | local pad = skin.padding or 2
876 | bar:SetPoint('TOPRIGHT', -pad - 3, -pad - 3)
877 | bar:SetPoint('BOTTOMRIGHT', -pad - 3, pad + 3)
878 | bar:SetPoint('LEFT', icon_frame, 'RIGHT', -pad, 0)
879 | bar:SetStatusBarTexture(skin.bar_texture)
880 | bar:SetScript('OnUpdate', self.OnBarUpdate)
881 | bar.parent = frame
882 | frame.bar = bar
883 | -- Reference bar for quick re-skinning when XLoot skin changes
884 | table.insert(addon.bars, bar)
885 |
886 | local spark = bar:CreateTexture(nil, 'OVERLAY')
887 | spark:SetWidth(14)
888 | spark:SetHeight(38)
889 | spark:SetTexture([[Interface\CastingBar\UI-CastingBar-Spark]])
890 | spark:SetBlendMode('ADD')
891 | bar.spark = spark
892 |
893 | -- Bind text
894 | local bind = icon_frame:CreateFontString(nil, 'OVERLAY')
895 | bind:SetPoint('BOTTOM', 0, 1)
896 | frame.text_bind = bind
897 |
898 | -- Time text
899 | local time = icon_frame:CreateFontString(nil, 'OVERLAY')
900 | time:SetPoint('CENTER', 0, 2)
901 | frame.text_time = time
902 |
903 | -- Item level
904 | local ilvl = icon_frame:CreateFontString(nil, 'OVERLAY')
905 | ilvl:SetPoint('TOPLEFT', 3, -3)
906 | frame.text_ilvl = ilvl
907 |
908 | -- Roll buttons
909 | local n = RollButtonPrototype:New(frame, 1, NEED, 'Dice', icon_frame, 3, -1, {.2, 1, .1})
910 | local g = RollButtonPrototype:New(frame, 2, GREED, 'Coin', n, 0, -2, {.1, .2, 1})
911 | local d = RollButtonPrototype:New(frame, 3, ROLL_DISENCHANT, 'DE', g, 0, 2, {.1, .2, 1})
912 | local p_to = d
913 | if not BUILD_HAS_DISENCHANT then
914 | p_to = g
915 | d:Hide()
916 | end
917 | local p = RollButtonPrototype:New(frame, 0, PASS, 'Pass', p_to, 0, 2, {.7, .7, .7})
918 | frame.need, frame.greed, frame.disenchant, frame.pass = n, g, d, p
919 |
920 | -- Roll status text
921 | local status = frame:CreateFontString(nil, 'OVERLAY')
922 | status:SetHeight(16)
923 | status:SetJustifyH('LEFT')
924 | status:SetPoint('LEFT', icon_frame, 'RIGHT', 1, 0)
925 | status:SetPoint('RIGHT', p, 'RIGHT', 2, 0)
926 | frame.text_status = status
927 |
928 | -- Loot name/link
929 | local loot = frame:CreateFontString(nil, 'OVERLAY')
930 | loot:SetHeight(16)
931 | loot:SetJustifyH('LEFT')
932 | loot:SetPoint('LEFT', p, 'RIGHT', 3, -1)
933 | loot:SetPoint('RIGHT', frame, 'RIGHT', -5, 0)
934 | frame.text_loot = loot
935 |
936 | frame:ApplyOptions()
937 |
938 | return frame
939 | end
940 |
941 | function RollFramePrototype:ApplyOptions()
942 | self:SetWidth(opt.roll_width)
943 |
944 | -- Status bar is reskinned with SkinUpdate
945 |
946 | self.need:ApplyOptions()
947 | self.greed:ApplyOptions()
948 | self.disenchant:ApplyOptions()
949 | self.pass:ApplyOptions()
950 |
951 | self.text_ilvl:SetFont(opt.font, 8, 'OUTLINE')
952 | self.text_bind:SetFont(opt.font, 8, 'THICKOUTLINE')
953 | self.text_time:SetFont(opt.font, 12, 'OUTLINE')
954 | self.text_status:SetFont(opt.font, 12, opt.font_flag)
955 | self.text_loot:SetFont(opt.font, 12, opt.font_flag)
956 |
957 | self.text_time:SetShown(opt.show_time_remaining)
958 | self.text_ilvl:SetShown(opt.text_ilvl)
959 | end
960 | end
961 |
962 | ---------------------------------------------------------------------------
963 | -- AddOn setup and events
964 | ---------------------------------------------------------------------------
965 |
966 | -- Update skins when XLoot skin changes
967 | function addon:SkinUpdate()
968 | local skin = Skinner:Reskin()
969 | local padding = skin.padding or 2
970 | local p, n = padding + 3, -padding - 3
971 | for _,bar in pairs(addon.bars) do
972 | bar:ClearAllPoints()
973 | bar:SetPoint('TOPRIGHT', n, n)
974 | bar:SetPoint('BOTTOMRIGHT', n, p)
975 | bar:SetPoint('LEFT', bar.parent.icon_frame, 'RIGHT', -padding, 0)
976 | bar:SetStatusBarTexture(skin.bar_texture)
977 | local link = bar.parent.link
978 | if link then
979 | local r, g, b = C_Item.GetItemQualityColor(select(3, GetItemInfo(link)))
980 | bar.parent.overlay:SetBorderColor(r, g, b)
981 | bar.parent.icon_frame:SetBorderColor(r, g, b)
982 | end
983 | end
984 |
985 | end
986 |
987 | -- Move anchors when scale changes
988 | function addon:ApplyOptions()
989 | opt = self.opt
990 |
991 | anchor:UpdateSVData(opt.roll_anchor)
992 | alert_anchor:UpdateSVData(opt.alert_anchor)
993 |
994 | self:SkinUpdate()
995 |
996 | anchor:Restack()
997 |
998 | for _,frame in pairs(anchor.children) do
999 | if frame.ApplyOptions then
1000 | frame:ApplyOptions()
1001 | end
1002 | end
1003 | end
1004 |
1005 |
1006 | ---------------------------------------------------------------------------
1007 | -- Test rolls
1008 | ---------------------------------------------------------------------------
1009 | local preview_loot = {
1010 | { 52722, false, true, true, true },
1011 | { 31304, true, false, true, true, 1 },
1012 | { 37254, true, false, false, true, 2, 2 },
1013 | { 13262, true, false, false, false, 4, 4, 4, 69 },
1014 | { 15487, false, true, true, true }
1015 | }
1016 | -- Activate items
1017 | for i, t in ipairs(preview_loot) do
1018 | GetItemInfo(t[1])
1019 | end
1020 |
1021 | local init, tests, links, StartFakeRoll = false, {}, {}, nil
1022 |
1023 | local deframe = CreateFrame('Frame')
1024 |
1025 | -- Currently only debugs one roll at a time.
1026 | function XLootGroup.TestSettings()
1027 | local FakeHistory
1028 | local schedule = {}
1029 | local type_index = { 'need', 'greed', 'disenchant', [0] = 'pass' }
1030 | if not init then
1031 | print(L.debug_warning)
1032 | init = true
1033 | local tick = 0
1034 | deframe:SetScript('OnUpdate', function(self, elapsed)
1035 | tick = tick + elapsed
1036 | if tick > 1 then
1037 | tick = 0
1038 | local time = GetTime()
1039 | for k,v in pairs(schedule) do
1040 | if v[1] < time then
1041 | local e = v
1042 | schedule[k] = nil
1043 | e[2]()
1044 | e[3](unpack(e[4]))
1045 | end
1046 | end
1047 | end
1048 | end)
1049 |
1050 | FakeHistory = {
1051 | rolls = {},
1052 | links = {},
1053 | items = {}
1054 | }
1055 |
1056 | local function after(seconds, func, target, ...)
1057 | table.insert(schedule, { GetTime() + seconds, func, target, {...} } )
1058 | end
1059 |
1060 | local function changed(...)
1061 | addon:LOOT_HISTORY_ROLL_CHANGED(...)
1062 | end
1063 |
1064 | function GetLootRollItemInfo(id)
1065 | return unpack(FakeHistory.rolls[id])
1066 | end
1067 |
1068 | function GetLootRollItemLink(id)
1069 | return FakeHistory.links[id]
1070 | end
1071 |
1072 | function GetLootRollTimeLeft()
1073 | return 1
1074 | end
1075 |
1076 | function RollOnLoot(rollid, rtypeid)
1077 | FakeHistory.items[1].players[1][3] = rtypeid
1078 | changed(1, 1)
1079 | end
1080 |
1081 | function UnitGroupRolesAssigned(player)
1082 | local s = math.random(1, 3)
1083 | if s == 1 then
1084 | return "HEALER"
1085 | elseif s == 2 then
1086 | return "DAMAGER"
1087 | end
1088 | return "TANK"
1089 | end
1090 |
1091 | function HistoryGetItem(hid)
1092 | return unpack(FakeHistory.items[hid].item)
1093 | end
1094 |
1095 | function HistoryGetPlayerInfo(hid, pid)
1096 | return unpack(FakeHistory.items[hid].players[pid])
1097 | end
1098 |
1099 | function StartFakeRoll()
1100 | local fake = {}
1101 |
1102 | local item = preview_loot[random(1, #preview_loot)]
1103 | local iname, ilink, iquality, _, _, _, _, _, _, itex = GetItemInfo(item[1])
1104 |
1105 | local rollid = #FakeHistory.rolls + 1
1106 |
1107 | fake.item = { rollid, ilink, 5, false, nil, false }
1108 | fake.players = {
1109 | { me, select(2, UnitClass('player')), nil, nil, false, true },
1110 | { 'Player1', 'MAGE', nil, nil, false, false },
1111 | { 'Player2', 'PRIEST', nil, nil, false, false },
1112 | { 'Player3', 'WARRIOR', nil, nil, false, false },
1113 | { 'Player4', 'SHAMAN', nil, nil, false, false }
1114 | }
1115 | FakeHistory.rolls[rollid] = { itex, iname, 1, iquality, select(2, unpack(item)) }
1116 | FakeHistory.links[rollid] = ilink
1117 |
1118 | table.insert(FakeHistory.items, 1, fake)
1119 |
1120 | addon:START_LOOT_ROLL(rollid, random(20000, 40000), true)
1121 | after(5, function() fake.players[2][3] = 0 end, changed, 1, 2)
1122 | after(7, function() fake.players[3][3] = 2 end, changed, 1, 3)
1123 | after(9, function() fake.players[4][3] = 3 end, changed, 1, 4)
1124 | after(11, function() fake.players[5][3] = 1 end, changed, 1, 5)
1125 | end
1126 |
1127 | end
1128 | StartFakeRoll()
1129 | end
1130 |
1131 | XLoot:SetSlashCommand('xlgd', XLootGroup.TestSettings)
1132 |
1133 | --@do-not-package@
1134 | local function alert()
1135 | local _, link = GetItemInfo(preview_loot[random(1, #preview_loot)][1])
1136 | LootWonAlertFrame_ShowAlert(link, random(1, 4), random(1, 4)-1, random(1, 100))
1137 | LootUpgradeFrame_ShowAlert(link, random(1, 4), 1, random(1,4)-1)
1138 | MoneyWonAlertFrame_ShowAlert(random(1, 100000))
1139 | end
1140 |
1141 | XLoot:SetSlashCommand('xlga', alert)
1142 |
1143 | local AC = LibStub('AceConsole-2.0', true)
1144 | if AC then print = function(...) AC:PrintLiteral(...) end end
1145 | --@end-do-not-package@
1146 |
--------------------------------------------------------------------------------