├── .gitignore
├── .gitattributes
├── Textures
├── HDIV-Mid.blp
├── TDF-Bot.blp
├── TDF-Fill.blp
├── TDF-Left.blp
├── TDF-Top.blp
├── VDIV-Bot.blp
├── VDIV-Mid.blp
├── VDIV-Top.blp
├── HDIV-Left.blp
├── HDIV-Right.blp
├── PNG
│ ├── TDF-Bot.png
│ ├── TDF-Top.png
│ ├── HDIV-Left.png
│ ├── HDIV-Mid.png
│ ├── TDF-Fill.png
│ ├── TDF-Left.png
│ ├── TDF-Right.png
│ ├── VDIV-Bot.png
│ ├── VDIV-Mid.png
│ ├── VDIV-Top.png
│ ├── HDIV-Right.png
│ ├── TDF-BotLeft.png
│ ├── TDF-BotRight.png
│ ├── TDF-TopLeft.png
│ ├── TDF-TopRight.png
│ ├── HDIV-LeftSplit.png
│ ├── VDIV-BotSplit.png
│ ├── VDIV-TopSplit.png
│ └── HDIV-RightSplit.png
├── TDF-BotLeft.blp
├── TDF-Right.blp
├── TDF-TopLeft.blp
├── TDF-BotRight.blp
├── TDF-TopRight.blp
├── VDIV-BotSplit.blp
├── VDIV-TopSplit.blp
├── HDIV-LeftSplit.blp
└── HDIV-RightSplit.blp
├── KKore.toc
├── KKore.xml
├── Libs
├── LibStub.lua
├── AceEvent-3.0.lua
├── AceLocale-3.0.lua
├── CallbackHandler-1.0.lua
├── LibDeformat-3.0.lua
├── AceBucket-3.0.lua
├── AceTimer-3.0.lua
├── AceComm-3.0.lua
├── ChatThrottleLib.lua
└── AceHook-3.0.lua
├── Locales
├── KKore-enUS.lua
├── KKore-ruRU.lua
├── KKore-deDE.lua
└── KKore-frFR.lua
├── KKoreDialogs.lua
├── LICENSE
├── KKoreHash.lua
├── KKoreLoot.lua
└── KKoreKonfer.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | *.bak
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.png binary
3 | *.blp binary
4 | *.jpg binary
5 |
--------------------------------------------------------------------------------
/Textures/HDIV-Mid.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/HDIV-Mid.blp
--------------------------------------------------------------------------------
/Textures/TDF-Bot.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-Bot.blp
--------------------------------------------------------------------------------
/Textures/TDF-Fill.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-Fill.blp
--------------------------------------------------------------------------------
/Textures/TDF-Left.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-Left.blp
--------------------------------------------------------------------------------
/Textures/TDF-Top.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-Top.blp
--------------------------------------------------------------------------------
/Textures/VDIV-Bot.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/VDIV-Bot.blp
--------------------------------------------------------------------------------
/Textures/VDIV-Mid.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/VDIV-Mid.blp
--------------------------------------------------------------------------------
/Textures/VDIV-Top.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/VDIV-Top.blp
--------------------------------------------------------------------------------
/Textures/HDIV-Left.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/HDIV-Left.blp
--------------------------------------------------------------------------------
/Textures/HDIV-Right.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/HDIV-Right.blp
--------------------------------------------------------------------------------
/Textures/PNG/TDF-Bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-Bot.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-Top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-Top.png
--------------------------------------------------------------------------------
/Textures/TDF-BotLeft.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-BotLeft.blp
--------------------------------------------------------------------------------
/Textures/TDF-Right.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-Right.blp
--------------------------------------------------------------------------------
/Textures/TDF-TopLeft.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-TopLeft.blp
--------------------------------------------------------------------------------
/Textures/PNG/HDIV-Left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/HDIV-Left.png
--------------------------------------------------------------------------------
/Textures/PNG/HDIV-Mid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/HDIV-Mid.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-Fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-Fill.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-Left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-Left.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-Right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-Right.png
--------------------------------------------------------------------------------
/Textures/PNG/VDIV-Bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/VDIV-Bot.png
--------------------------------------------------------------------------------
/Textures/PNG/VDIV-Mid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/VDIV-Mid.png
--------------------------------------------------------------------------------
/Textures/PNG/VDIV-Top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/VDIV-Top.png
--------------------------------------------------------------------------------
/Textures/TDF-BotRight.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-BotRight.blp
--------------------------------------------------------------------------------
/Textures/TDF-TopRight.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/TDF-TopRight.blp
--------------------------------------------------------------------------------
/Textures/VDIV-BotSplit.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/VDIV-BotSplit.blp
--------------------------------------------------------------------------------
/Textures/VDIV-TopSplit.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/VDIV-TopSplit.blp
--------------------------------------------------------------------------------
/Textures/HDIV-LeftSplit.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/HDIV-LeftSplit.blp
--------------------------------------------------------------------------------
/Textures/HDIV-RightSplit.blp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/HDIV-RightSplit.blp
--------------------------------------------------------------------------------
/Textures/PNG/HDIV-Right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/HDIV-Right.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-BotLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-BotLeft.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-BotRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-BotRight.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-TopLeft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-TopLeft.png
--------------------------------------------------------------------------------
/Textures/PNG/TDF-TopRight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/TDF-TopRight.png
--------------------------------------------------------------------------------
/Textures/PNG/HDIV-LeftSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/HDIV-LeftSplit.png
--------------------------------------------------------------------------------
/Textures/PNG/VDIV-BotSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/VDIV-BotSplit.png
--------------------------------------------------------------------------------
/Textures/PNG/VDIV-TopSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/VDIV-TopSplit.png
--------------------------------------------------------------------------------
/Textures/PNG/HDIV-RightSplit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/speaker/kore/master/Textures/PNG/HDIV-RightSplit.png
--------------------------------------------------------------------------------
/KKore.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 20502
2 | ## Title: KahLua Kore Libraries |cffff2222-K-|r
3 | ## Notes: Core libraries for all |cffff2222KahLua|r mods.
4 | ## Author: Cruciformer
5 | ## Version: @revision@
6 | ## X-email: me@cruciformer.com
7 | ## X-Credits: Ace3 development team
8 | ## X-Category: Library
9 | ## X-License: Apache
10 | ## X-Website: http://kahluamod.com/kore
11 | ## X-git: git@github.com:kahluamods/kore
12 |
13 | KKore.xml
14 |
--------------------------------------------------------------------------------
/KKore.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Libs/LibStub.lua:
--------------------------------------------------------------------------------
1 | -- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
2 | -- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
3 | local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
4 | local LibStub = _G[LIBSTUB_MAJOR]
5 |
6 | if not LibStub or LibStub.minor < LIBSTUB_MINOR then
7 | LibStub = LibStub or {libs = {}, minors = {} }
8 | _G[LIBSTUB_MAJOR] = LibStub
9 | LibStub.minor = LIBSTUB_MINOR
10 |
11 | function LibStub:NewLibrary(major, minor)
12 | assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
13 | minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
14 |
15 | local oldminor = self.minors[major]
16 | if oldminor and oldminor >= minor then return nil end
17 | self.minors[major], self.libs[major] = minor, self.libs[major] or {}
18 | return self.libs[major], oldminor
19 | end
20 |
21 | function LibStub:GetLibrary(major, silent)
22 | if not self.libs[major] and not silent then
23 | error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
24 | end
25 | return self.libs[major], self.minors[major]
26 | end
27 |
28 | function LibStub:IterateLibraries() return pairs(self.libs) end
29 | setmetatable(LibStub, { __call = LibStub.GetLibrary })
30 | end
31 |
--------------------------------------------------------------------------------
/Locales/KKore-enUS.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - core library functions for KahLua addons.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
8 |
9 | Copyright 2008-2019 James Kean Johnston. All rights reserved.
10 |
11 | Licensed under the Apache License, Version 2.0 (the "License");
12 | you may not use this file except in compliance with the License.
13 | You may obtain a copy of the License at
14 |
15 | http://www.apache.org/licenses/LICENSE-2.0
16 |
17 | Unless required by applicable law or agreed to in writing, software
18 | distributed under the License is distributed on an "AS IS" BASIS,
19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | See the License for the specific language governing permissions and
21 | limitations under the License.
22 | ]]
23 |
24 | local K = LibStub:GetLibrary("KKore")
25 |
26 | if (not K) then
27 | return
28 | end
29 |
30 | local L = LibStub("AceLocale-3.0"):NewLocale("KKore", "enUS", true)
31 | if (not L) then
32 | return
33 | end
34 |
35 | --
36 | -- A few strings that are very commonly used that can be translated once.
37 | -- These are mainly used in user interface elements so the strings should
38 | -- be short, preferably one word.
39 | --
40 | K.OK_STR = "Ok"
41 | K.CANCEL_STR = "Cancel"
42 | K.ACCEPT_STR = "Accept"
43 | K.CLOSE_STR = "Close"
44 | K.OPEN_STR = "Open"
45 | K.HELP_STR = "Help"
46 | K.YES_STR = "Yes"
47 | K.NO_STR = "No"
48 | K.KAHLUA = "KahLua"
49 |
50 | L["CMD_KAHLUA"] = "kahlua"
51 | L["CMD_KKORE"] = "kkore"
52 | L["CMD_HELP"] = "help"
53 | L["CMD_LIST"] = "list"
54 | L["CMD_DEBUG"] = "debug"
55 | L["CMD_VERSION"] = "version"
56 |
57 | L["KAHLUA_VER"] = "(version %d)"
58 | L["KAHLUA_DESC"] = "a suite of user interface enhancements."
59 | L["KORE_DESC"] = "core %s functionality, such as debugging and profiling."
60 | L["Usage: %s/%s module [arg [arg...]]%s"] = true
61 | L[" Where module is one of the following modules:"] = true
62 | L["For help with any module, type %s/%s module %s%s."] = true
63 | L["KahLua Kore usage: %s/%s command [arg [arg...]]%s"] = true
64 | L["%s/%s %s module level%s"] = true
65 | L[" Sets the debug level for a module. 0 disables."] = true
66 | L[" The higher the number the more verbose the output."] = true
67 | L[" Lists all modules registered with KahLua."] = true
68 | L["The following modules are available:"] = true
69 | L["Cannot enable debugging for '%s' - no such module."] = true
70 | L["Debug level %d out of bounds - must be between 0 and 10."] = true
71 | L["Module '%s' does not exist. Use %s/%s %s%s for a list of available modules."] = true
72 | L["KKore extensions loaded:"] = true
73 | L["Chest"] = true
74 |
75 | -- Konfer related stuff
76 | L["Not Set"] = true
77 | L["Tank"] = true
78 | L["Ranged DPS"] = true
79 | L["Melee DPS"] = true
80 | L["Healer"] = true
81 | L["Spellcaster"] = true
82 | L["your version of %s is out of date. Please update it."] = true
83 | L["VCTITLE"] = "%s %s Version Check"
84 | L["Version"] = true
85 | L["In Raid"] = true
86 | L["Who"] = true
87 | L["Shield"] = true
88 |
89 | L["KONFER_SEL_TITLE"] = "Select Active %s Konfer Module"
90 | L["KONFER_SEL_HEADER"] = "You have multiple %s Konfer modules installed, and more than one of them is active and set to auto-open when you loot a corpse or chest. This can cause conflicts and you need to select which one of the modules should be the active one. All others will be suspended."
91 | L["KONFER_SEL_DDTITLE"] = "Select Module to Make Active"
92 | L["KONFER_ACTIVE"] = "active"
93 | L["KONFER_SUSPENDED"] = "suspended"
94 | L["KONFER_SUSPEND_OTHERS"] = "You have just activated the %s Konfer module above, but other Konfer modules are also currently active. Having multiple Konfer modules active at the same time can cause problems, especially if more than one of them is set to auto-open on loot. It is suggested that you suspend all other Konfer modules. If you would like to do this and make the module above the only active one, press 'Ok' below. If you are certain you want to leave multiple Konfer modules running, press 'Cancel'."
95 |
--------------------------------------------------------------------------------
/Locales/KKore-ruRU.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - core library functions for KahLua addons.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
8 |
9 | Copyright 2008-2018 James Kean Johnston. All rights reserved.
10 |
11 | Licensed under the Apache License, Version 2.0 (the "License");
12 | you may not use this file except in compliance with the License.
13 | You may obtain a copy of the License at
14 |
15 | http://www.apache.org/licenses/LICENSE-2.0
16 |
17 | Unless required by applicable law or agreed to in writing, software
18 | distributed under the License is distributed on an "AS IS" BASIS,
19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | See the License for the specific language governing permissions and
21 | limitations under the License.
22 | ]]
23 |
24 | local K = LibStub:GetLibrary("KKore")
25 |
26 | if (not K) then
27 | return
28 | end
29 |
30 | local L = LibStub("AceLocale-3.0"):NewLocale("KKore", "ruRU")
31 | if (not L) then
32 | return
33 | end
34 |
35 | --
36 | -- A few strings that are very commonly used that can be translated once.
37 | -- These are mainly used in user interface elements so the strings should
38 | -- be short, preferably one word.
39 | --
40 | K.OK_STR = "Ok"
41 | K.CANCEL_STR = "Отмена"
42 | K.ACCEPT_STR = "Принять"
43 | K.CLOSE_STR = "Закрыть"
44 | K.OPEN_STR = "Открыть"
45 | K.HELP_STR = "Помощь"
46 | K.YES_STR = "Да"
47 | K.NO_STR = "Нет"
48 | K.KAHLUA = "KahLua"
49 |
50 | L["CMD_KAHLUA"] = "kahlua"
51 | L["CMD_KKORE"] = "kkore"
52 | L["CMD_HELP"] = "help"
53 | L["CMD_LIST"] = "list"
54 | L["CMD_DEBUG"] = "debug"
55 | L["CMD_VERSION"] = "version"
56 |
57 | L["KAHLUA_VER"] = "(версия %d)"
58 | L["KAHLUA_DESC"] = "комплекс усовершенствований UI."
59 | L["KORE_DESC"] = "основные %s функции, такие как отладка и профили."
60 | L["Usage: %s/%s module [arg [arg...]]%s"] = "Использование: %s/%s модуль [arg [arg...]]%s"
61 | L[" Where module is one of the following modules:"] = " Где модуль один из следующих:"
62 | L["For help with any module, type %s/%s module %s%s."] = "Для помощи по любому из модулей наберите %s/%s модуль %s%s."
63 | L["KahLua Kore usage: %s/%s command [arg [arg...]]%s"] = "KahLua Kore использование: %s/%s команда [arg [arg...]]%s."
64 | L["%s/%s %s module level%s"] = "%s/%s %s уровень модуля %s"
65 | L[" Sets the debug level for a module. 0 disables."] = " Задайте уровень отладки для модуля. 0 отключить."
66 | L[" The higher the number the more verbose the output."] = " Чем выше число, тем более подробный вывод."
67 | L[" Lists all modules registered with KahLua."] = " Список всех модулей, зерегистрированных в KahLua."
68 | L["The following modules are available:"] = "Доступны следующие модули:"
69 | L["Cannot enable debugging for '%s' - no such module."] = "Не могу включить отладку для '%s' - модуль не существует."
70 | L["Debug level %d out of bounds - must be between 0 and 10."] = "Уровень отладки %d вне диапазона - должен быть от 0 до 10."
71 | L["Module '%s' does not exist. Use %s/%s %s%s for a list of available modules."] = "Модуль '%s' не существует. Используйте %s/%s %s%s для отображения списка доступных модулей."
72 | L["KKore extensions loaded:"] = "KKore расширения загружены:"
73 | L["Chest"] = true
74 |
75 | L["Not Set"] = "Нет"
76 | L["Tank"] = "Танк"
77 | L["Ranged DPS"] = "Рэйнж ДД"
78 | L["Melee DPS"] = "Мили ДД"
79 | L["Healer"] = "Хилер"
80 | L["Spellcaster"] = "Кастер"
81 | L["your version of %s is out of date. Please update it."] = true
82 | L["VCTITLE"] = "%s %s Version Check"
83 | L["Version"] = true
84 | L["In Raid"] = true
85 | L["Who"] = true
86 | L["Shield"] = true
87 |
88 | L["KONFER_SEL_TITLE"] = "Выберите Активный %s Konfer Модуль"
89 | L["KONFER_SEL_HEADER"] = "У Вас установлено несколько %s Konfer модулей, и активны более одного, когда Вы осматриваете тело монстра или сундук. Это может вызывать конфликты и Вы должны выбрать лишь один, который будет оставаться активным. Все остальные будут отключены."
90 | L["KONFER_SEL_DDTITLE"] = "Пометить модуль как Активный"
91 | L["KONFER_ACTIVE"] = "активный"
92 | L["KONFER_SUSPENDED"] = "отключен"
93 | L["KONFER_SUSPEND_OTHERS"] = "Вы активировали %s Konfer модуль, но один из других модулей сейчас также помечен как Активный. Наличие нескольких одновременно активных Konfer модулей может вызывать проблемы, особенно если они должны срабатывать при дележе добычи. Предполагается, что вы должны отключить остальные Konfer модули. Если Вы хотите это сделать и оставить активным, только этот модуль, а остальные отключить нажмите 'Ok'. Если вы уверены, что хотите оставить несколько активных Konfer модулей, нажмите 'Отмена'."
94 |
--------------------------------------------------------------------------------
/Locales/KKore-deDE.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - core library functions for KahLua addons.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
8 |
9 | Copyright 2008-2019 James Kean Johnston. All rights reserved.
10 |
11 | Licensed under the Apache License, Version 2.0 (the "License");
12 | you may not use this file except in compliance with the License.
13 | You may obtain a copy of the License at
14 |
15 | http://www.apache.org/licenses/LICENSE-2.0
16 |
17 | Unless required by applicable law or agreed to in writing, software
18 | distributed under the License is distributed on an "AS IS" BASIS,
19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | See the License for the specific language governing permissions and
21 | limitations under the License.
22 | ]]
23 |
24 | local K = LibStub:GetLibrary("KKore")
25 |
26 | if (not K) then
27 | return
28 | end
29 |
30 | local L = LibStub("AceLocale-3.0"):NewLocale("KKore", "deDE")
31 | if (not L) then
32 | return
33 | end
34 |
35 | --
36 | -- A few strings that are very commonly used that can be translated once.
37 | -- These are mainly used in user interface elements so the strings should
38 | -- be short, preferably one word.
39 | --
40 | K.OK_STR = "Ok"
41 | K.CANCEL_STR = "Abbrechen"
42 | K.ACCEPT_STR = "Akzeptieren"
43 | K.CLOSE_STR = "Schließen"
44 | K.OPEN_STR = "\195\150ffnen"
45 | K.HELP_STR = "Hilfe"
46 | K.YES_STR = "Ja"
47 | K.NO_STR = "Nein"
48 | K.KAHLUA = "KahLua"
49 |
50 | L["CMD_KAHLUA"] = "kahlua"
51 | L["CMD_KKORE"] = "kkore"
52 | L["CMD_HELP"] = "hilfe"
53 | L["CMD_LIST"] = "list"
54 | L["CMD_DEBUG"] = "debig"
55 | L["CMD_VERSION"] = "version"
56 |
57 | L["KAHLUA_VER"] = "(Version %d)"
58 | L["KAHLUA_DESC"] = "eine Sammlung von User Interface Verbesserungen."
59 | L["KORE_DESC"] = "Core %s Fuktionalität, wie z. B. debugging und profiling."
60 | L["Usage: %s/%s module [arg [arg...]]%s"] = "Benutzung: %s/%s Modul [arg [arg...]]%s"
61 | L[" Where module is one of the following modules:"] = " Wo das Modul eines der folgenden ist:"
62 | L["For help with any module, type %s/%s module %s%s."] = "Für Hilfe mit einem der Module, tippe %s/%s Modul %s%s."
63 | L["KahLua Kore usage: %s/%s command [arg [arg...]]%s"] = "KahLua Kore Benutzung: %s/%s Kommando [arg [arg...]]%s."
64 | L["%s/%s %s module level%s"] = "%s/%s %s Modul level%s"
65 | L[" Sets the debug level for a module. 0 disables."] = " Setzt das Debug Level für ein Modul. 0 Deaktiviert es."
66 | L[" The higher the number the more verbose the output."] = " Je höher die Zahl, desto ausführlicher ist die Ausgabe."
67 | L[" Lists all modules registered with KahLua."] = " Listet alle Module die mit KahLua registriert sind auf."
68 | L["The following modules are available:"] = "Die folgenden Module sind verfügbar:"
69 | L["Cannot enable debugging for '%s' - no such module."] = "Kann das debuggen für '%s' nicht aktivieren - dieses Modul existiert nicht."
70 | L["Debug level %d out of bounds - must be between 0 and 10."] = "Debug Level %d ist außerhalb des Rahmens - es muss zwischen 0 und 10 liegen."
71 | L["Module '%s' does not exist. Use %s/%s %s%s for a list of available modules."] = "Modul '%s' existiert nicht. Benutze %s/%s %s%s für eine Liste aller verfügbarer Module."
72 | L["KKore extensions loaded:"] = "KKore Erweiterungen geladen:"
73 | L["Chest"] = true
74 |
75 | L["Not Set"] = "Nicht festgelegt"
76 | L["Tank"] = "Tank"
77 | L["Ranged DPS"] = "Ranged DPS"
78 | L["Melee DPS"] = "Melee DPS"
79 | L["Healer"] = "Healer"
80 | L["Spellcaster"] = "Spellcaster"
81 | L["your version of %s is out of date. Please update it."] = "Deine Version von %s ist nicht aktuell. Bitte aktualisiere sie."
82 | L["VCTITLE"] = "%s %s Version Check"
83 | L["Version"] = "Version"
84 | L["In Raid"] = "In Raid"
85 | L["Who"] = "Wer"
86 | L["Shield"] = true
87 |
88 | L["KONFER_SEL_TITLE"] = "Auswahl des aktiven %s Konfer-Moduls"
89 | L["KONFER_SEL_HEADER"] = "Du hast %s Konfer-Module installiert und mehr als eines von ihnen ist aktiv und eingestellt auf automatisches Ãffnen, wenn ein Leichnam oder eine Kiste/Truhe geplündert wird. Dies kann Konflikte verursachen, du solltest eines der Module als aktives auswählen. Alle anderen werden dann ausgeschlossen."
90 | L["KONFER_SEL_DDTITLE"] = "Modul-Auswahl zum Aktivieren"
91 | L["KONFER_ACTIVE"] = "aktiv"
92 | L["KONFER_SUSPENDED"] = "ausgeschlossen"
93 | L["KONFER_SUSPEND_OTHERS"] = "Du hast das %s Konfer-Modul gerade aktiviert, aber andere Konfer-Module sind ebenfalls aktiv. Mehrere Module zur selben Zeit aktiv zu haben, kann Probleme verursachen, besonders wenn mehr als eins sich beim Looten automatisch öffnet. Es wird empfohlen, die anderen Module zu deaktivieren. Wenn du dies tun willst und nur das ausgewählte aktivieren willst, drücke den 'OK'-Button. Wenn du sicher bist, dass mehrere Konfer-Module laufen sollen, dann drücke 'Abbrechen'."
94 |
--------------------------------------------------------------------------------
/Locales/KKore-frFR.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - core library functions for KahLua addons.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
8 |
9 | Copyright 2008-2019 James Kean Johnston. All rights reserved.
10 |
11 | Licensed under the Apache License, Version 2.0 (the "License");
12 | you may not use this file except in compliance with the License.
13 | You may obtain a copy of the License at
14 |
15 | http://www.apache.org/licenses/LICENSE-2.0
16 |
17 | Unless required by applicable law or agreed to in writing, software
18 | distributed under the License is distributed on an "AS IS" BASIS,
19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | See the License for the specific language governing permissions and
21 | limitations under the License.
22 | ]]
23 |
24 | local K = LibStub:GetLibrary("KKore")
25 |
26 | if (not K) then
27 | return
28 | end
29 |
30 | local L = LibStub("AceLocale-3.0"):NewLocale("KKore", "frFR")
31 | if (not L) then
32 | return
33 | end
34 |
35 | --
36 | -- A few strings that are very commonly used that can be translated once.
37 | -- These are mainly used in user interface elements so the strings should
38 | -- be short, preferably one word.
39 | --
40 | K.OK_STR = "OK"
41 | K.CANCEL_STR = "Annuler"
42 | K.ACCEPT_STR = "Accepter"
43 | K.CLOSE_STR = "Fermer"
44 | K.OPEN_STR = "Ouvrir"
45 | K.HELP_STR = "Aide"
46 | K.YES_STR = "Oui"
47 | K.NO_STR = "Non"
48 | K.KAHLUA = "KahLua"
49 |
50 | L["CMD_KAHLUA"] = "kahlua"
51 | L["CMD_KKORE"] = "kkore"
52 | L["CMD_HELP"] = "aide"
53 | L["CMD_LIST"] = "liste"
54 | L["CMD_DEBUG"] = "debug"
55 | L["CMD_VERSION"] = "version"
56 |
57 | L["KAHLUA_VER"] = "(Version %d)"
58 | L["KAHLUA_DESC"] = "un ensemble d'améliorations de l'interface utilisateur."
59 | L["KORE_DESC"] = "Fonctionnaliés %s du coeur, telles que le debug ou les profils."
60 | L["Usage: %s/%s module [arg [arg...]]%s"] = "Utilisation : %s/%s module [arg [arg...]]%s"
61 | L[" Where module is one of the following modules:"] = " module étant l'in des modules suivants :"
62 | L["For help with any module, type %s/%s module %s%s."] = "Pour une aide sur l'un des modules, tapez %s/%s module %s%s."
63 | L["KahLua Kore usage: %s/%s command [arg [arg...]]%s"] = "Utilisation de KahLua Kore : %s/%s commande [arg [arg...]]%s."
64 | L["%s/%s %s module level%s"] = "%s/%s %s module level%s"
65 | L[" Sets the debug level for a module. 0 disables."] = " Définit le niveau de déboguage pour un module. 0 désactive."
66 | L[" The higher the number the more verbose the output."] = " Plus le nombre est grand, plus la sortie est détaillée."
67 | L[" Lists all modules registered with KahLua."] = " Liste tous les modules enregistrés avec KahLua."
68 | L["The following modules are available:"] = "Les modules suivants sont disponibles :"
69 | L["Cannot enable debugging for '%s' - no such module."] = "Impossible d'activer le déboguage pour '%s' - module inexistant."
70 | L["Debug level %d out of bounds - must be between 0 and 10."] = "Niveau de déboguage %s hors limites - doit être compris entre 0 et 10."
71 | L["Module '%s' does not exist. Use %s/%s %s%s for a list of available modules."] = "Module '%s' inexistant. Utilisez %s/%s %s%s pour une liste des modules disponibles."
72 | L["KKore extensions loaded:"] = "Extensions KKore chargées :"
73 | L["Chest"] = "Coffre"
74 |
75 | L["Not Set"] = "Non d\195\169fini"
76 | L["Tank"] = "Tank"
77 | L["Ranged DPS"] = "DPS distant"
78 | L["Melee DPS"] = "DPS CaC"
79 | L["Healer"] = "Heal"
80 | L["Spellcaster"] = "DPS magique"
81 | L["your version of %s is out of date. Please update it."] = "Votre version de %s n'est pas \195\160 jour. T\195\169l\195\169chargez-la sur le site des GDO."
82 | L["VCTITLE"] = "%s %s Version Check"
83 | L["Version"] = "Version"
84 | L["In Raid"] = "In Raid"
85 | L["Who"] = "Qui"
86 | L["Shield"] = true
87 |
88 | L["KONFER_SEL_TITLE"] = "S\195\169lectionner le module Konfer %s actif"
89 | L["KONFER_SEL_HEADER"] = "Vous avez plusieurs add-ons Konfer %s install\195\169s, et plus d'un est actif et configur\195\169 pour s'ouvrir automatiquement lors du loot d'un corps ou d'un coffre. Ceci peut provoquer des conflits, vous devez donc choisir lequel activer, tous les autres seront d\195\169sactiv\195\169s."
90 | L["KONFER_SEL_DDTITLE"] = "Choisir le module \195\160 activer"
91 | L["KONFER_ACTIVE"] = "activ\195\169"
92 | L["KONFER_SUSPENDED"] = "d\195\169sactiv\195\169"
93 | L["KONFER_SUSPEND_OTHERS"] = "Vous venez d'activer le module Konfer %s ci-dessus, mais d'autres modules Konfer sont \195\169galement activ\195\169s. Avoir plusieurs modules Konfer actifs en m\195\170me temps peut g\195\169n\195\169rer des probl\195\168mes, notamment si plus d'un est configur\195\169 pour s'ouvrir automatiquement lors d'un loot. Nous vous conseillons de d\195\169sactiver tous les autres add-ons Konfer. Pour suivre ce conseil et faire de ce module le seul actif, cliquez sur 'Ok'. Si vous \195\170tes certain de vouloir laisser plusieurs add-ons Konfer actifs, cliquez sur 'Annuler'."
94 |
--------------------------------------------------------------------------------
/Libs/AceEvent-3.0.lua:
--------------------------------------------------------------------------------
1 | --- AceEvent-3.0 provides event registration and secure dispatching.
2 | -- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
3 | -- CallbackHandler, and dispatches all game events or addon message to the registrees.
4 | --
5 | -- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
6 | -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
7 | -- and can be accessed directly, without having to explicitly call AceEvent itself.\\
8 | -- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
9 | -- make into AceEvent.
10 | -- @class file
11 | -- @name AceEvent-3.0
12 | -- @release $Id: AceEvent-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
13 | local CallbackHandler = LibStub("CallbackHandler-1.0")
14 |
15 | local MAJOR, MINOR = "AceEvent-3.0", 4
16 | local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
17 |
18 | if not AceEvent then return end
19 |
20 | -- Lua APIs
21 | local pairs = pairs
22 |
23 | AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
24 | AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
25 |
26 | -- APIs and registry for blizzard events, using CallbackHandler lib
27 | if not AceEvent.events then
28 | AceEvent.events = CallbackHandler:New(AceEvent,
29 | "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
30 | end
31 |
32 | function AceEvent.events:OnUsed(target, eventname)
33 | AceEvent.frame:RegisterEvent(eventname)
34 | end
35 |
36 | function AceEvent.events:OnUnused(target, eventname)
37 | AceEvent.frame:UnregisterEvent(eventname)
38 | end
39 |
40 |
41 | -- APIs and registry for IPC messages, using CallbackHandler lib
42 | if not AceEvent.messages then
43 | AceEvent.messages = CallbackHandler:New(AceEvent,
44 | "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
45 | )
46 | AceEvent.SendMessage = AceEvent.messages.Fire
47 | end
48 |
49 | --- embedding and embed handling
50 | local mixins = {
51 | "RegisterEvent", "UnregisterEvent",
52 | "RegisterMessage", "UnregisterMessage",
53 | "SendMessage",
54 | "UnregisterAllEvents", "UnregisterAllMessages",
55 | }
56 |
57 | --- Register for a Blizzard Event.
58 | -- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
59 | -- Any arguments to the event will be passed on after that.
60 | -- @name AceEvent:RegisterEvent
61 | -- @class function
62 | -- @paramsig event[, callback [, arg]]
63 | -- @param event The event to register for
64 | -- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
65 | -- @param arg An optional argument to pass to the callback function
66 |
67 | --- Unregister an event.
68 | -- @name AceEvent:UnregisterEvent
69 | -- @class function
70 | -- @paramsig event
71 | -- @param event The event to unregister
72 |
73 | --- Register for a custom AceEvent-internal message.
74 | -- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied)
75 | -- Any arguments to the event will be passed on after that.
76 | -- @name AceEvent:RegisterMessage
77 | -- @class function
78 | -- @paramsig message[, callback [, arg]]
79 | -- @param message The message to register for
80 | -- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
81 | -- @param arg An optional argument to pass to the callback function
82 |
83 | --- Unregister a message
84 | -- @name AceEvent:UnregisterMessage
85 | -- @class function
86 | -- @paramsig message
87 | -- @param message The message to unregister
88 |
89 | --- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
90 | -- @name AceEvent:SendMessage
91 | -- @class function
92 | -- @paramsig message, ...
93 | -- @param message The message to send
94 | -- @param ... Any arguments to the message
95 |
96 |
97 | -- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
98 | -- @param target target object to embed AceEvent in
99 | function AceEvent:Embed(target)
100 | for k, v in pairs(mixins) do
101 | target[v] = self[v]
102 | end
103 | self.embeds[target] = true
104 | return target
105 | end
106 |
107 | -- AceEvent:OnEmbedDisable( target )
108 | -- target (object) - target object that is being disabled
109 | --
110 | -- Unregister all events messages etc when the target disables.
111 | -- this method should be called by the target manually or by an addon framework
112 | function AceEvent:OnEmbedDisable(target)
113 | target:UnregisterAllEvents()
114 | target:UnregisterAllMessages()
115 | end
116 |
117 | -- Script to fire blizzard events into the event listeners
118 | local events = AceEvent.events
119 | AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
120 | events:Fire(event, ...)
121 | end)
122 |
123 | --- Finally: upgrade our old embeds
124 | for target, v in pairs(AceEvent.embeds) do
125 | AceEvent:Embed(target)
126 | end
127 |
--------------------------------------------------------------------------------
/Libs/AceLocale-3.0.lua:
--------------------------------------------------------------------------------
1 | --- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings.
2 | -- @class file
3 | -- @name AceLocale-3.0
4 | -- @release $Id: AceLocale-3.0.lua 1035 2011-07-09 03:20:13Z kaelten $
5 | local MAJOR,MINOR = "AceLocale-3.0", 6
6 |
7 | local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
8 |
9 | if not AceLocale then return end -- no upgrade needed
10 |
11 | -- Lua APIs
12 | local assert, tostring, error = assert, tostring, error
13 | local getmetatable, setmetatable, rawset, rawget = getmetatable, setmetatable, rawset, rawget
14 |
15 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
16 | -- List them here for Mikk's FindGlobals script
17 | -- GLOBALS: GAME_LOCALE, geterrorhandler
18 |
19 | local gameLocale = GetLocale()
20 | if gameLocale == "enGB" then
21 | gameLocale = "enUS"
22 | end
23 |
24 | AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref
25 | AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName"
26 |
27 | -- This metatable is used on all tables returned from GetLocale
28 | local readmeta = {
29 | __index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key
30 | rawset(self, key, key) -- only need to see the warning once, really
31 | geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'")
32 | return key
33 | end
34 | }
35 |
36 | -- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys
37 | local readmetasilent = {
38 | __index = function(self, key) -- requesting totally unknown entries: return key
39 | rawset(self, key, key) -- only need to invoke this function once
40 | return key
41 | end
42 | }
43 |
44 | -- Remember the locale table being registered right now (it gets set by :NewLocale())
45 | -- NOTE: Do never try to register 2 locale tables at once and mix their definition.
46 | local registering
47 |
48 | -- local assert false function
49 | local assertfalse = function() assert(false) end
50 |
51 | -- This metatable proxy is used when registering nondefault locales
52 | local writeproxy = setmetatable({}, {
53 | __newindex = function(self, key, value)
54 | rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string
55 | end,
56 | __index = assertfalse
57 | })
58 |
59 | -- This metatable proxy is used when registering the default locale.
60 | -- It refuses to overwrite existing values
61 | -- Reason 1: Allows loading locales in any order
62 | -- Reason 2: If 2 modules have the same string, but only the first one to be
63 | -- loaded has a translation for the current locale, the translation
64 | -- doesn't get overwritten.
65 | --
66 | local writedefaultproxy = setmetatable({}, {
67 | __newindex = function(self, key, value)
68 | if not rawget(registering, key) then
69 | rawset(registering, key, value == true and key or value)
70 | end
71 | end,
72 | __index = assertfalse
73 | })
74 |
75 | --- Register a new locale (or extend an existing one) for the specified application.
76 | -- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players
77 | -- game locale.
78 | -- @paramsig application, locale[, isDefault[, silent]]
79 | -- @param application Unique name of addon / module
80 | -- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc.
81 | -- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS)
82 | -- @param silent If true, the locale will not issue warnings for missing keys. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used).
83 | -- @usage
84 | -- -- enUS.lua
85 | -- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true)
86 | -- L["string1"] = true
87 | --
88 | -- -- deDE.lua
89 | -- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE")
90 | -- if not L then return end
91 | -- L["string1"] = "Zeichenkette1"
92 | -- @return Locale Table to add localizations to, or nil if the current locale is not required.
93 | function AceLocale:NewLocale(application, locale, isDefault, silent)
94 |
95 | -- GAME_LOCALE allows translators to test translations of addons without having that wow client installed
96 | local gameLocale = GAME_LOCALE or gameLocale
97 |
98 | local app = AceLocale.apps[application]
99 |
100 | if silent and app and getmetatable(app) ~= readmetasilent then
101 | geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered")
102 | end
103 |
104 | if not app then
105 | if silent=="raw" then
106 | app = {}
107 | else
108 | app = setmetatable({}, silent and readmetasilent or readmeta)
109 | end
110 | AceLocale.apps[application] = app
111 | AceLocale.appnames[app] = application
112 | end
113 |
114 | if locale ~= gameLocale and not isDefault then
115 | return -- nop, we don't need these translations
116 | end
117 |
118 | registering = app -- remember globally for writeproxy and writedefaultproxy
119 |
120 | if isDefault then
121 | return writedefaultproxy
122 | end
123 |
124 | return writeproxy
125 | end
126 |
127 | --- Returns localizations for the current locale (or default locale if translations are missing).
128 | -- Errors if nothing is registered (spank developer, not just a missing translation)
129 | -- @param application Unique name of addon / module
130 | -- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional)
131 | -- @return The locale table for the current language.
132 | function AceLocale:GetLocale(application, silent)
133 | if not silent and not AceLocale.apps[application] then
134 | error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2)
135 | end
136 | return AceLocale.apps[application]
137 | end
138 |
--------------------------------------------------------------------------------
/Libs/CallbackHandler-1.0.lua:
--------------------------------------------------------------------------------
1 | --[[ $Id: CallbackHandler-1.0.lua 1186 2018-07-21 14:19:18Z nevcairiel $ ]]
2 | local MAJOR, MINOR = "CallbackHandler-1.0", 7
3 | local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
4 |
5 | if not CallbackHandler then return end -- No upgrade needed
6 |
7 | local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
8 |
9 | -- Lua APIs
10 | local tconcat = table.concat
11 | local assert, error, loadstring = assert, error, loadstring
12 | local setmetatable, rawset, rawget = setmetatable, rawset, rawget
13 | local next, select, pairs, type, tostring = next, select, pairs, type, tostring
14 |
15 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
16 | -- List them here for Mikk's FindGlobals script
17 | -- GLOBALS: geterrorhandler
18 |
19 | local xpcall = xpcall
20 |
21 | local function errorhandler(err)
22 | return geterrorhandler()(err)
23 | end
24 |
25 | local function Dispatch(handlers, ...)
26 | local index, method = next(handlers)
27 | if not method then return end
28 | repeat
29 | xpcall(method, errorhandler, ...)
30 | index, method = next(handlers, index)
31 | until not method
32 | end
33 |
34 | --------------------------------------------------------------------------
35 | -- CallbackHandler:New
36 | --
37 | -- target - target object to embed public APIs in
38 | -- RegisterName - name of the callback registration API, default "RegisterCallback"
39 | -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
40 | -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
41 |
42 | function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
43 |
44 | RegisterName = RegisterName or "RegisterCallback"
45 | UnregisterName = UnregisterName or "UnregisterCallback"
46 | if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
47 | UnregisterAllName = "UnregisterAllCallbacks"
48 | end
49 |
50 | -- we declare all objects and exported APIs inside this closure to quickly gain access
51 | -- to e.g. function names, the "target" parameter, etc
52 |
53 |
54 | -- Create the registry object
55 | local events = setmetatable({}, meta)
56 | local registry = { recurse=0, events=events }
57 |
58 | -- registry:Fire() - fires the given event/message into the registry
59 | function registry:Fire(eventname, ...)
60 | if not rawget(events, eventname) or not next(events[eventname]) then return end
61 | local oldrecurse = registry.recurse
62 | registry.recurse = oldrecurse + 1
63 |
64 | Dispatch(events[eventname], eventname, ...)
65 |
66 | registry.recurse = oldrecurse
67 |
68 | if registry.insertQueue and oldrecurse==0 then
69 | -- Something in one of our callbacks wanted to register more callbacks; they got queued
70 | for eventname,callbacks in pairs(registry.insertQueue) do
71 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
72 | for self,func in pairs(callbacks) do
73 | events[eventname][self] = func
74 | -- fire OnUsed callback?
75 | if first and registry.OnUsed then
76 | registry.OnUsed(registry, target, eventname)
77 | first = nil
78 | end
79 | end
80 | end
81 | registry.insertQueue = nil
82 | end
83 | end
84 |
85 | -- Registration of a callback, handles:
86 | -- self["method"], leads to self["method"](self, ...)
87 | -- self with function ref, leads to functionref(...)
88 | -- "addonId" (instead of self) with function ref, leads to functionref(...)
89 | -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
90 | target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
91 | if type(eventname) ~= "string" then
92 | error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
93 | end
94 |
95 | method = method or eventname
96 |
97 | local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
98 |
99 | if type(method) ~= "string" and type(method) ~= "function" then
100 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
101 | end
102 |
103 | local regfunc
104 |
105 | if type(method) == "string" then
106 | -- self["method"] calling style
107 | if type(self) ~= "table" then
108 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
109 | elseif self==target then
110 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
111 | elseif type(self[method]) ~= "function" then
112 | error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
113 | end
114 |
115 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
116 | local arg=select(1,...)
117 | regfunc = function(...) self[method](self,arg,...) end
118 | else
119 | regfunc = function(...) self[method](self,...) end
120 | end
121 | else
122 | -- function ref with self=object or self="addonId" or self=thread
123 | if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
124 | error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
125 | end
126 |
127 | if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
128 | local arg=select(1,...)
129 | regfunc = function(...) method(arg,...) end
130 | else
131 | regfunc = method
132 | end
133 | end
134 |
135 |
136 | if events[eventname][self] or registry.recurse<1 then
137 | -- if registry.recurse<1 then
138 | -- we're overwriting an existing entry, or not currently recursing. just set it.
139 | events[eventname][self] = regfunc
140 | -- fire OnUsed callback?
141 | if registry.OnUsed and first then
142 | registry.OnUsed(registry, target, eventname)
143 | end
144 | else
145 | -- we're currently processing a callback in this registry, so delay the registration of this new entry!
146 | -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
147 | registry.insertQueue = registry.insertQueue or setmetatable({},meta)
148 | registry.insertQueue[eventname][self] = regfunc
149 | end
150 | end
151 |
152 | -- Unregister a callback
153 | target[UnregisterName] = function(self, eventname)
154 | if not self or self==target then
155 | error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
156 | end
157 | if type(eventname) ~= "string" then
158 | error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
159 | end
160 | if rawget(events, eventname) and events[eventname][self] then
161 | events[eventname][self] = nil
162 | -- Fire OnUnused callback?
163 | if registry.OnUnused and not next(events[eventname]) then
164 | registry.OnUnused(registry, target, eventname)
165 | end
166 | end
167 | if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
168 | registry.insertQueue[eventname][self] = nil
169 | end
170 | end
171 |
172 | -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
173 | if UnregisterAllName then
174 | target[UnregisterAllName] = function(...)
175 | if select("#",...)<1 then
176 | error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
177 | end
178 | if select("#",...)==1 and ...==target then
179 | error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
180 | end
181 |
182 |
183 | for i=1,select("#",...) do
184 | local self = select(i,...)
185 | if registry.insertQueue then
186 | for eventname, callbacks in pairs(registry.insertQueue) do
187 | if callbacks[self] then
188 | callbacks[self] = nil
189 | end
190 | end
191 | end
192 | for eventname, callbacks in pairs(events) do
193 | if callbacks[self] then
194 | callbacks[self] = nil
195 | -- Fire OnUnused callback?
196 | if registry.OnUnused and not next(callbacks) then
197 | registry.OnUnused(registry, target, eventname)
198 | end
199 | end
200 | end
201 | end
202 | end
203 | end
204 |
205 | return registry
206 | end
207 |
208 |
209 | -- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
210 | -- try to upgrade old implicit embeds since the system is selfcontained and
211 | -- relies on closures to work.
212 |
213 |
--------------------------------------------------------------------------------
/Libs/LibDeformat-3.0.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Name: LibDeformat-3.0
3 | Author(s): ckknight (ckknight@gmail.com)
4 | Website: http://www.wowace.com/projects/libdeformat-3-0/
5 | Description: A library to convert a post-formatted string back to its original arguments given its format string.
6 | License: MIT
7 | ]]
8 |
9 | local LibDeformat = LibStub:NewLibrary("LibDeformat-3.0", 1)
10 |
11 | if not LibDeformat then
12 | return
13 | end
14 |
15 | -- this function does nothing and returns nothing
16 | local function do_nothing()
17 | end
18 |
19 | -- a dictionary of format to match entity
20 | local FORMAT_SEQUENCES = {
21 | ["s"] = ".+",
22 | ["c"] = ".",
23 | ["%d*d"] = "%%-?%%d+",
24 | ["[fg]"] = "%%-?%%d+%%.?%%d*",
25 | ["%%%.%d[fg]"] = "%%-?%%d+%%.?%%d*",
26 | }
27 |
28 | -- a set of format sequences that are string-based, i.e. not numbers.
29 | local STRING_BASED_SEQUENCES = {
30 | ["s"] = true,
31 | ["c"] = true,
32 | }
33 |
34 | local cache = setmetatable({}, {__mode='k'})
35 | -- generate the deformat function for the pattern, or fetch from the cache.
36 | local function get_deformat_function(pattern)
37 | local func = cache[pattern]
38 | if func then
39 | return func
40 | end
41 |
42 | -- escape the pattern, so that string.match can use it properly
43 | local unpattern = '^' .. pattern:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") .. '$'
44 |
45 | -- a dictionary of index-to-boolean representing whether the index is a number rather than a string.
46 | local number_indexes = {}
47 |
48 | -- (if the pattern is a numbered format,) a dictionary of index-to-real index.
49 | local index_translation = nil
50 |
51 | -- the highest found index, also the number of indexes found.
52 | local highest_index
53 | if not pattern:find("%%1%$") then
54 | -- not a numbered format
55 |
56 | local i = 0
57 | while true do
58 | i = i + 1
59 | local first_index
60 | local first_sequence
61 | for sequence in pairs(FORMAT_SEQUENCES) do
62 | local index = unpattern:find("%%%%" .. sequence)
63 | if index and (not first_index or index < first_index) then
64 | first_index = index
65 | first_sequence = sequence
66 | end
67 | end
68 | if not first_index then
69 | break
70 | end
71 | unpattern = unpattern:gsub("%%%%" .. first_sequence, "(" .. FORMAT_SEQUENCES[first_sequence] .. ")", 1)
72 | number_indexes[i] = not STRING_BASED_SEQUENCES[first_sequence]
73 | end
74 |
75 | highest_index = i - 1
76 | else
77 | -- a numbered format
78 |
79 | local i = 0
80 | while true do
81 | i = i + 1
82 | local found_sequence
83 | for sequence in pairs(FORMAT_SEQUENCES) do
84 | if unpattern:find("%%%%" .. i .. "%%%$" .. sequence) then
85 | found_sequence = sequence
86 | break
87 | end
88 | end
89 | if not found_sequence then
90 | break
91 | end
92 | unpattern = unpattern:gsub("%%%%" .. i .. "%%%$" .. found_sequence, "(" .. FORMAT_SEQUENCES[found_sequence] .. ")", 1)
93 | number_indexes[i] = not STRING_BASED_SEQUENCES[found_sequence]
94 | end
95 | highest_index = i - 1
96 |
97 | i = 0
98 | index_translation = {}
99 | pattern:gsub("%%(%d)%$", function(w)
100 | i = i + 1
101 | index_translation[i] = tonumber(w)
102 | end)
103 | end
104 |
105 | if highest_index == 0 then
106 | cache[pattern] = do_nothing
107 | else
108 | --[=[
109 | -- resultant function looks something like this:
110 | local unpattern = ...
111 | return function(text)
112 | local a1, a2 = text:match(unpattern)
113 | if not a1 then
114 | return nil, nil
115 | end
116 | return a1+0, a2
117 | end
118 |
119 | -- or if it were a numbered pattern,
120 | local unpattern = ...
121 | return function(text)
122 | local a2, a1 = text:match(unpattern)
123 | if not a1 then
124 | return nil, nil
125 | end
126 | return a1+0, a2
127 | end
128 | ]=]
129 |
130 | local t = {}
131 | t[#t+1] = [=[
132 | return function(text)
133 | local ]=]
134 |
135 | for i = 1, highest_index do
136 | if i ~= 1 then
137 | t[#t+1] = ", "
138 | end
139 | t[#t+1] = "a"
140 | if not index_translation then
141 | t[#t+1] = i
142 | else
143 | t[#t+1] = index_translation[i]
144 | end
145 | end
146 |
147 | t[#t+1] = [=[ = text:match(]=]
148 | t[#t+1] = ("%q"):format(unpattern)
149 | t[#t+1] = [=[)
150 | if not a1 then
151 | return ]=]
152 |
153 | for i = 1, highest_index do
154 | if i ~= 1 then
155 | t[#t+1] = ", "
156 | end
157 | t[#t+1] = "nil"
158 | end
159 |
160 | t[#t+1] = "\n"
161 | t[#t+1] = [=[
162 | end
163 | ]=]
164 |
165 | t[#t+1] = "return "
166 | for i = 1, highest_index do
167 | if i ~= 1 then
168 | t[#t+1] = ", "
169 | end
170 | t[#t+1] = "a"
171 | t[#t+1] = i
172 | if number_indexes[i] then
173 | t[#t+1] = "+0"
174 | end
175 | end
176 | t[#t+1] = "\n"
177 | t[#t+1] = [=[
178 | end
179 | ]=]
180 |
181 | t = table.concat(t, "")
182 |
183 | -- print(t)
184 |
185 | cache[pattern] = assert(loadstring(t))()
186 | end
187 |
188 | return cache[pattern]
189 | end
190 |
191 | --- Return the arguments of the given format string as found in the text.
192 | -- @param text The resultant formatted text.
193 | -- @param pattern The pattern used to create said text.
194 | -- @return a tuple of values, either strings or numbers, based on the pattern.
195 | -- @usage LibDeformat.Deformat("Hello, friend", "Hello, %s") == "friend"
196 | -- @usage LibDeformat.Deformat("Hello, friend", "Hello, %1$s") == "friend"
197 | -- @usage LibDeformat.Deformat("Cost: $100", "Cost: $%d") == 100
198 | -- @usage LibDeformat.Deformat("Cost: $100", "Cost: $%1$d") == 100
199 | -- @usage LibDeformat.Deformat("Alpha, Bravo", "%s, %s") => "Alpha", "Bravo"
200 | -- @usage LibDeformat.Deformat("Alpha, Bravo", "%1$s, %2$s") => "Alpha", "Bravo"
201 | -- @usage LibDeformat.Deformat("Alpha, Bravo", "%2$s, %1$s") => "Bravo", "Alpha"
202 | -- @usage LibDeformat.Deformat("Hello, friend", "Cost: $%d") == nil
203 | -- @usage LibDeformat("Hello, friend", "Hello, %s") == "friend"
204 | function LibDeformat.Deformat(text, pattern)
205 | if type(text) ~= "string" then
206 | error(("Argument #1 to `Deformat' must be a string, got %s (%s)."):format(type(text), text), 2)
207 | elseif type(pattern) ~= "string" then
208 | error(("Argument #2 to `Deformat' must be a string, got %s (%s)."):format(type(pattern), pattern), 2)
209 | end
210 |
211 | return get_deformat_function(pattern)(text)
212 | end
213 |
214 | --[===[@debug@
215 | function LibDeformat.Test()
216 | local function tuple(success, ...)
217 | if success then
218 | return true, { n = select('#', ...), ... }
219 | else
220 | return false, ...
221 | end
222 | end
223 |
224 | local function check(text, pattern, ...)
225 | local success, results = tuple(pcall(LibDeformat.Deformat, text, pattern))
226 | if not success then
227 | return false, results
228 | end
229 |
230 | if select('#', ...) ~= results.n then
231 | return false, ("Differing number of return values. Expected: %d. Actual: %d."):format(select('#', ...), results.n)
232 | end
233 |
234 | for i = 1, results.n do
235 | local expected = select(i, ...)
236 | local actual = results[i]
237 | if type(expected) ~= type(actual) then
238 | return false, ("Return #%d differs by type. Expected: %s (%s). Actual: %s (%s)"):format(type(expected), expected, type(actual), actual)
239 | elseif expected ~= actual then
240 | return false, ("Return #%d differs. Expected: %s. Actual: %s"):format(expected, actual)
241 | end
242 | end
243 |
244 | return true
245 | end
246 |
247 | local function test(text, pattern, ...)
248 | local success, problem = check(text, pattern, ...)
249 | if not success then
250 | print(("Problem with (%q, %q): %s"):format(text, pattern, problem or ""))
251 | end
252 | end
253 |
254 | test("Hello, friend", "Hello, %s", "friend")
255 | test("Hello, friend", "Hello, %1$s", "friend")
256 | test("Cost: $100", "Cost: $%d", 100)
257 | test("Cost: $100", "Cost: $%1$d", 100)
258 | test("Alpha, Bravo", "%s, %s", "Alpha", "Bravo")
259 | test("Alpha, Bravo", "%1$s, %2$s", "Alpha", "Bravo")
260 | test("Alpha, Bravo", "%2$s, %1$s", "Bravo", "Alpha")
261 | test("Alpha, Bravo, Charlie, Delta, Echo", "%s, %s, %s, %s, %s", "Alpha", "Bravo", "Charlie", "Delta", "Echo")
262 | test("Alpha, Bravo, Charlie, Delta, Echo", "%1$s, %2$s, %3$s, %4$s, %5$s", "Alpha", "Bravo", "Charlie", "Delta", "Echo")
263 | test("Alpha, Bravo, Charlie, Delta, Echo", "%5$s, %4$s, %3$s, %2$s, %1$s", "Echo", "Delta", "Charlie", "Bravo", "Alpha")
264 | test("Alpha, Bravo, Charlie, Delta, Echo", "%2$s, %3$s, %4$s, %5$s, %1$s", "Echo", "Alpha", "Bravo", "Charlie", "Delta")
265 | test("Alpha, Bravo, Charlie, Delta, Echo", "%3$s, %4$s, %5$s, %1$s, %2$s", "Delta", "Echo", "Alpha", "Bravo", "Charlie")
266 | test("Alpha, Bravo, Charlie, Delta", "%s, %s, %s, %s, %s", nil, nil, nil, nil, nil)
267 | test("Hello, friend", "Cost: $%d", nil)
268 | print("LibDeformat-3.0: Tests completed.")
269 | end
270 | --@end-debug@]===]
271 |
272 | setmetatable(LibDeformat, { __call = function(self, ...) return self.Deformat(...) end })
--------------------------------------------------------------------------------
/Libs/AceBucket-3.0.lua:
--------------------------------------------------------------------------------
1 | --- A bucket to catch events in. **AceBucket-3.0** provides throttling of events that fire in bursts and
2 | -- your addon only needs to know about the full burst.
3 | --
4 | -- This Bucket implementation works as follows:\\
5 | -- Initially, no schedule is running, and its waiting for the first event to happen.\\
6 | -- The first event will start the bucket, and get the scheduler running, which will collect all
7 | -- events in the given interval. When that interval is reached, the bucket is pushed to the
8 | -- callback and a new schedule is started. When a bucket is empty after its interval, the scheduler is
9 | -- stopped, and the bucket is only listening for the next event to happen, basically back in its initial state.
10 | --
11 | -- In addition, the buckets collect information about the "arg1" argument of the events that fire, and pass those as a
12 | -- table to your callback. This functionality was mostly designed for the UNIT_* events.\\
13 | -- The table will have the different values of "arg1" as keys, and the number of occurances as their value, e.g.\\
14 | -- { ["player"] = 2, ["target"] = 1, ["party1"] = 1 }
15 | --
16 | -- **AceBucket-3.0** can be embeded into your addon, either explicitly by calling AceBucket:Embed(MyAddon) or by
17 | -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
18 | -- and can be accessed directly, without having to explicitly call AceBucket itself.\\
19 | -- It is recommended to embed AceBucket, otherwise you'll have to specify a custom `self` on all calls you
20 | -- make into AceBucket.
21 | -- @usage
22 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("BucketExample", "AceBucket-3.0")
23 | --
24 | -- function MyAddon:OnEnable()
25 | -- -- Register a bucket that listens to all the HP related events,
26 | -- -- and fires once per second
27 | -- self:RegisterBucketEvent({"UNIT_HEALTH", "UNIT_MAXHEALTH"}, 1, "UpdateHealth")
28 | -- end
29 | --
30 | -- function MyAddon:UpdateHealth(units)
31 | -- if units.player then
32 | -- print("Your HP changed!")
33 | -- end
34 | -- end
35 | -- @class file
36 | -- @name AceBucket-3.0.lua
37 | -- @release $Id: AceBucket-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
38 |
39 | local MAJOR, MINOR = "AceBucket-3.0", 4
40 | local AceBucket, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
41 |
42 | if not AceBucket then return end -- No Upgrade needed
43 |
44 | AceBucket.buckets = AceBucket.buckets or {}
45 | AceBucket.embeds = AceBucket.embeds or {}
46 |
47 | -- the libraries will be lazyly bound later, to avoid errors due to loading order issues
48 | local AceEvent, AceTimer
49 |
50 | -- Lua APIs
51 | local tconcat = table.concat
52 | local type, next, pairs, select = type, next, pairs, select
53 | local tonumber, tostring, rawset = tonumber, tostring, rawset
54 | local assert, loadstring, error = assert, loadstring, error
55 |
56 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
57 | -- List them here for Mikk's FindGlobals script
58 | -- GLOBALS: LibStub, geterrorhandler
59 |
60 | local bucketCache = setmetatable({}, {__mode='k'})
61 |
62 | --[[
63 | xpcall safecall implementation
64 | ]]
65 | local xpcall = xpcall
66 |
67 | local function errorhandler(err)
68 | return geterrorhandler()(err)
69 | end
70 |
71 | local function safecall(func, ...)
72 | if func then
73 | return xpcall(func, errorhandler, ...)
74 | end
75 | end
76 |
77 | -- FireBucket ( bucket )
78 | --
79 | -- send the bucket to the callback function and schedule the next FireBucket in interval seconds
80 | local function FireBucket(bucket)
81 | local received = bucket.received
82 |
83 | -- we dont want to fire empty buckets
84 | if next(received) ~= nil then
85 | local callback = bucket.callback
86 | if type(callback) == "string" then
87 | safecall(bucket.object[callback], bucket.object, received)
88 | else
89 | safecall(callback, received)
90 | end
91 |
92 | for k in pairs(received) do
93 | received[k] = nil
94 | end
95 |
96 | -- if the bucket was not empty, schedule another FireBucket in interval seconds
97 | bucket.timer = AceTimer.ScheduleTimer(bucket, FireBucket, bucket.interval, bucket)
98 | else -- if it was empty, clear the timer and wait for the next event
99 | bucket.timer = nil
100 | end
101 | end
102 |
103 | -- BucketHandler ( event, arg1 )
104 | --
105 | -- callback func for AceEvent
106 | -- stores arg1 in the received table, and schedules the bucket if necessary
107 | local function BucketHandler(self, event, arg1)
108 | if arg1 == nil then
109 | arg1 = "nil"
110 | end
111 |
112 | self.received[arg1] = (self.received[arg1] or 0) + 1
113 |
114 | -- if we are not scheduled yet, start a timer on the interval for our bucket to be cleared
115 | if not self.timer then
116 | self.timer = AceTimer.ScheduleTimer(self, FireBucket, self.interval, self)
117 | end
118 | end
119 |
120 | -- RegisterBucket( event, interval, callback, isMessage )
121 | --
122 | -- event(string or table) - the event, or a table with the events, that this bucket listens to
123 | -- interval(int) - time between bucket fireings
124 | -- callback(func or string) - function pointer, or method name of the object, that gets called when the bucket is cleared
125 | -- isMessage(boolean) - register AceEvent Messages instead of game events
126 | local function RegisterBucket(self, event, interval, callback, isMessage)
127 | -- try to fetch the librarys
128 | if not AceEvent or not AceTimer then
129 | AceEvent = LibStub:GetLibrary("AceEvent-3.0", true)
130 | AceTimer = LibStub:GetLibrary("AceTimer-3.0", true)
131 | if not AceEvent or not AceTimer then
132 | error(MAJOR .. " requires AceEvent-3.0 and AceTimer-3.0", 3)
133 | end
134 | end
135 |
136 | if type(event) ~= "string" and type(event) ~= "table" then error("Usage: RegisterBucket(event, interval, callback): 'event' - string or table expected.", 3) end
137 | if not callback then
138 | if type(event) == "string" then
139 | callback = event
140 | else
141 | error("Usage: RegisterBucket(event, interval, callback): cannot omit callback when event is not a string.", 3)
142 | end
143 | end
144 | if not tonumber(interval) then error("Usage: RegisterBucket(event, interval, callback): 'interval' - number expected.", 3) end
145 | if type(callback) ~= "string" and type(callback) ~= "function" then error("Usage: RegisterBucket(event, interval, callback): 'callback' - string or function or nil expected.", 3) end
146 | if type(callback) == "string" and type(self[callback]) ~= "function" then error("Usage: RegisterBucket(event, interval, callback): 'callback' - method not found on target object.", 3) end
147 |
148 | local bucket = next(bucketCache)
149 | if bucket then
150 | bucketCache[bucket] = nil
151 | else
152 | bucket = { handler = BucketHandler, received = {} }
153 | end
154 | bucket.object, bucket.callback, bucket.interval = self, callback, tonumber(interval)
155 |
156 | local regFunc = isMessage and AceEvent.RegisterMessage or AceEvent.RegisterEvent
157 |
158 | if type(event) == "table" then
159 | for _,e in pairs(event) do
160 | regFunc(bucket, e, "handler")
161 | end
162 | else
163 | regFunc(bucket, event, "handler")
164 | end
165 |
166 | local handle = tostring(bucket)
167 | AceBucket.buckets[handle] = bucket
168 |
169 | return handle
170 | end
171 |
172 | --- Register a Bucket for an event (or a set of events)
173 | -- @param event The event to listen for, or a table of events.
174 | -- @param interval The Bucket interval (burst interval)
175 | -- @param callback The callback function, either as a function reference, or a string pointing to a method of the addon object.
176 | -- @return The handle of the bucket (for unregistering)
177 | -- @usage
178 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceBucket-3.0")
179 | -- MyAddon:RegisterBucketEvent("BAG_UPDATE", 0.2, "UpdateBags")
180 | --
181 | -- function MyAddon:UpdateBags()
182 | -- -- do stuff
183 | -- end
184 | function AceBucket:RegisterBucketEvent(event, interval, callback)
185 | return RegisterBucket(self, event, interval, callback, false)
186 | end
187 |
188 | --- Register a Bucket for an AceEvent-3.0 addon message (or a set of messages)
189 | -- @param message The message to listen for, or a table of messages.
190 | -- @param interval The Bucket interval (burst interval)
191 | -- @param callback The callback function, either as a function reference, or a string pointing to a method of the addon object.
192 | -- @return The handle of the bucket (for unregistering)
193 | -- @usage
194 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceBucket-3.0")
195 | -- MyAddon:RegisterBucketEvent("SomeAddon_InformationMessage", 0.2, "ProcessData")
196 | --
197 | -- function MyAddon:ProcessData()
198 | -- -- do stuff
199 | -- end
200 | function AceBucket:RegisterBucketMessage(message, interval, callback)
201 | return RegisterBucket(self, message, interval, callback, true)
202 | end
203 |
204 | --- Unregister any events and messages from the bucket and clear any remaining data.
205 | -- @param handle The handle of the bucket as returned by RegisterBucket*
206 | function AceBucket:UnregisterBucket(handle)
207 | local bucket = AceBucket.buckets[handle]
208 | if bucket then
209 | AceEvent.UnregisterAllEvents(bucket)
210 | AceEvent.UnregisterAllMessages(bucket)
211 |
212 | -- clear any remaining data in the bucket
213 | for k in pairs(bucket.received) do
214 | bucket.received[k] = nil
215 | end
216 |
217 | if bucket.timer then
218 | AceTimer.CancelTimer(bucket, bucket.timer)
219 | bucket.timer = nil
220 | end
221 |
222 | AceBucket.buckets[handle] = nil
223 | -- store our bucket in the cache
224 | bucketCache[bucket] = true
225 | end
226 | end
227 |
228 | --- Unregister all buckets of the current addon object (or custom "self").
229 | function AceBucket:UnregisterAllBuckets()
230 | -- hmm can we do this more efficient? (it is not done often so shouldn't matter much)
231 | for handle, bucket in pairs(AceBucket.buckets) do
232 | if bucket.object == self then
233 | AceBucket.UnregisterBucket(self, handle)
234 | end
235 | end
236 | end
237 |
238 |
239 |
240 | -- embedding and embed handling
241 | local mixins = {
242 | "RegisterBucketEvent",
243 | "RegisterBucketMessage",
244 | "UnregisterBucket",
245 | "UnregisterAllBuckets",
246 | }
247 |
248 | -- Embeds AceBucket into the target object making the functions from the mixins list available on target:..
249 | -- @param target target object to embed AceBucket in
250 | function AceBucket:Embed( target )
251 | for _, v in pairs( mixins ) do
252 | target[v] = self[v]
253 | end
254 | self.embeds[target] = true
255 | return target
256 | end
257 |
258 | function AceBucket:OnEmbedDisable( target )
259 | target:UnregisterAllBuckets()
260 | end
261 |
262 | for addon in pairs(AceBucket.embeds) do
263 | AceBucket:Embed(addon)
264 | end
265 |
--------------------------------------------------------------------------------
/KKoreDialogs.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - useful simple dialogs
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
8 |
9 | Copyright 2008-2021 James Kean Johnston. All rights reserved.
10 |
11 | Licensed under the Apache License, Version 2.0 (the "License");
12 | you may not use this file except in compliance with the License.
13 | You may obtain a copy of the License at
14 |
15 | http://www.apache.org/licenses/LICENSE-2.0
16 |
17 | Unless required by applicable law or agreed to in writing, software
18 | distributed under the License is distributed on an "AS IS" BASIS,
19 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 | See the License for the specific language governing permissions and
21 | limitations under the License.
22 | ]]
23 |
24 | local K, KM = LibStub:GetLibrary("KKore")
25 | assert(K, "KKoreKonfer requires KKore")
26 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKore r4 or later")
27 |
28 | local KUI, KM = LibStub:GetLibrary("KKoreUI")
29 | assert(KUI, "KKoreKonfer requires KKoreUI")
30 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKoreUI r4 or later")
31 |
32 | local L = LibStub("AceLocale-3.0"):GetLocale("KKore")
33 |
34 | -- Local aliases for global or LUA library functions
35 | local _G = _G
36 | local tinsert = table.insert
37 | local tremove = table.remove
38 | local setmetatable = setmetatable
39 | local tconcat = table.concat
40 | local tsort = table.sort
41 | local tostring = tostring
42 | local GetTime = GetTime
43 | local min = math.min
44 | local max = math.max
45 | local strfmt = string.format
46 | local strsub = string.sub
47 | local strlen = string.len
48 | local strfind = string.find
49 | local xpcall, pcall = xpcall, pcall
50 | local pairs, next, type = pairs, next, type
51 | local select, assert, loadstring = select, assert, loadstring
52 | local printf = K.printf
53 | local MakeFrame= KUI.MakeFrame
54 |
55 | local ucolor = K.ucolor
56 | local ecolor = K.ecolor
57 | local icolor = K.icolor
58 |
59 | function K.ConfirmationDialog(kmod, ttxt, msg, val, func, farg, isshown, height, option, xtras)
60 | local height = height or 240
61 | local confirmdlg = kmod.confirmdialog
62 |
63 | if (not confirmdlg) then
64 | local arg = {
65 | x = "CENTER", y = "MIDDLE",
66 | name = "KKoreConfirmDialog",
67 | title = "",
68 | border = true,
69 | width = 450,
70 | height = height,
71 | canmove = true,
72 | canresize = false,
73 | escclose = false,
74 | blackbg = true,
75 | okbutton = { text = K.OK_STR },
76 | cancelbutton = { text = K.CANCEL_STR },
77 | }
78 | if (xtras and type(xtras) == "table") then
79 | for k,v in pairs(xtras) do
80 | arg[k] = v
81 | end
82 | end
83 |
84 | local ret = KUI:CreateDialogFrame(arg)
85 |
86 | arg = {
87 | x = "CENTER", y = -4, height = 24, autosize = false,
88 | font = "GameFontNormal", text = "", width = 375,
89 | color = {r = 1, g = 1, b = 1, a = 1 }, border = true,
90 | justifyh = "CENTER",
91 | }
92 | ret.str1 = KUI:CreateStringLabel(arg, ret)
93 |
94 | arg = {
95 | x = 8, y = -35, width = 410, height = height - 95 - (option and 24 or 0),
96 | autosize = false,
97 | color = {r = 1, g = 0, b = 0, a = 1 }, text = "",
98 | font = "GameFontNormal", justifyv = "TOP",
99 | }
100 | ret.str2 = KUI:CreateStringLabel(arg, ret)
101 |
102 | arg = {
103 | x = "CENTER", y = -height + 85, label = { text = ""},
104 | }
105 | ret.opt = KUI:CreateCheckBox(arg, ret)
106 | ret.optval = false
107 | ret.opt:Catch("OnValueChanged", function(this, evt, val)
108 | ret.optval = val
109 | end)
110 |
111 | ret.OnCancel = function(this)
112 | local tcd = this.kmod.confirmdialog
113 | tcd:Hide()
114 | if (tcd.isshown) then
115 | tcd.kmod.mainwin:Show()
116 | end
117 | tcd.isshown = nil
118 | end
119 |
120 | ret.OnAccept = function(this)
121 | local tcd = this.kmod.confirmdialog
122 | tcd:Hide()
123 | if (tcd.isshown) then
124 | tcd.kmod.mainwin:Show()
125 | end
126 | tcd.runfunction(this.kmod, tcd.arg, tcd.optval)
127 | tcd.isshown = nil
128 | end
129 |
130 | ret.kmod = kmod
131 | kmod.confirmdialog = ret
132 | confirmdlg = kmod.confirmdialog
133 | end
134 |
135 | confirmdlg:SetHeight(height)
136 | confirmdlg.str2:SetHeight(height - 95 - (option and 24 or 0))
137 | confirmdlg.opt:SetPoint("TOP", confirmdlg, "BOTTOM", 0, 56)
138 | if (option) then
139 | confirmdlg.opt:SetText(option)
140 | confirmdlg.opt:Show()
141 | confirmdlg.optval = false
142 | confirmdlg.opt:SetChecked(false)
143 | else
144 | confirmdlg.opt:Hide()
145 | end
146 | confirmdlg:SetTitleText(ttxt)
147 | confirmdlg.str1:SetText(val)
148 | confirmdlg.str2:SetText(msg)
149 | confirmdlg.runfunction = func
150 | confirmdlg.arg = farg
151 | confirmdlg.isshown = isshown
152 |
153 | confirmdlg.kmod.mainwin:Hide()
154 | confirmdlg:Show()
155 | end
156 |
157 | function K.RenameDialog(kmod, ttxt, oldlbl, oldval, newlbl, len, func, farg, shown, xtras)
158 | local renamedlg = kmod.renamedialog
159 |
160 | if (not renamedlg) then
161 | local arg = {
162 | x = "CENTER", y = "MIDDLE",
163 | name = "KKoreRenameDialog",
164 | title = "",
165 | border = true,
166 | width = 450,
167 | height = 125,
168 | canmove = true,
169 | canresize = false,
170 | escclose = true,
171 | blackbg = true,
172 | okbutton = { text = K.OK_STR },
173 | cancelbutton = { text = K.CANCEL_STR },
174 | }
175 | if (xtras and type(xtras) == "table") then
176 | for k,v in pairs(xtras) do
177 | arg[k] = v
178 | end
179 | end
180 |
181 | local ret = KUI:CreateDialogFrame(arg)
182 |
183 | arg = {
184 | x = 0, y = 0, width = 150, height = 24, autosize = false,
185 | justifyh = "RIGHT", font = "GameFontNormal", text = "",
186 | }
187 | ret.str1 = KUI:CreateStringLabel(arg, ret)
188 |
189 | arg = {
190 | x = 0, y = 0, width = 200, height = 24, autosize = false,
191 | justifyh = "LEFT", font = "GameFontNormal", text = "",
192 | color = {r = 1, g = 1, b = 1, a = 1 }, border = true,
193 | }
194 | ret.str2 = KUI:CreateStringLabel(arg, ret)
195 |
196 | ret.str2:ClearAllPoints()
197 | ret.str2:SetPoint("TOPLEFT", ret.str1, "TOPRIGHT", 8, 0)
198 |
199 | arg = {
200 | x = 0, y = -30, width = 150, height = 24, autosize = false,
201 | justifyh = "RIGHT", font = "GameFontNormal", text = "",
202 | }
203 | ret.str3 = KUI:CreateStringLabel(arg, ret)
204 |
205 | arg = {
206 | x = 0, y = -30, width = 200, height = 20,
207 | }
208 | ret.input = KUI:CreateEditBox(arg, ret)
209 | ret.input:SetFocus()
210 |
211 | ret.input:ClearAllPoints()
212 | ret.input:SetPoint("TOPLEFT", ret.str3, "TOPRIGHT", 12, 0)
213 |
214 | ret.OnCancel = function(this)
215 | local trd = this.kmod.renamedialog
216 | trd:Hide()
217 | if (trd.isshown) then
218 | trd.kmod.mainwin:Show()
219 | end
220 | trd.isshown = nil
221 | end
222 |
223 | ret.OnAccept = function(this)
224 | local trd = this.kmod.renamedialog
225 | local rv = trd.runfunction(trd.input:GetText(), trd.arg, true)
226 | if (rv) then
227 | trd:Show()
228 | trd.input:SetFocus()
229 | else
230 | trd:Hide()
231 | if (trd.isshown) then
232 | trd.kmod.mainwin:Show()
233 | end
234 | trd.isshown = nil
235 | end
236 | end
237 | ret.input.OnEnterPressed = function(this)
238 | local tp = this:GetParent():GetParent()
239 | tp.OnAccept(tp)
240 | end
241 |
242 | ret.kmod = kmod
243 | kmod.renamedialog = ret
244 | renamedlg = kmod.renamedialog
245 | end
246 |
247 | renamedlg:SetTitleText(ttxt)
248 | renamedlg.str1:SetText(oldlbl)
249 | renamedlg.str2:SetText(oldval)
250 | renamedlg.str3:SetText(newlbl)
251 | renamedlg.input:SetMaxLetters(len)
252 | renamedlg.runfunction = func
253 | renamedlg.arg = farg
254 | renamedlg.isshown = shown
255 |
256 | renamedlg.kmod.mainwin:Hide()
257 | renamedlg:Show()
258 | renamedlg.input:SetText("")
259 | renamedlg.input:SetFocus()
260 | end
261 |
262 | function K.PopupSelectionList(kmod, name, list, title, width, height, parent, itemheight, func, topspace, botspace, xtras)
263 | if (kmod.popupwindow) then
264 | kmod.popupwindow:Hide()
265 | kmod.popupwindow = nil
266 | end
267 |
268 | assert(name)
269 |
270 | local arg = {
271 | name = name,
272 | itemheight = itemheight,
273 | width = width,
274 | height = height,
275 | header = topspace,
276 | footer = botspace,
277 | title = title,
278 | titlewidth = width - 110,
279 | canmove = true,
280 | canresize = true,
281 | escclose = true,
282 | blackbg = true,
283 | xbutton = false,
284 | level = 32,
285 | border = "THICK",
286 | x = "CENTER",
287 | y = "MIDDLE",
288 | func = function(lst, idx, arg)
289 | func(lst[idx].value)
290 | end,
291 | timeout = 3,
292 | }
293 | if (xtras and type(xtras) == "table") then
294 | for k,v in pairs(xtras) do
295 | arg[k] = v
296 | end
297 | end
298 |
299 | local rv = KUI:CreatePopupList(arg, parent)
300 | assert(rv)
301 | rv.kmod = kmod
302 |
303 | rv:UpdateList(list)
304 | rv:Show()
305 |
306 | return rv
307 | end
308 |
309 | function K.SingleStringInputDialog(kmod, name, title, text, width, height, xtras)
310 | local arg = {
311 | x = "CENTER", y = "MIDDLE",
312 | name = name,
313 | title = title,
314 | border = "THICK",
315 | width = width,
316 | height = height,
317 | canmove = true,
318 | canresize = false,
319 | escclose = true,
320 | xbutton = false,
321 | blackbg = true,
322 | okbutton = { text = K.ACCEPTSTR },
323 | cancelbutton = { text = K.CANCELSTR },
324 | }
325 | if (xtras and type(xtras) == "table") then
326 | for k,v in pairs(xtras) do
327 | arg[k] = v
328 | end
329 | end
330 |
331 | local ret = KUI:CreateDialogFrame(arg)
332 | assert(ret)
333 | ret.kmod = kmod
334 |
335 | arg = {
336 | x = 8, y = 0, width = width - 40, height = height - 90, autosize = false,
337 | font = "GameFontNormal", text = text, }
338 | ret.str1 = KUI:CreateStringLabel(arg, ret)
339 |
340 | arg = { x = "CENTER", y = -(height - 85), len = 32 }
341 | ret.ebox = KUI:CreateEditBox(arg, ret)
342 |
343 | ret.ebox:SetFocus()
344 |
345 | return ret, ret.ebox
346 | end
347 |
--------------------------------------------------------------------------------
/Libs/AceTimer-3.0.lua:
--------------------------------------------------------------------------------
1 | --- **AceTimer-3.0** provides a central facility for registering timers.
2 | -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
3 | -- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
4 | -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
5 | -- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
6 | -- restricts us to.
7 | --
8 | -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
9 | -- need to cancel the timer you just registered.
10 | --
11 | -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
12 | -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
13 | -- and can be accessed directly, without having to explicitly call AceTimer itself.\\
14 | -- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you
15 | -- make into AceTimer.
16 | -- @class file
17 | -- @name AceTimer-3.0
18 | -- @release $Id: AceTimer-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
19 |
20 | local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
21 | local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
22 |
23 | if not AceTimer then return end -- No upgrade needed
24 | AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
25 | local activeTimers = AceTimer.activeTimers -- Upvalue our private data
26 |
27 | -- Lua APIs
28 | local type, unpack, next, error, select = type, unpack, next, error, select
29 | -- WoW APIs
30 | local GetTime, C_TimerAfter = GetTime, C_Timer.After
31 |
32 | local function new(self, loop, func, delay, ...)
33 | if delay < 0.01 then
34 | delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
35 | end
36 |
37 | local timer = {
38 | object = self,
39 | func = func,
40 | looping = loop,
41 | argsCount = select("#", ...),
42 | delay = delay,
43 | ends = GetTime() + delay,
44 | ...
45 | }
46 |
47 | activeTimers[timer] = timer
48 |
49 | -- Create new timer closure to wrap the "timer" object
50 | timer.callback = function()
51 | if not timer.cancelled then
52 | if type(timer.func) == "string" then
53 | -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
54 | -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
55 | timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
56 | else
57 | timer.func(unpack(timer, 1, timer.argsCount))
58 | end
59 |
60 | if timer.looping and not timer.cancelled then
61 | -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
62 | -- due to fps differences
63 | local time = GetTime()
64 | local delay = timer.delay - (time - timer.ends)
65 | -- Ensure the delay doesn't go below the threshold
66 | if delay < 0.01 then delay = 0.01 end
67 | C_TimerAfter(delay, timer.callback)
68 | timer.ends = time + delay
69 | else
70 | activeTimers[timer.handle or timer] = nil
71 | end
72 | end
73 | end
74 |
75 | C_TimerAfter(delay, timer.callback)
76 | return timer
77 | end
78 |
79 | --- Schedule a new one-shot timer.
80 | -- The timer will fire once in `delay` seconds, unless canceled before.
81 | -- @param callback Callback function for the timer pulse (funcref or method name).
82 | -- @param delay Delay for the timer, in seconds.
83 | -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
84 | -- @usage
85 | -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
86 | --
87 | -- function MyAddOn:OnEnable()
88 | -- self:ScheduleTimer("TimerFeedback", 5)
89 | -- end
90 | --
91 | -- function MyAddOn:TimerFeedback()
92 | -- print("5 seconds passed")
93 | -- end
94 | function AceTimer:ScheduleTimer(func, delay, ...)
95 | if not func or not delay then
96 | error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
97 | end
98 | if type(func) == "string" then
99 | if type(self) ~= "table" then
100 | error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2)
101 | elseif not self[func] then
102 | error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
103 | end
104 | end
105 | return new(self, nil, func, delay, ...)
106 | end
107 |
108 | --- Schedule a repeating timer.
109 | -- The timer will fire every `delay` seconds, until canceled.
110 | -- @param callback Callback function for the timer pulse (funcref or method name).
111 | -- @param delay Delay for the timer, in seconds.
112 | -- @param ... An optional, unlimited amount of arguments to pass to the callback function.
113 | -- @usage
114 | -- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
115 | --
116 | -- function MyAddOn:OnEnable()
117 | -- self.timerCount = 0
118 | -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
119 | -- end
120 | --
121 | -- function MyAddOn:TimerFeedback()
122 | -- self.timerCount = self.timerCount + 1
123 | -- print(("%d seconds passed"):format(5 * self.timerCount))
124 | -- -- run 30 seconds in total
125 | -- if self.timerCount == 6 then
126 | -- self:CancelTimer(self.testTimer)
127 | -- end
128 | -- end
129 | function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
130 | if not func or not delay then
131 | error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2)
132 | end
133 | if type(func) == "string" then
134 | if type(self) ~= "table" then
135 | error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2)
136 | elseif not self[func] then
137 | error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2)
138 | end
139 | end
140 | return new(self, true, func, delay, ...)
141 | end
142 |
143 | --- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
144 | -- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
145 | -- and the timer has not fired yet or was canceled before.
146 | -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
147 | function AceTimer:CancelTimer(id)
148 | local timer = activeTimers[id]
149 |
150 | if not timer then
151 | return false
152 | else
153 | timer.cancelled = true
154 | activeTimers[id] = nil
155 | return true
156 | end
157 | end
158 |
159 | --- Cancels all timers registered to the current addon object ('self')
160 | function AceTimer:CancelAllTimers()
161 | for k,v in next, activeTimers do
162 | if v.object == self then
163 | AceTimer.CancelTimer(self, k)
164 | end
165 | end
166 | end
167 |
168 | --- Returns the time left for a timer with the given id, registered by the current addon object ('self').
169 | -- This function will return 0 when the id is invalid.
170 | -- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
171 | -- @return The time left on the timer.
172 | function AceTimer:TimeLeft(id)
173 | local timer = activeTimers[id]
174 | if not timer then
175 | return 0
176 | else
177 | return timer.ends - GetTime()
178 | end
179 | end
180 |
181 |
182 | -- ---------------------------------------------------------------------
183 | -- Upgrading
184 |
185 | -- Upgrade from old hash-bucket based timers to C_Timer.After timers.
186 | if oldminor and oldminor < 10 then
187 | -- disable old timer logic
188 | AceTimer.frame:SetScript("OnUpdate", nil)
189 | AceTimer.frame:SetScript("OnEvent", nil)
190 | AceTimer.frame:UnregisterAllEvents()
191 | -- convert timers
192 | for object,timers in next, AceTimer.selfs do
193 | for handle,timer in next, timers do
194 | if type(timer) == "table" and timer.callback then
195 | local newTimer
196 | if timer.delay then
197 | newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg)
198 | else
199 | newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg)
200 | end
201 | -- Use the old handle for old timers
202 | activeTimers[newTimer] = nil
203 | activeTimers[handle] = newTimer
204 | newTimer.handle = handle
205 | end
206 | end
207 | end
208 | AceTimer.selfs = nil
209 | AceTimer.hash = nil
210 | AceTimer.debug = nil
211 | elseif oldminor and oldminor < 17 then
212 | -- Upgrade from old animation based timers to C_Timer.After timers.
213 | AceTimer.inactiveTimers = nil
214 | AceTimer.frame = nil
215 | local oldTimers = AceTimer.activeTimers
216 | -- Clear old timer table and update upvalue
217 | AceTimer.activeTimers = {}
218 | activeTimers = AceTimer.activeTimers
219 | for handle, timer in next, oldTimers do
220 | local newTimer
221 | -- Stop the old timer animation
222 | local duration, elapsed = timer:GetDuration(), timer:GetElapsed()
223 | timer:GetParent():Stop()
224 | if timer.looping then
225 | newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount))
226 | else
227 | newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount))
228 | end
229 | -- Use the old handle for old timers
230 | activeTimers[newTimer] = nil
231 | activeTimers[handle] = newTimer
232 | newTimer.handle = handle
233 | end
234 |
235 | -- Migrate transitional handles
236 | if oldminor < 13 and AceTimer.hashCompatTable then
237 | for handle, id in next, AceTimer.hashCompatTable do
238 | local t = activeTimers[id]
239 | if t then
240 | activeTimers[id] = nil
241 | activeTimers[handle] = t
242 | t.handle = handle
243 | end
244 | end
245 | AceTimer.hashCompatTable = nil
246 | end
247 | end
248 |
249 | -- ---------------------------------------------------------------------
250 | -- Embed handling
251 |
252 | AceTimer.embeds = AceTimer.embeds or {}
253 |
254 | local mixins = {
255 | "ScheduleTimer", "ScheduleRepeatingTimer",
256 | "CancelTimer", "CancelAllTimers",
257 | "TimeLeft"
258 | }
259 |
260 | function AceTimer:Embed(target)
261 | AceTimer.embeds[target] = true
262 | for _,v in next, mixins do
263 | target[v] = AceTimer[v]
264 | end
265 | return target
266 | end
267 |
268 | -- AceTimer:OnEmbedDisable(target)
269 | -- target (object) - target object that AceTimer is embedded in.
270 | --
271 | -- cancel all timers registered for the object
272 | function AceTimer:OnEmbedDisable(target)
273 | target:CancelAllTimers()
274 | end
275 |
276 | for addon in next, AceTimer.embeds do
277 | AceTimer:Embed(addon)
278 | end
279 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/Libs/AceComm-3.0.lua:
--------------------------------------------------------------------------------
1 | --- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
2 | -- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
3 | -- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
4 | --
5 | -- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
6 | -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
7 | -- and can be accessed directly, without having to explicitly call AceComm itself.\\
8 | -- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
9 | -- make into AceComm.
10 | -- @class file
11 | -- @name AceComm-3.0
12 | -- @release $Id: AceComm-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
13 |
14 | --[[ AceComm-3.0
15 |
16 | TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
17 |
18 | ]]
19 |
20 | local CallbackHandler = LibStub("CallbackHandler-1.0")
21 | local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
22 |
23 | local MAJOR, MINOR = "AceComm-3.0", 12
24 | local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
25 |
26 | if not AceComm then return end
27 |
28 | -- Lua APIs
29 | local type, next, pairs, tostring = type, next, pairs, tostring
30 | local strsub, strfind = string.sub, string.find
31 | local match = string.match
32 | local tinsert, tconcat = table.insert, table.concat
33 | local error, assert = error, assert
34 |
35 | -- WoW APIs
36 | local Ambiguate = Ambiguate
37 |
38 | -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
39 | -- List them here for Mikk's FindGlobals script
40 | -- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
41 |
42 | AceComm.embeds = AceComm.embeds or {}
43 |
44 | -- for my sanity and yours, let's give the message type bytes some names
45 | local MSG_MULTI_FIRST = "\001"
46 | local MSG_MULTI_NEXT = "\002"
47 | local MSG_MULTI_LAST = "\003"
48 | local MSG_ESCAPE = "\004"
49 |
50 | -- remove old structures (pre WoW 4.0)
51 | AceComm.multipart_origprefixes = nil
52 | AceComm.multipart_reassemblers = nil
53 |
54 | -- the multipart message spool: indexed by a combination of sender+distribution+
55 | AceComm.multipart_spool = AceComm.multipart_spool or {}
56 |
57 | --- Register for Addon Traffic on a specified prefix
58 | -- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
59 | -- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
60 | function AceComm:RegisterComm(prefix, method)
61 | if method == nil then
62 | method = "OnCommReceived"
63 | end
64 |
65 | if #prefix > 16 then -- TODO: 15?
66 | error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
67 | end
68 | if C_ChatInfo then
69 | C_ChatInfo.RegisterAddonMessagePrefix(prefix)
70 | else
71 | RegisterAddonMessagePrefix(prefix)
72 | end
73 |
74 | return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
75 | end
76 |
77 | local warnedPrefix=false
78 |
79 | --- Send a message over the Addon Channel
80 | -- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
81 | -- @param text Data to send, nils (\000) not allowed. Any length.
82 | -- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
83 | -- @param target Destination for some distributions; see SendAddonMessage API
84 | -- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
85 | -- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
86 | -- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
87 | function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
88 | prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
89 | if not( type(prefix)=="string" and
90 | type(text)=="string" and
91 | type(distribution)=="string" and
92 | (target==nil or type(target)=="string" or type(target)=="number") and
93 | (prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
94 | ) then
95 | error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
96 | end
97 |
98 | local textlen = #text
99 | local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
100 | local queueName = prefix..distribution..(target or "")
101 |
102 | local ctlCallback = nil
103 | if callbackFn then
104 | ctlCallback = function(sent)
105 | return callbackFn(callbackArg, sent, textlen)
106 | end
107 | end
108 |
109 | local forceMultipart
110 | if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
111 | -- we need to escape the first character with a \004
112 | if textlen+1 > maxtextlen then -- would we go over the size limit?
113 | forceMultipart = true -- just make it multipart, no escape problems then
114 | else
115 | text = "\004" .. text
116 | end
117 | end
118 |
119 | if not forceMultipart and textlen <= maxtextlen then
120 | -- fits all in one message
121 | CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
122 | else
123 | maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
124 |
125 | -- first part
126 | local chunk = strsub(text, 1, maxtextlen)
127 | CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
128 |
129 | -- continuation
130 | local pos = 1+maxtextlen
131 |
132 | while pos+maxtextlen <= textlen do
133 | chunk = strsub(text, pos, pos+maxtextlen-1)
134 | CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
135 | pos = pos + maxtextlen
136 | end
137 |
138 | -- final part
139 | chunk = strsub(text, pos)
140 | CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
141 | end
142 | end
143 |
144 |
145 | ----------------------------------------
146 | -- Message receiving
147 | ----------------------------------------
148 |
149 | do
150 | local compost = setmetatable({}, {__mode = "k"})
151 | local function new()
152 | local t = next(compost)
153 | if t then
154 | compost[t]=nil
155 | for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
156 | t[i]=nil
157 | end
158 | return t
159 | end
160 |
161 | return {}
162 | end
163 |
164 | local function lostdatawarning(prefix,sender,where)
165 | DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
166 | end
167 |
168 | function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
169 | local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
170 | local spool = AceComm.multipart_spool
171 |
172 | --[[
173 | if spool[key] then
174 | lostdatawarning(prefix,sender,"First")
175 | -- continue and overwrite
176 | end
177 | --]]
178 |
179 | spool[key] = message -- plain string for now
180 | end
181 |
182 | function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
183 | local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
184 | local spool = AceComm.multipart_spool
185 | local olddata = spool[key]
186 |
187 | if not olddata then
188 | --lostdatawarning(prefix,sender,"Next")
189 | return
190 | end
191 |
192 | if type(olddata)~="table" then
193 | -- ... but what we have is not a table. So make it one. (Pull a composted one if available)
194 | local t = new()
195 | t[1] = olddata -- add old data as first string
196 | t[2] = message -- and new message as second string
197 | spool[key] = t -- and put the table in the spool instead of the old string
198 | else
199 | tinsert(olddata, message)
200 | end
201 | end
202 |
203 | function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
204 | local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
205 | local spool = AceComm.multipart_spool
206 | local olddata = spool[key]
207 |
208 | if not olddata then
209 | --lostdatawarning(prefix,sender,"End")
210 | return
211 | end
212 |
213 | spool[key] = nil
214 |
215 | if type(olddata) == "table" then
216 | -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
217 | tinsert(olddata, message)
218 | AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
219 | compost[olddata] = true
220 | else
221 | -- if we've only received a "first", the spooled data will still only be a string
222 | AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
223 | end
224 | end
225 | end
226 |
227 |
228 |
229 |
230 |
231 |
232 | ----------------------------------------
233 | -- Embed CallbackHandler
234 | ----------------------------------------
235 |
236 | if not AceComm.callbacks then
237 | AceComm.callbacks = CallbackHandler:New(AceComm,
238 | "_RegisterComm",
239 | "UnregisterComm",
240 | "UnregisterAllComm")
241 | end
242 |
243 | AceComm.callbacks.OnUsed = nil
244 | AceComm.callbacks.OnUnused = nil
245 |
246 | local function OnEvent(self, event, prefix, message, distribution, sender)
247 | if event == "CHAT_MSG_ADDON" then
248 | sender = Ambiguate(sender, "none")
249 | local control, rest = match(message, "^([\001-\009])(.*)")
250 | if control then
251 | if control==MSG_MULTI_FIRST then
252 | AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
253 | elseif control==MSG_MULTI_NEXT then
254 | AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
255 | elseif control==MSG_MULTI_LAST then
256 | AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
257 | elseif control==MSG_ESCAPE then
258 | AceComm.callbacks:Fire(prefix, rest, distribution, sender)
259 | else
260 | -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
261 | end
262 | else
263 | -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
264 | AceComm.callbacks:Fire(prefix, message, distribution, sender)
265 | end
266 | else
267 | assert(false, "Received "..tostring(event).." event?!")
268 | end
269 | end
270 |
271 | AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
272 | AceComm.frame:SetScript("OnEvent", OnEvent)
273 | AceComm.frame:UnregisterAllEvents()
274 | AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
275 |
276 |
277 | ----------------------------------------
278 | -- Base library stuff
279 | ----------------------------------------
280 |
281 | local mixins = {
282 | "RegisterComm",
283 | "UnregisterComm",
284 | "UnregisterAllComm",
285 | "SendCommMessage",
286 | }
287 |
288 | -- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
289 | -- @param target target object to embed AceComm-3.0 in
290 | function AceComm:Embed(target)
291 | for k, v in pairs(mixins) do
292 | target[v] = self[v]
293 | end
294 | self.embeds[target] = true
295 | return target
296 | end
297 |
298 | function AceComm:OnEmbedDisable(target)
299 | target:UnregisterAllComm()
300 | end
301 |
302 | -- Update embeds
303 | for target, v in pairs(AceComm.embeds) do
304 | AceComm:Embed(target)
305 | end
306 |
--------------------------------------------------------------------------------
/KKoreHash.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- KahLua Kore - MD5 hashing and CRC checking.
3 | --
4 | -- MD5 converted from C code written by Colin Plumb in 1993. He claims
5 | -- no copyright and the code is in the public domain, as is this Lua
6 | -- implementation. The MD5 conversion done by Kean Johnston (Cruciformer).
7 | -- See http://cvsweb.xfree86.org/cvsweb/cvs/lib/md5.c?rev=1.1.1.2 for the
8 | -- original source. I modified the algorithm to not rely on memcpy() which
9 | -- is problematic in Lua.
10 | --
11 | -- Quick CRC-32 calculation code provided by Allara on Curseforge
12 | -- (http://forums.curseforge.com/showthread.php?t=15001). I also modified
13 | -- that code to allow for a running calculation.
14 | --
15 |
16 | local KKOREHASH_MAJOR = "KKoreHash"
17 | local KKOREHASH_MINOR = 4
18 | local H, oldminor = LibStub:NewLibrary(KKOREHASH_MAJOR, KKOREHASH_MINOR)
19 |
20 | if (not H) then
21 | return
22 | end
23 |
24 | H.debug_id = KKOREHASH_MAJOR
25 |
26 | local K, KM = LibStub:GetLibrary("KKore")
27 | assert (K, "KKoreHash requires KKore")
28 | assert (tonumber(KM) >= 4, "KKoreHash requires KKore r4 or later")
29 | K:RegisterExtension (H, KKOREHASH_MAJOR, KKOREHASH_MINOR)
30 |
31 | local bor = bit.bor
32 | local band = bit.band
33 | local bxor = bit.bxor
34 | local lshift = bit.lshift
35 | local rshift = bit.rshift
36 | local bmod = bit.mod
37 | local strchr = string.char
38 | local strlen = string.len
39 | local strbyte = string.byte
40 | local strsub = string.sub
41 | local strrep = string.rep
42 | local strfmt = string.format
43 | local tinsert = table.insert
44 | local tremove = table.remove
45 |
46 | local ff = 0xffffffff
47 |
48 | local md5magic = {
49 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
50 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
51 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
52 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
53 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
54 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
55 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
56 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
57 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
58 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
59 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
60 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
61 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
62 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
63 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
64 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
65 | }
66 |
67 | local function F1(x, y, z)
68 | return bor(band(x, y),band(-x - 1, z))
69 | end
70 |
71 | local function F2(x, y, z)
72 | return bor(band(x, z),band(y, -z - 1))
73 | end
74 |
75 | local function F3(x, y, z)
76 | return bxor(x, bxor(y, z))
77 | end
78 |
79 | local function F4(x, y, z)
80 | return bxor(y, bor(x, -z - 1))
81 | end
82 |
83 | local function MSTEP(f, a, b, c, d, x, s, mv)
84 | a = band(a + f(b, c, d) + x + mv, ff)
85 | return bor(lshift(band(a, rshift(ff, s)), s),rshift(a, 32 - s)) + b
86 | end
87 |
88 | local function MD5xform(cbuf, ibuf)
89 | local a, b, c, d = unpack(cbuf, 0, 3)
90 |
91 | a = MSTEP(F1, a, b, c, d, ibuf[ 0], 7, md5magic[ 1])
92 | d = MSTEP(F1, d, a, b, c, ibuf[ 1], 12, md5magic[ 2])
93 | c = MSTEP(F1, c, d, a, b, ibuf[ 2], 17, md5magic[ 3])
94 | b = MSTEP(F1, b, c, d, a, ibuf[ 3], 22, md5magic[ 4])
95 | a = MSTEP(F1, a, b, c, d, ibuf[ 4], 7, md5magic[ 5])
96 | d = MSTEP(F1, d, a, b, c, ibuf[ 5], 12, md5magic[ 6])
97 | c = MSTEP(F1, c, d, a, b, ibuf[ 6], 17, md5magic[ 7])
98 | b = MSTEP(F1, b, c, d, a, ibuf[ 7], 22, md5magic[ 8])
99 | a = MSTEP(F1, a, b, c, d, ibuf[ 8], 7, md5magic[ 9])
100 | d = MSTEP(F1, d, a, b, c, ibuf[ 9], 12, md5magic[10])
101 | c = MSTEP(F1, c, d, a, b, ibuf[10], 17, md5magic[11])
102 | b = MSTEP(F1, b, c, d, a, ibuf[11], 22, md5magic[12])
103 | a = MSTEP(F1, a, b, c, d, ibuf[12], 7, md5magic[13])
104 | d = MSTEP(F1, d, a, b, c, ibuf[13], 12, md5magic[14])
105 | c = MSTEP(F1, c, d, a, b, ibuf[14], 17, md5magic[15])
106 | b = MSTEP(F1, b, c, d, a, ibuf[15], 22, md5magic[16])
107 |
108 | a = MSTEP(F2, a, b, c, d, ibuf[ 1], 5, md5magic[17])
109 | d = MSTEP(F2, d, a, b, c, ibuf[ 6], 9, md5magic[18])
110 | c = MSTEP(F2, c, d, a, b, ibuf[11], 14, md5magic[19])
111 | b = MSTEP(F2, b, c, d, a, ibuf[ 0], 20, md5magic[20])
112 | a = MSTEP(F2, a, b, c, d, ibuf[ 5], 5, md5magic[21])
113 | d = MSTEP(F2, d, a, b, c, ibuf[10], 9, md5magic[22])
114 | c = MSTEP(F2, c, d, a, b, ibuf[15], 14, md5magic[23])
115 | b = MSTEP(F2, b, c, d, a, ibuf[ 4], 20, md5magic[24])
116 | a = MSTEP(F2, a, b, c, d, ibuf[ 9], 5, md5magic[25])
117 | d = MSTEP(F2, d, a, b, c, ibuf[14], 9, md5magic[26])
118 | c = MSTEP(F2, c, d, a, b, ibuf[ 3], 14, md5magic[27])
119 | b = MSTEP(F2, b, c, d, a, ibuf[ 8], 20, md5magic[28])
120 | a = MSTEP(F2, a, b, c, d, ibuf[13], 5, md5magic[29])
121 | d = MSTEP(F2, d, a, b, c, ibuf[ 2], 9, md5magic[30])
122 | c = MSTEP(F2, c, d, a, b, ibuf[ 7], 14, md5magic[31])
123 | b = MSTEP(F2, b, c, d, a, ibuf[12], 20, md5magic[32])
124 |
125 | a = MSTEP(F3, a, b, c, d, ibuf[ 5], 4, md5magic[33])
126 | d = MSTEP(F3, d, a, b, c, ibuf[ 8], 11, md5magic[34])
127 | c = MSTEP(F3, c, d, a, b, ibuf[11], 16, md5magic[35])
128 | b = MSTEP(F3, b, c, d, a, ibuf[14], 23, md5magic[36])
129 | a = MSTEP(F3, a, b, c, d, ibuf[ 1], 4, md5magic[37])
130 | d = MSTEP(F3, d, a, b, c, ibuf[ 4], 11, md5magic[38])
131 | c = MSTEP(F3, c, d, a, b, ibuf[ 7], 16, md5magic[39])
132 | b = MSTEP(F3, b, c, d, a, ibuf[10], 23, md5magic[40])
133 | a = MSTEP(F3, a, b, c, d, ibuf[13], 4, md5magic[41])
134 | d = MSTEP(F3, d, a, b, c, ibuf[ 0], 11, md5magic[42])
135 | c = MSTEP(F3, c, d, a, b, ibuf[ 3], 16, md5magic[43])
136 | b = MSTEP(F3, b, c, d, a, ibuf[ 6], 23, md5magic[44])
137 | a = MSTEP(F3, a, b, c, d, ibuf[ 9], 4, md5magic[45])
138 | d = MSTEP(F3, d, a, b, c, ibuf[12], 11, md5magic[46])
139 | c = MSTEP(F3, c, d, a, b, ibuf[15], 16, md5magic[47])
140 | b = MSTEP(F3, b, c, d, a, ibuf[ 2], 23, md5magic[48])
141 |
142 | a = MSTEP(F4, a, b, c, d, ibuf[ 0], 6, md5magic[49])
143 | d = MSTEP(F4, d, a, b, c, ibuf[ 7], 10, md5magic[50])
144 | c = MSTEP(F4, c, d, a, b, ibuf[14], 15, md5magic[51])
145 | b = MSTEP(F4, b, c, d, a, ibuf[ 5], 21, md5magic[52])
146 | a = MSTEP(F4, a, b, c, d, ibuf[12], 6, md5magic[53])
147 | d = MSTEP(F4, d, a, b, c, ibuf[ 3], 10, md5magic[54])
148 | c = MSTEP(F4, c, d, a, b, ibuf[10], 15, md5magic[55])
149 | b = MSTEP(F4, b, c, d, a, ibuf[ 1], 21, md5magic[56])
150 | a = MSTEP(F4, a, b, c, d, ibuf[ 8], 6, md5magic[57])
151 | d = MSTEP(F4, d, a, b, c, ibuf[15], 10, md5magic[58])
152 | c = MSTEP(F4, c, d, a, b, ibuf[ 6], 15, md5magic[59])
153 | b = MSTEP(F4, b, c, d, a, ibuf[13], 21, md5magic[60])
154 | a = MSTEP(F4, a, b, c, d, ibuf[ 4], 6, md5magic[61])
155 | d = MSTEP(F4, d, a, b, c, ibuf[11], 10, md5magic[62])
156 | c = MSTEP(F4, c, d, a, b, ibuf[ 2], 15, md5magic[63])
157 | b = MSTEP(F4, b, c, d, a, ibuf[ 9], 21, md5magic[64])
158 |
159 | cbuf[0] = band(cbuf[0] + a, ff)
160 | cbuf[1] = band(cbuf[1] + b, ff)
161 | cbuf[2] = band(cbuf[2] + c, ff)
162 | cbuf[3] = band(cbuf[3] + d, ff)
163 | end
164 |
165 | function H:MD5Init()
166 | local ret = {buf = {}, bits = {}, inbuf = {}}
167 | ret.buf[0] = 0x67452301
168 | ret.buf[1] = 0xefcdab89
169 | ret.buf[2] = 0x98badcfe
170 | ret.buf[3] = 0x10325476
171 | ret.bits[0] = 0
172 | ret.bits[1] = 0
173 | return ret
174 | end
175 |
176 | function H:MD5Update(ctx, buf, len)
177 | local ibuf = {}
178 | local t, j, k, l
179 |
180 | if (not len) then
181 | len = strlen(buf)
182 | end
183 |
184 | t = band(rshift(ctx.bits[0], 3), 0x3f)
185 |
186 | if ((ctx.bits[0] + lshift(len, 3)) < ctx.bits[0]) then
187 | cts.bits[1] = ctx.bits[1] + 1
188 | end
189 | ctx.bits[0] = ctx.bits[0] + lshift(len, 3)
190 | ctx.bits[1] = ctx.bits[1] + rshift(len, 29)
191 |
192 | for l = 1, len do
193 | ctx.inbuf[t] = band(strbyte(buf, l, l), 0xff)
194 | t = t + 1
195 | if (t == 64) then
196 | k = 0
197 | for j = 0, 15 do
198 | ibuf[j] = band(band(lshift(ctx.inbuf[k+3], 24), ff) +
199 | band(lshift(ctx.inbuf[k+2], 16), ff) +
200 | band(lshift(ctx.inbuf[k+1], 8), ff) +
201 | band(ctx.inbuf[k], ff), ff)
202 | k = k + 4
203 | end
204 | MD5xform(ctx.buf, ibuf)
205 | t = 0
206 | end
207 | end
208 | end
209 |
210 | local md5pad = strchr(128) .. strrep(strchr(0), 63)
211 |
212 | function H:MD5Final(ctx)
213 | local digest = {}
214 | local ibuf = {}
215 | local t, j, k, l, p
216 |
217 | ibuf[14] = band(ctx.bits[0], ff)
218 | ibuf[15] = band(ctx.bits[1], ff)
219 |
220 | t = band(rshift(ctx.bits[0], 3), 0x3f)
221 | if (t < 56) then
222 | p = 56 - t
223 | else
224 | p = 120 - t
225 | end
226 | H:MD5Update(ctx, md5pad, p)
227 |
228 | k = 0
229 | for j = 0, 13 do
230 | ibuf[j] = band(band(lshift(ctx.inbuf[k+3], 24), ff) +
231 | band(lshift(ctx.inbuf[k+2], 16), ff) +
232 | band(lshift(ctx.inbuf[k+1], 8), ff) +
233 | band(ctx.inbuf[k], ff), ff)
234 | k = k + 4
235 | end
236 | MD5xform(ctx.buf, ibuf)
237 |
238 | k = 0
239 | for j = 0, 3 do
240 | digest[k] = band(ctx.buf[j], 0xff)
241 | digest[k+1] = band(rshift(ctx.buf[j], 8), 0xff)
242 | digest[k+2] = band(rshift(ctx.buf[j], 16), 0xff)
243 | digest[k+3] = band(rshift(ctx.buf[j], 24), 0xff)
244 | k = k + 4
245 | end
246 |
247 | ctx.buf[0] = nil
248 | ctx.buf[1] = nil
249 | ctx.buf[2] = nil
250 | ctx.buf[3] = nil
251 | ctx.bits[0] = nil
252 | ctx.bits[1] = nil
253 | ctx.inbuf = nil
254 | ctx = nil
255 |
256 | return strfmt(strrep("%02x", 16), unpack(digest, 0, 15))
257 | end
258 |
259 | function H:MD5(s)
260 | local ctx = H:MD5Init()
261 | H:MD5Update(ctx, s)
262 | return H:MD5Final(ctx)
263 | end
264 |
265 | local crc_mod = {
266 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
267 | 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
268 | 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
269 | 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
270 | 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
271 | 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
272 | 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
273 | 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
274 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
275 | 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
276 | 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
277 | 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
278 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
279 | 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
280 | 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
281 | 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
282 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
283 | 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
284 | 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
285 | 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
286 | 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
287 | 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
288 | 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
289 | 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
290 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
291 | 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
292 | 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
293 | 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
294 | 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
295 | 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
296 | 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
297 | 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
298 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
299 | 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
300 | 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
301 | 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
302 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
303 | 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
304 | 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
305 | 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
306 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
307 | 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
308 | 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
309 | 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
310 | 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
311 | 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
312 | 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
313 | 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
314 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
315 | 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
316 | 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
317 | 0x2D02EF8D }
318 |
319 | function H:CRC32(data, current_crc, finalise)
320 | local crc
321 | local l = strlen(data)
322 | local j
323 |
324 | if (c ~= nil) then
325 | crc = current_crc
326 | else
327 | crc = ff
328 | end
329 |
330 | for j = 1, l, 1 do
331 | crc = bxor(rshift(crc, 8), crc_mod[band(bxor(crc, strbyte(data, j)), 0xFF) + 1])
332 | end
333 |
334 | if (finalise == false) then
335 | return crc
336 | end
337 |
338 | return bxor(crc, ff)
339 | end
340 |
341 |
--------------------------------------------------------------------------------
/KKoreLoot.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - loot distribution handling.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 |
8 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
9 |
10 | Copyright 2008-2021 James Kean Johnston. All rights reserved.
11 |
12 | Licensed under the Apache License, Version 2.0 (the "License");
13 | you may not use this file except in compliance with the License.
14 | You may obtain a copy of the License at
15 |
16 | http://www.apache.org/licenses/LICENSE-2.0
17 |
18 | Unless required by applicable law or agreed to in writing, software
19 | distributed under the License is distributed on an "AS IS" BASIS,
20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 | See the License for the specific language governing permissions and
22 | limitations under the License.
23 | ]]
24 |
25 | local KKORELOOT_MAJOR = "KKoreLoot"
26 | local KKORELOOT_MINOR = 4
27 | local KLD, oldminor = LibStub:NewLibrary(KKORELOOT_MAJOR, KKORELOOT_MINOR)
28 |
29 | if (not KLD) then
30 | return
31 | end
32 |
33 | KLD.debug_id = KKORELOOT_MAJOR
34 |
35 | local K, KM = LibStub:GetLibrary("KKore")
36 | assert (K, "KKoreLoot requires KKore")
37 | assert (tonumber(KM) >= 4, "KKoreLoot requires KKore r4 or later")
38 | K:RegisterExtension (KLD, KKORELOOT_MAJOR, KKORELOOT_MINOR)
39 |
40 | local KRP, KM = LibStub:GetLibrary("KKoreParty")
41 | assert (KRP, "KKoreLoot requires KKoreParty")
42 | assert (tonumber(KM) >= 4, "KKoreLoot requires KKoreParty r4 or later")
43 |
44 | local L = LibStub("AceLocale-3.0"):GetLocale("KKore")
45 |
46 | --
47 | -- Constants for easy representation of the various armor and weapon types
48 | -- that a character can equip. This uses the Blizzard localised strings as
49 | -- much as possible.
50 | --
51 | K.INV_HEAD = 1
52 | K.INV_NECK = 2
53 | K.INV_SHOULDER = 3
54 | K.INV_BODY = 4
55 | K.INV_CHEST = 5
56 | K.INV_WAIST = 6
57 | K.INV_LEGS = 7
58 | K.INV_FEET = 8
59 | K.INV_WRIST = 9
60 | K.INV_HANDS = 10
61 | K.INV_FINGER = 11
62 | K.INV_TRINKET = 12
63 | K.INV_1HWEAPON = 13
64 | K.INV_SHIELD = 14
65 | K.INV_RANGED = 15
66 | K.INV_BACK = 16
67 | K.INV_2HWEAPON = 17
68 | K.INV_BAG = 18
69 | K.INV_TABARD = 19
70 | K.INV_ROBE = 20
71 | K.INV_MHWEAPON = 21
72 | K.INV_OHWEAPON = 22
73 | K.INV_HOLDABLE = 23
74 | K.INV_AMMO = 24
75 | K.INV_THROWN = 25
76 | K.INV_RANGEDRIGHT = 26
77 | K.INV_QUIVER = 27
78 | K.INV_RELIC = 28
79 |
80 | K.InvSlotNames = {
81 | [K.INV_HEAD] = INVTYPE_HEAD,
82 | [K.INV_NECK] = INVTYPE_NECK,
83 | [K.INV_SHOULDER] = INVTYPE_SHOULDER,
84 | [K.INV_BODY] = INVTYPE_BODY,
85 | [K.INV_CHEST] = INVTYPE_CHEST,
86 | [K.INV_WAIST] = INVTYPE_WAIST,
87 | [K.INV_LEGS] = INVTYPE_LEGS,
88 | [K.INV_FEET] = INVTYPE_FEET,
89 | [K.INV_WRIST] = INVTYPE_WRIST,
90 | [K.INV_HANDS] = INVTYPE_HAND,
91 | [K.INV_FINGER] = INVTYPE_FINGER,
92 | [K.INV_TRINKET] = INVTYPE_TRINKET,
93 | [K.INV_1HWEAPON] = INVTYPE_WEAPON .. " " .. WEAPON,
94 | [K.INV_SHIELD] = L["Shield"],
95 | [K.INV_RANGED] = INVTYPE_RANGED .. " " .. WEAPON,
96 | [K.INV_BACK] = INVTYPE_CLOAK,
97 | [K.INV_2HWEAPON] = INVTYPE_2HWEAPON .. " " .. WEAPON,
98 | [K.INV_BAG] = INVTYPE_BAG,
99 | [K.INV_TABARD] = INVTYPE_TABARD,
100 | [K.INV_ROBE] = INVTYPE_ROBE,
101 | [K.INV_MHWEAPON] = INVTYPE_WEAPONMAINHAND .. " " .. WEAPON,
102 | [K.INV_OHWEAPON] = INVTYPE_WEAPONOFFHAND .. " " .. WEAPON,
103 | [K.INV_HOLDABLE] = INVTYPE_HOLDABLE,
104 | [K.INV_AMMO] = INVTYPE_AMMO,
105 | [K.INV_THROWN] = INVTYPE_THROWN .. " " .. WEAPON,
106 | [K.INV_RANGEDRIGHT] = INVTYPE_RANGEDRIGHT,
107 | [K.INV_QUIVER] = INVTYPE_QUIVER,
108 | [K.INV_RELIC] = INVTYPE_RELIC,
109 | }
110 |
111 | local strmatch = string.match
112 | local printf = K.printf
113 | local tinsert = table.insert
114 | local pairs = pairs
115 |
116 | local function debug(lvl,...)
117 | K.debug("kore", lvl, ...)
118 | end
119 |
120 | local LOOT_METHOD_UNKNOWN = KRP.LOOT_METHOD_UNKNWON
121 | local LOOT_METHOD_FREEFORALL = KRP.LOOT_METHOD_FREEFORALL
122 | local LOOT_METHOD_ROUNDROBIN = KRP.LOOT_METHOD_ROUNDROBIN
123 | local LOOT_METHOD_MASTER = KRP.LOOT_METHOD_MASTER
124 | local LOOT_METHOD_GROUP = KRP.LOOT_METHOD_GROUP
125 | local LOOT_METHOD_NEEDB4GREED = KRP.LOOT_METHOD_NEEDB4GREED
126 | local LOOT_METHOD_PERSONAL = KRP.LOOT_METHOD_PERSONAL
127 |
128 | KLD.addons = {}
129 |
130 | KLD.valid_callbacks = {
131 | ["ml_candidate"] = true,
132 | ["loot_item"] = true,
133 | ["start_loot_info"] = true,
134 | ["end_loot_info"] = true,
135 | ["looting_ready"] = true,
136 | ["loot_assigned"] = true,
137 | }
138 |
139 | KLD.initialised = false
140 |
141 | -- Name of the unit being looted or nil if none.
142 | KLD.unit_name = nil
143 |
144 | -- GUID of the unit being looted or nil if none.
145 | KLD.unit_guid = nil
146 |
147 | -- Whether or not KLD.unit_guid is a real GUID (if set at all).
148 | KLD.unit_realguid = false
149 |
150 | -- Name of the chest or item being opened or nil if none
151 | KLD.chest_name = nil
152 |
153 | -- Number of loot items on the current corpse / chest or 0 if there is no
154 | -- such current corpse or there are no items that match the threshold.
155 | KLD.num_items = 0
156 |
157 | -- State variable to indicate if we should skip populating loot this time.
158 | KLD.skip_loot = false
159 |
160 | -- Table of items on the current corpse or nil if there is no current corpse.
161 | -- Each element in this table is a table with the following members:
162 | -- name - name of the item
163 | -- ilink - the full item link
164 | -- itemid - the item ID
165 | -- lootslot - the loot slot number
166 | -- quantity - how many of the item
167 | -- quality - the item quality
168 | -- locked - whether or not the item is locked
169 | -- candidates - list of possible candidates if we are master looting
170 | KLD.items = nil
171 |
172 | local disenchant_name = GetSpellInfo(13262)
173 | local herbalism_name = GetSpellInfo(11993)
174 | local mining_name = GetSpellInfo(32606)
175 | -- local skinning_name = GetSpellInfo(75644)
176 |
177 | --
178 | -- Function: get_ml_candidates(slot)
179 | -- Purpose : Returns the list of valid candidates for the provided
180 | -- loot slot item, or nil if there is no current loot slot or
181 | -- we are not using master looting.
182 | -- Callback: Calls ml_candidate for each candidate.
183 | --
184 | local function get_ml_candidates(slot)
185 | if (not KLD.initialised) then
186 | return nil
187 | end
188 |
189 | if (not KRP.master_looter) then
190 | return nil
191 | end
192 |
193 | local candidates = {}
194 | local count = 0
195 | for i = 1, MAX_RAID_MEMBERS do
196 | local name = GetMasterLootCandidate(slot, i)
197 | if (name) then
198 | name = K.CanonicalName(name, nil)
199 | if (not name) then
200 | return nil
201 | end
202 |
203 | local cinfo = {}
204 | cinfo["index"] = i
205 | cinfo["lootslot"] = slot
206 | candidates[name] = cinfo
207 | KLD:DoCallbacks("ml_candidate", candidates[name])
208 | count = count + 1
209 | end
210 | end
211 |
212 | if (count > 0) then
213 | return candidates
214 | else
215 | return nil
216 | end
217 | end
218 |
219 | local function reset_items()
220 | KLD.items = nil
221 | KLD.num_items = 0
222 | end
223 |
224 | -- Actually retrieve all of the loot slot item info.
225 | local function populate_items()
226 | local nitems = GetNumLootItems()
227 | local items = {}
228 | local count = 0
229 |
230 | KLD:DoCallbacks("start_loot_info")
231 |
232 | for i = 1, nitems do
233 | if (LootSlotHasItem(i)) then
234 | local icon, name, quant, _, qual, locked = GetLootSlotInfo(i)
235 | local ilink = GetLootSlotLink(i)
236 | local itemid = nil
237 | local item = {}
238 |
239 | if (icon and qual >= KRP.loot_threshold) then
240 | item["name"] = name
241 | if (ilink and ilink ~= "") then
242 | item["ilink"] = ilink
243 | itemid = strmatch(ilink, "item:(%d+)")
244 | end
245 |
246 | item["itemid"] = itemid
247 | item["lootslot"] = i
248 | item["quantity"] = quant
249 | item["quality"] = qual
250 | item["locked"] = locked or false
251 | item["candidates"] = get_ml_candidates(i) or {}
252 |
253 | items[i] = item
254 | count = count + 1
255 | end
256 | end
257 | end
258 |
259 | KLD.num_items = count
260 | if (KLD.num_items > 0) then
261 | KLD.items = items
262 | -- Only do callbacks once all items are in the list.
263 | for k, v in pairs(KLD.items) do
264 | KLD:DoCallbacks("loot_item", v)
265 | end
266 | else
267 | KLD.items = nil
268 | end
269 |
270 | KLD:DoCallbacks("end_loot_info")
271 | end
272 |
273 | local function reset_loot_target()
274 | KLD.unit_name = nil
275 | KLD.unit_guid = nil
276 | KLD.unit_realguid = false
277 | end
278 |
279 | local function populate_loot_target()
280 | local uname = UnitName("target")
281 | local uguid = UnitGUID("target")
282 | local realguid = true
283 |
284 | if (not uname or uname == "") then
285 | if (KLD.chest_name and KLD.chest_name ~= "") then
286 | uname = KLD.chest_name
287 | else
288 | uname = L["Chest"]
289 | end
290 | end
291 |
292 | if (not uguid or uguid == "") then
293 | uguid = 0
294 | realguid = false
295 | if (KLD.chest_name and KLD.chest_name ~= "") then
296 | uguid = KLD.chest_name
297 | end
298 | end
299 |
300 | KLD.unit_name = uname
301 | KLD.unit_guid = uguid
302 | KLD.unit_realguid = realguid
303 | end
304 |
305 | --
306 | -- Function: KLD.RefreshLoot()
307 | -- Purpose : Refresh the internal view of the loot items on the current
308 | -- corpse. This should only ever be called when we know that
309 | -- we have a valid corpse and that loot is not being skipped.
310 | -- Fires : ITEMS_UPDATED
311 | --
312 | function KLD.RefreshLoot()
313 | if (KLD.initialised) then
314 | return
315 | end
316 |
317 | reset_loot_target()
318 | reset_items()
319 |
320 | populate_loot_target()
321 | populate_items()
322 | end
323 |
324 | --
325 | -- Function: KLD.GiveMasterLoot(slot, target)
326 | -- Purpose : Give the loot in KLD.items[slot] to the specified target. If
327 | -- master looting is not active, or the slot is invalid, returns
328 | -- 1. If the slot and the target are valid but the target is not
329 | -- in the list of valid recipients for the item, returns 2. If
330 | -- there was no error, return 0.
331 | -- Fires : LOOT_ASSIGNED(slot, target)
332 | --
333 | function KLD.GiveMasterLoot(slot, target)
334 | if (not KLD.initialised or not KRP.is_ml or not slot or slot < 1
335 | or not target or target == "" or not KLD.items
336 | or KLD.num_items < 1 or not KLD.items[slot]) then
337 | return 1
338 | end
339 |
340 | local cand = KLD.items[slot].candidates
341 |
342 | if (not cand or not cand[target]) then
343 | return 2
344 | end
345 |
346 | GiveMasterLoot(slot, cand[target].index)
347 |
348 | KLD:DoCallbacks("loot_assigned", slot, target)
349 | return 0
350 | end
351 |
352 | local function loot_ready_evt()
353 | if (not KLD.initialised) then
354 | return
355 | end
356 |
357 | if (KLD.skip_loot) then
358 | KLD.skip_loot = nil
359 | return
360 | end
361 |
362 | reset_items()
363 | reset_loot_target()
364 |
365 | populate_loot_target()
366 | populate_items()
367 |
368 | KLD:DoCallbacks("looting_ready")
369 | end
370 |
371 | local function loot_closed_evt()
372 | if (not KLD.initialised) then
373 | return
374 | end
375 |
376 | reset_items()
377 | reset_loot_target()
378 |
379 | KLD.chest_name = nil
380 |
381 | KLD:DoCallbacks("looting_ended")
382 | end
383 |
384 | local function unit_spellcast_succeeded(evt, caster, sname, rank, tgt)
385 | if (caster == "player") then
386 | if (sname == OPENING) then
387 | KLD.chest_name = tgt
388 | return
389 | end
390 |
391 | if ((sname == disenchant_name) or (sname == herbalism_name) or
392 | (sname == mining_name)) then
393 | KLD.skip_loot = true
394 | end
395 | end
396 | end
397 |
398 | local function kld_do_refresh(evt, ...)
399 | if (not KLD.initialised) then
400 | return
401 | end
402 | KLD.RefreshLoot()
403 | KLD:DoCallbacks("looting_ready")
404 | end
405 |
406 | --
407 | -- When an addon is suspended or resumed, we need to do a refresh because
408 | -- the addon may have callbacks that have either been populated and now
409 | -- need to be removed (addon suspended) or needs to add new data via the
410 | -- callbacks (addon resumed). So we trap these two events and use them to
411 | -- schedule a refresh.
412 | --
413 | function KLD:OnActivateAddon(name, onoff)
414 | kld_do_refresh()
415 | end
416 |
417 | function KLD:OnLateInit()
418 | if (KLD.initialised) then
419 | return
420 | end
421 |
422 | KLD:RegisterEvent("LOOT_READY", loot_ready_evt)
423 | KLD:RegisterEvent("LOOT_CLOSED", loot_closed_evt)
424 | KLD:RegisterEvent("UPDATE_MASTER_LOOT_LIST", function()
425 | KLD:RefreshLoot()
426 | end)
427 | KLD:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", unit_spellcast_succeeded)
428 |
429 | KLD.initialised = true
430 | end
431 |
--------------------------------------------------------------------------------
/Libs/ChatThrottleLib.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- ChatThrottleLib by Mikk
3 | --
4 | -- Manages AddOn chat output to keep player from getting kicked off.
5 | --
6 | -- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
7 | -- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
8 | --
9 | -- Priorities get an equal share of available bandwidth when fully loaded.
10 | -- Communication channels are separated on extension+chattype+destination and
11 | -- get round-robinned. (Destination only matters for whispers and channels,
12 | -- obviously)
13 | --
14 | -- Will install hooks for SendChatMessage and SendAddonMessage to measure
15 | -- bandwidth bypassing the library and use less bandwidth itself.
16 | --
17 | --
18 | -- Fully embeddable library. Just copy this file into your addon directory,
19 | -- add it to the .toc, and it's done.
20 | --
21 | -- Can run as a standalone addon also, but, really, just embed it! :-)
22 | --
23 | -- LICENSE: ChatThrottleLib is released into the Public Domain
24 | --
25 |
26 | local CTL_VERSION = 24
27 |
28 | local _G = _G
29 |
30 | if _G.ChatThrottleLib then
31 | if _G.ChatThrottleLib.version >= CTL_VERSION then
32 | -- There's already a newer (or same) version loaded. Buh-bye.
33 | return
34 | elseif not _G.ChatThrottleLib.securelyHooked then
35 | print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, =v16) in it!")
36 | -- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
37 | -- ... and if someone has securehooked, they can kiss that goodbye too... >.<
38 | _G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
39 | if _G.ChatThrottleLib.ORIG_SendAddonMessage then
40 | _G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
41 | end
42 | end
43 | _G.ChatThrottleLib.ORIG_SendChatMessage = nil
44 | _G.ChatThrottleLib.ORIG_SendAddonMessage = nil
45 | end
46 |
47 | if not _G.ChatThrottleLib then
48 | _G.ChatThrottleLib = {}
49 | end
50 |
51 | ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
52 | local ChatThrottleLib = _G.ChatThrottleLib
53 |
54 | ChatThrottleLib.version = CTL_VERSION
55 |
56 |
57 |
58 | ------------------ TWEAKABLES -----------------
59 |
60 | ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
61 | ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
62 |
63 | ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
64 |
65 | ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
66 |
67 |
68 | local setmetatable = setmetatable
69 | local table_remove = table.remove
70 | local tostring = tostring
71 | local GetTime = GetTime
72 | local math_min = math.min
73 | local math_max = math.max
74 | local next = next
75 | local strlen = string.len
76 | local GetFramerate = GetFramerate
77 | local strlower = string.lower
78 | local unpack,type,pairs,wipe = unpack,type,pairs,wipe
79 | local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty
80 |
81 |
82 | -----------------------------------------------------------------------
83 | -- Double-linked ring implementation
84 |
85 | local Ring = {}
86 | local RingMeta = { __index = Ring }
87 |
88 | function Ring:New()
89 | local ret = {}
90 | setmetatable(ret, RingMeta)
91 | return ret
92 | end
93 |
94 | function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
95 | if self.pos then
96 | obj.prev = self.pos.prev
97 | obj.prev.next = obj
98 | obj.next = self.pos
99 | obj.next.prev = obj
100 | else
101 | obj.next = obj
102 | obj.prev = obj
103 | self.pos = obj
104 | end
105 | end
106 |
107 | function Ring:Remove(obj)
108 | obj.next.prev = obj.prev
109 | obj.prev.next = obj.next
110 | if self.pos == obj then
111 | self.pos = obj.next
112 | if self.pos == obj then
113 | self.pos = nil
114 | end
115 | end
116 | end
117 |
118 |
119 |
120 | -----------------------------------------------------------------------
121 | -- Recycling bin for pipes
122 | -- A pipe is a plain integer-indexed queue of messages
123 | -- Pipes normally live in Rings of pipes (3 rings total, one per priority)
124 |
125 | ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
126 | local PipeBin = setmetatable({}, {__mode="k"})
127 |
128 | local function DelPipe(pipe)
129 | PipeBin[pipe] = true
130 | end
131 |
132 | local function NewPipe()
133 | local pipe = next(PipeBin)
134 | if pipe then
135 | wipe(pipe)
136 | PipeBin[pipe] = nil
137 | return pipe
138 | end
139 | return {}
140 | end
141 |
142 |
143 |
144 |
145 | -----------------------------------------------------------------------
146 | -- Recycling bin for messages
147 |
148 | ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
149 | local MsgBin = setmetatable({}, {__mode="k"})
150 |
151 | local function DelMsg(msg)
152 | msg[1] = nil
153 | -- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
154 | MsgBin[msg] = true
155 | end
156 |
157 | local function NewMsg()
158 | local msg = next(MsgBin)
159 | if msg then
160 | MsgBin[msg] = nil
161 | return msg
162 | end
163 | return {}
164 | end
165 |
166 |
167 | -----------------------------------------------------------------------
168 | -- ChatThrottleLib:Init
169 | -- Initialize queues, set up frame for OnUpdate, etc
170 |
171 |
172 | function ChatThrottleLib:Init()
173 |
174 | -- Set up queues
175 | if not self.Prio then
176 | self.Prio = {}
177 | self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
178 | self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
179 | self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
180 | end
181 |
182 | -- v4: total send counters per priority
183 | for _, Prio in pairs(self.Prio) do
184 | Prio.nTotalSent = Prio.nTotalSent or 0
185 | end
186 |
187 | if not self.avail then
188 | self.avail = 0 -- v5
189 | end
190 | if not self.nTotalSent then
191 | self.nTotalSent = 0 -- v5
192 | end
193 |
194 |
195 | -- Set up a frame to get OnUpdate events
196 | if not self.Frame then
197 | self.Frame = CreateFrame("Frame")
198 | self.Frame:Hide()
199 | end
200 | self.Frame:SetScript("OnUpdate", self.OnUpdate)
201 | self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
202 | self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
203 | self.OnUpdateDelay = 0
204 | self.LastAvailUpdate = GetTime()
205 | self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
206 |
207 | -- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
208 | if not self.securelyHooked then
209 | -- Use secure hooks as of v16. Old regular hook support yanked out in v21.
210 | self.securelyHooked = true
211 | --SendChatMessage
212 | hooksecurefunc("SendChatMessage", function(...)
213 | return ChatThrottleLib.Hook_SendChatMessage(...)
214 | end)
215 | --SendAddonMessage
216 | if _G.C_ChatInfo then
217 | hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...)
218 | return ChatThrottleLib.Hook_SendAddonMessage(...)
219 | end)
220 | else
221 | hooksecurefunc("SendAddonMessage", function(...)
222 | return ChatThrottleLib.Hook_SendAddonMessage(...)
223 | end)
224 | end
225 | end
226 | self.nBypass = 0
227 | end
228 |
229 |
230 | -----------------------------------------------------------------------
231 | -- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
232 |
233 | local bMyTraffic = false
234 |
235 | function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
236 | if bMyTraffic then
237 | return
238 | end
239 | local self = ChatThrottleLib
240 | local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
241 | self.avail = self.avail - size
242 | self.nBypass = self.nBypass + size -- just a statistic
243 | end
244 | function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
245 | if bMyTraffic then
246 | return
247 | end
248 | local self = ChatThrottleLib
249 | local size = tostring(text or ""):len() + tostring(prefix or ""):len();
250 | size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
251 | self.avail = self.avail - size
252 | self.nBypass = self.nBypass + size -- just a statistic
253 | end
254 |
255 |
256 |
257 | -----------------------------------------------------------------------
258 | -- ChatThrottleLib:UpdateAvail
259 | -- Update self.avail with how much bandwidth is currently available
260 |
261 | function ChatThrottleLib:UpdateAvail()
262 | local now = GetTime()
263 | local MAX_CPS = self.MAX_CPS;
264 | local newavail = MAX_CPS * (now - self.LastAvailUpdate)
265 | local avail = self.avail
266 |
267 | if now - self.HardThrottlingBeginTime < 5 then
268 | -- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
269 | avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
270 | self.bChoking = true
271 | elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs
272 | avail = math_min(MAX_CPS, avail + newavail*0.5)
273 | self.bChoking = true -- just a statistic
274 | else
275 | avail = math_min(self.BURST, avail + newavail)
276 | self.bChoking = false
277 | end
278 |
279 | avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
280 |
281 | self.avail = avail
282 | self.LastAvailUpdate = now
283 |
284 | return avail
285 | end
286 |
287 |
288 | -----------------------------------------------------------------------
289 | -- Despooling logic
290 | -- Reminder:
291 | -- - We have 3 Priorities, each containing a "Ring" construct ...
292 | -- - ... made up of N "Pipe"s (1 for each destination/pipename)
293 | -- - and each pipe contains messages
294 |
295 | function ChatThrottleLib:Despool(Prio)
296 | local ring = Prio.Ring
297 | while ring.pos and Prio.avail > ring.pos[1].nSize do
298 | local msg = table_remove(ring.pos, 1)
299 | if not ring.pos[1] then -- did we remove last msg in this pipe?
300 | local pipe = Prio.Ring.pos
301 | Prio.Ring:Remove(pipe)
302 | Prio.ByName[pipe.name] = nil
303 | DelPipe(pipe)
304 | else
305 | Prio.Ring.pos = Prio.Ring.pos.next
306 | end
307 | local didSend=false
308 | local lowerDest = strlower(msg[3] or "")
309 | if lowerDest == "raid" and not UnitInRaid("player") then
310 | -- do nothing
311 | elseif lowerDest == "party" and not UnitInParty("player") then
312 | -- do nothing
313 | else
314 | Prio.avail = Prio.avail - msg.nSize
315 | bMyTraffic = true
316 | msg.f(unpack(msg, 1, msg.n))
317 | bMyTraffic = false
318 | Prio.nTotalSent = Prio.nTotalSent + msg.nSize
319 | DelMsg(msg)
320 | didSend = true
321 | end
322 | -- notify caller of delivery (even if we didn't send it)
323 | if msg.callbackFn then
324 | msg.callbackFn (msg.callbackArg, didSend)
325 | end
326 | -- USER CALLBACK MAY ERROR
327 | end
328 | end
329 |
330 |
331 | function ChatThrottleLib.OnEvent(this,event)
332 | -- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
333 | local self = ChatThrottleLib
334 | if event == "PLAYER_ENTERING_WORLD" then
335 | self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning
336 | self.avail = 0
337 | end
338 | end
339 |
340 |
341 | function ChatThrottleLib.OnUpdate(this,delay)
342 | local self = ChatThrottleLib
343 |
344 | self.OnUpdateDelay = self.OnUpdateDelay + delay
345 | if self.OnUpdateDelay < 0.08 then
346 | return
347 | end
348 | self.OnUpdateDelay = 0
349 |
350 | self:UpdateAvail()
351 |
352 | if self.avail < 0 then
353 | return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
354 | end
355 |
356 | -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
357 | local n = 0
358 | for prioname,Prio in pairs(self.Prio) do
359 | if Prio.Ring.pos or Prio.avail < 0 then
360 | n = n + 1
361 | end
362 | end
363 |
364 | -- Anything queued still?
365 | if n<1 then
366 | -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
367 | for prioname, Prio in pairs(self.Prio) do
368 | self.avail = self.avail + Prio.avail
369 | Prio.avail = 0
370 | end
371 | self.bQueueing = false
372 | self.Frame:Hide()
373 | return
374 | end
375 |
376 | -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
377 | local avail = self.avail/n
378 | self.avail = 0
379 |
380 | for prioname, Prio in pairs(self.Prio) do
381 | if Prio.Ring.pos or Prio.avail < 0 then
382 | Prio.avail = Prio.avail + avail
383 | if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
384 | self:Despool(Prio)
385 | -- Note: We might not get here if the user-supplied callback function errors out! Take care!
386 | end
387 | end
388 | end
389 |
390 | end
391 |
392 |
393 |
394 |
395 | -----------------------------------------------------------------------
396 | -- Spooling logic
397 |
398 | function ChatThrottleLib:Enqueue(prioname, pipename, msg)
399 | local Prio = self.Prio[prioname]
400 | local pipe = Prio.ByName[pipename]
401 | if not pipe then
402 | self.Frame:Show()
403 | pipe = NewPipe()
404 | pipe.name = pipename
405 | Prio.ByName[pipename] = pipe
406 | Prio.Ring:Add(pipe)
407 | end
408 |
409 | pipe[#pipe + 1] = msg
410 |
411 | self.bQueueing = true
412 | end
413 |
414 | function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
415 | if not self or not prio or not prefix or not text or not self.Prio[prio] then
416 | error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
417 | end
418 | if callbackFn and type(callbackFn)~="function" then
419 | error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
420 | end
421 |
422 | local nSize = text:len()
423 |
424 | if nSize>255 then
425 | error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
426 | end
427 |
428 | nSize = nSize + self.MSG_OVERHEAD
429 |
430 | -- Check if there's room in the global available bandwidth gauge to send directly
431 | if not self.bQueueing and nSize < self:UpdateAvail() then
432 | self.avail = self.avail - nSize
433 | bMyTraffic = true
434 | _G.SendChatMessage(text, chattype, language, destination)
435 | bMyTraffic = false
436 | self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
437 | if callbackFn then
438 | callbackFn (callbackArg, true)
439 | end
440 | -- USER CALLBACK MAY ERROR
441 | return
442 | end
443 |
444 | -- Message needs to be queued
445 | local msg = NewMsg()
446 | msg.f = _G.SendChatMessage
447 | msg[1] = text
448 | msg[2] = chattype or "SAY"
449 | msg[3] = language
450 | msg[4] = destination
451 | msg.n = 4
452 | msg.nSize = nSize
453 | msg.callbackFn = callbackFn
454 | msg.callbackArg = callbackArg
455 |
456 | self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
457 | end
458 |
459 |
460 | function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
461 | if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
462 | error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
463 | end
464 | if callbackFn and type(callbackFn)~="function" then
465 | error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
466 | end
467 |
468 | local nSize = text:len();
469 |
470 | if C_ChatInfo or RegisterAddonMessagePrefix then
471 | if nSize>255 then
472 | error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
473 | end
474 | else
475 | nSize = nSize + prefix:len() + 1
476 | if nSize>255 then
477 | error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
478 | end
479 | end
480 |
481 | nSize = nSize + self.MSG_OVERHEAD;
482 |
483 | -- Check if there's room in the global available bandwidth gauge to send directly
484 | if not self.bQueueing and nSize < self:UpdateAvail() then
485 | self.avail = self.avail - nSize
486 | bMyTraffic = true
487 | if _G.C_ChatInfo then
488 | _G.C_ChatInfo.SendAddonMessage(prefix, text, chattype, target)
489 | else
490 | _G.SendAddonMessage(prefix, text, chattype, target)
491 | end
492 | bMyTraffic = false
493 | self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
494 | if callbackFn then
495 | callbackFn (callbackArg, true)
496 | end
497 | -- USER CALLBACK MAY ERROR
498 | return
499 | end
500 |
501 | -- Message needs to be queued
502 | local msg = NewMsg()
503 | msg.f = _G.C_ChatInfo and _G.C_ChatInfo.SendAddonMessage or _G.SendAddonMessage
504 | msg[1] = prefix
505 | msg[2] = text
506 | msg[3] = chattype
507 | msg[4] = target
508 | msg.n = (target~=nil) and 4 or 3;
509 | msg.nSize = nSize
510 | msg.callbackFn = callbackFn
511 | msg.callbackArg = callbackArg
512 |
513 | self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
514 | end
515 |
516 |
517 |
518 |
519 | -----------------------------------------------------------------------
520 | -- Get the ball rolling!
521 |
522 | ChatThrottleLib:Init()
523 |
524 | --[[ WoWBench debugging snippet
525 | if(WOWB_VER) then
526 | local function SayTimer()
527 | print("SAY: "..GetTime().." "..arg1)
528 | end
529 | ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
530 | ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
531 | end
532 | ]]
533 |
534 |
535 |
--------------------------------------------------------------------------------
/Libs/AceHook-3.0.lua:
--------------------------------------------------------------------------------
1 | --- **AceHook-3.0** offers safe Hooking/Unhooking of functions, methods and frame scripts.
2 | -- Using AceHook-3.0 is recommended when you need to unhook your hooks again, so the hook chain isn't broken
3 | -- when you manually restore the original function.
4 | --
5 | -- **AceHook-3.0** can be embeded into your addon, either explicitly by calling AceHook:Embed(MyAddon) or by
6 | -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
7 | -- and can be accessed directly, without having to explicitly call AceHook itself.\\
8 | -- It is recommended to embed AceHook, otherwise you'll have to specify a custom `self` on all calls you
9 | -- make into AceHook.
10 | -- @class file
11 | -- @name AceHook-3.0
12 | -- @release $Id: AceHook-3.0.lua 1243 2020-10-18 00:00:19Z nevcairiel $
13 | local ACEHOOK_MAJOR, ACEHOOK_MINOR = "AceHook-3.0", 9
14 | local AceHook, oldminor = LibStub:NewLibrary(ACEHOOK_MAJOR, ACEHOOK_MINOR)
15 |
16 | if not AceHook then return end -- No upgrade needed
17 |
18 | AceHook.embeded = AceHook.embeded or {}
19 | AceHook.registry = AceHook.registry or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end })
20 | AceHook.handlers = AceHook.handlers or {}
21 | AceHook.actives = AceHook.actives or {}
22 | AceHook.scripts = AceHook.scripts or {}
23 | AceHook.onceSecure = AceHook.onceSecure or {}
24 | AceHook.hooks = AceHook.hooks or {}
25 |
26 | -- local upvalues
27 | local registry = AceHook.registry
28 | local handlers = AceHook.handlers
29 | local actives = AceHook.actives
30 | local scripts = AceHook.scripts
31 | local onceSecure = AceHook.onceSecure
32 |
33 | -- Lua APIs
34 | local pairs, next, type = pairs, next, type
35 | local format = string.format
36 | local assert, error = assert, error
37 |
38 | -- WoW APIs
39 | local issecurevariable, hooksecurefunc = issecurevariable, hooksecurefunc
40 | local _G = _G
41 |
42 | -- functions for later definition
43 | local donothing, createHook, hook
44 |
45 | local protectedScripts = {
46 | OnClick = true,
47 | }
48 |
49 | -- upgrading of embeded is done at the bottom of the file
50 |
51 | local mixins = {
52 | "Hook", "SecureHook",
53 | "HookScript", "SecureHookScript",
54 | "Unhook", "UnhookAll",
55 | "IsHooked",
56 | "RawHook", "RawHookScript"
57 | }
58 |
59 | -- AceHook:Embed( target )
60 | -- target (object) - target object to embed AceHook in
61 | --
62 | -- Embeds AceEevent into the target object making the functions from the mixins list available on target:..
63 | function AceHook:Embed( target )
64 | for k, v in pairs( mixins ) do
65 | target[v] = self[v]
66 | end
67 | self.embeded[target] = true
68 | -- inject the hooks table safely
69 | target.hooks = target.hooks or {}
70 | return target
71 | end
72 |
73 | -- AceHook:OnEmbedDisable( target )
74 | -- target (object) - target object that is being disabled
75 | --
76 | -- Unhooks all hooks when the target disables.
77 | -- this method should be called by the target manually or by an addon framework
78 | function AceHook:OnEmbedDisable( target )
79 | target:UnhookAll()
80 | end
81 |
82 | function createHook(self, handler, orig, secure, failsafe)
83 | local uid
84 | local method = type(handler) == "string"
85 | if failsafe and not secure then
86 | -- failsafe hook creation
87 | uid = function(...)
88 | if actives[uid] then
89 | if method then
90 | self[handler](self, ...)
91 | else
92 | handler(...)
93 | end
94 | end
95 | return orig(...)
96 | end
97 | -- /failsafe hook
98 | else
99 | -- all other hooks
100 | uid = function(...)
101 | if actives[uid] then
102 | if method then
103 | return self[handler](self, ...)
104 | else
105 | return handler(...)
106 | end
107 | elseif not secure then -- backup on non secure
108 | return orig(...)
109 | end
110 | end
111 | -- /hook
112 | end
113 | return uid
114 | end
115 |
116 | function donothing() end
117 |
118 | function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
119 | if not handler then handler = method end
120 |
121 | -- These asserts make sure AceHooks's devs play by the rules.
122 | assert(not script or type(script) == "boolean")
123 | assert(not secure or type(secure) == "boolean")
124 | assert(not raw or type(raw) == "boolean")
125 | assert(not forceSecure or type(forceSecure) == "boolean")
126 | assert(usage)
127 |
128 | -- Error checking Battery!
129 | if obj and type(obj) ~= "table" then
130 | error(format("%s: 'object' - nil or table expected got %s", usage, type(obj)), 3)
131 | end
132 | if type(method) ~= "string" then
133 | error(format("%s: 'method' - string expected got %s", usage, type(method)), 3)
134 | end
135 | if type(handler) ~= "string" and type(handler) ~= "function" then
136 | error(format("%s: 'handler' - nil, string, or function expected got %s", usage, type(handler)), 3)
137 | end
138 | if type(handler) == "string" and type(self[handler]) ~= "function" then
139 | error(format("%s: 'handler' - Handler specified does not exist at self[handler]", usage), 3)
140 | end
141 | if script then
142 | if not obj or not obj.GetScript or not obj:HasScript(method) then
143 | error(format("%s: You can only hook a script on a frame object", usage), 3)
144 | end
145 | if not secure and obj.IsProtected and obj:IsProtected() and protectedScripts[method] then
146 | error(format("Cannot hook secure script %q; Use SecureHookScript(obj, method, [handler]) instead.", method), 3)
147 | end
148 | else
149 | local issecure
150 | if obj then
151 | issecure = onceSecure[obj] and onceSecure[obj][method] or issecurevariable(obj, method)
152 | else
153 | issecure = onceSecure[method] or issecurevariable(method)
154 | end
155 | if issecure then
156 | if forceSecure then
157 | if obj then
158 | onceSecure[obj] = onceSecure[obj] or {}
159 | onceSecure[obj][method] = true
160 | else
161 | onceSecure[method] = true
162 | end
163 | elseif not secure then
164 | error(format("%s: Attempt to hook secure function %s. Use `SecureHook' or add `true' to the argument list to override.", usage, method), 3)
165 | end
166 | end
167 | end
168 |
169 | local uid
170 | if obj then
171 | uid = registry[self][obj] and registry[self][obj][method]
172 | else
173 | uid = registry[self][method]
174 | end
175 |
176 | if uid then
177 | if actives[uid] then
178 | -- Only two sane choices exist here. We either a) error 100% of the time or b) always unhook and then hook
179 | -- choice b would likely lead to odd debuging conditions or other mysteries so we're going with a.
180 | error(format("Attempting to rehook already active hook %s.", method))
181 | end
182 |
183 | if handlers[uid] == handler then -- turn on a decative hook, note enclosures break this ability, small memory leak
184 | actives[uid] = true
185 | return
186 | elseif obj then -- is there any reason not to call unhook instead of doing the following several lines?
187 | if self.hooks and self.hooks[obj] then
188 | self.hooks[obj][method] = nil
189 | end
190 | registry[self][obj][method] = nil
191 | else
192 | if self.hooks then
193 | self.hooks[method] = nil
194 | end
195 | registry[self][method] = nil
196 | end
197 | handlers[uid], actives[uid], scripts[uid] = nil, nil, nil
198 | uid = nil
199 | end
200 |
201 | local orig
202 | if script then
203 | orig = obj:GetScript(method) or donothing
204 | elseif obj then
205 | orig = obj[method]
206 | else
207 | orig = _G[method]
208 | end
209 |
210 | if not orig then
211 | error(format("%s: Attempting to hook a non existing target", usage), 3)
212 | end
213 |
214 | uid = createHook(self, handler, orig, secure, not (raw or secure))
215 |
216 | if obj then
217 | self.hooks[obj] = self.hooks[obj] or {}
218 | registry[self][obj] = registry[self][obj] or {}
219 | registry[self][obj][method] = uid
220 |
221 | if not secure then
222 | self.hooks[obj][method] = orig
223 | end
224 |
225 | if script then
226 | if not secure then
227 | obj:SetScript(method, uid)
228 | else
229 | obj:HookScript(method, uid)
230 | end
231 | else
232 | if not secure then
233 | obj[method] = uid
234 | else
235 | hooksecurefunc(obj, method, uid)
236 | end
237 | end
238 | else
239 | registry[self][method] = uid
240 |
241 | if not secure then
242 | _G[method] = uid
243 | self.hooks[method] = orig
244 | else
245 | hooksecurefunc(method, uid)
246 | end
247 | end
248 |
249 | actives[uid], handlers[uid], scripts[uid] = true, handler, script and true or nil
250 | end
251 |
252 | --- Hook a function or a method on an object.
253 | -- The hook created will be a "safe hook", that means that your handler will be called
254 | -- before the hooked function ("Pre-Hook"), and you don't have to call the original function yourself,
255 | -- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
256 | -- This type of hook is typically used if you need to know if some function got called, and don't want to modify it.
257 | -- @paramsig [object], method, [handler], [hookSecure]
258 | -- @param object The object to hook a method from
259 | -- @param method If object was specified, the name of the method, or the name of the function to hook.
260 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
261 | -- @param hookSecure If true, AceHook will allow hooking of secure functions.
262 | -- @usage
263 | -- -- create an addon with AceHook embeded
264 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
265 | --
266 | -- function MyAddon:OnEnable()
267 | -- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
268 | -- self:Hook("ActionButton_UpdateHotkeys", true)
269 | -- end
270 | --
271 | -- function MyAddon:ActionButton_UpdateHotkeys(button, type)
272 | -- print(button:GetName() .. " is updating its HotKey")
273 | -- end
274 | function AceHook:Hook(object, method, handler, hookSecure)
275 | if type(object) == "string" then
276 | method, handler, hookSecure, object = object, method, handler, nil
277 | end
278 |
279 | if handler == true then
280 | handler, hookSecure = nil, true
281 | end
282 |
283 | hook(self, object, method, handler, false, false, false, hookSecure or false, "Usage: Hook([object], method, [handler], [hookSecure])")
284 | end
285 |
286 | --- RawHook a function or a method on an object.
287 | -- The hook created will be a "raw hook", that means that your handler will completly replace
288 | -- the original function, and your handler has to call the original function (or not, depending on your intentions).\\
289 | -- The original function will be stored in `self.hooks[object][method]` or `self.hooks[functionName]` respectively.\\
290 | -- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
291 | -- or want to control execution of the original function.
292 | -- @paramsig [object], method, [handler], [hookSecure]
293 | -- @param object The object to hook a method from
294 | -- @param method If object was specified, the name of the method, or the name of the function to hook.
295 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
296 | -- @param hookSecure If true, AceHook will allow hooking of secure functions.
297 | -- @usage
298 | -- -- create an addon with AceHook embeded
299 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
300 | --
301 | -- function MyAddon:OnEnable()
302 | -- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
303 | -- self:RawHook("ActionButton_UpdateHotkeys", true)
304 | -- end
305 | --
306 | -- function MyAddon:ActionButton_UpdateHotkeys(button, type)
307 | -- if button:GetName() == "MyButton" then
308 | -- -- do stuff here
309 | -- else
310 | -- self.hooks.ActionButton_UpdateHotkeys(button, type)
311 | -- end
312 | -- end
313 | function AceHook:RawHook(object, method, handler, hookSecure)
314 | if type(object) == "string" then
315 | method, handler, hookSecure, object = object, method, handler, nil
316 | end
317 |
318 | if handler == true then
319 | handler, hookSecure = nil, true
320 | end
321 |
322 | hook(self, object, method, handler, false, false, true, hookSecure or false, "Usage: RawHook([object], method, [handler], [hookSecure])")
323 | end
324 |
325 | --- SecureHook a function or a method on an object.
326 | -- This function is a wrapper around the `hooksecurefunc` function in the WoW API. Using AceHook
327 | -- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
328 | -- required anymore, or the addon is being disabled.\\
329 | -- Secure Hooks should be used if the secure-status of the function is vital to its function,
330 | -- and taint would block execution. Secure Hooks are always called after the original function was called
331 | -- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
332 | -- @paramsig [object], method, [handler]
333 | -- @param object The object to hook a method from
334 | -- @param method If object was specified, the name of the method, or the name of the function to hook.
335 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
336 | function AceHook:SecureHook(object, method, handler)
337 | if type(object) == "string" then
338 | method, handler, object = object, method, nil
339 | end
340 |
341 | hook(self, object, method, handler, false, true, false, false, "Usage: SecureHook([object], method, [handler])")
342 | end
343 |
344 | --- Hook a script handler on a frame.
345 | -- The hook created will be a "safe hook", that means that your handler will be called
346 | -- before the hooked script ("Pre-Hook"), and you don't have to call the original function yourself,
347 | -- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
348 | -- This is the frame script equivalent of the :Hook safe-hook. It would typically be used to be notified
349 | -- when a certain event happens to a frame.
350 | -- @paramsig frame, script, [handler]
351 | -- @param frame The Frame to hook the script on
352 | -- @param script The script to hook
353 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
354 | -- @usage
355 | -- -- create an addon with AceHook embeded
356 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
357 | --
358 | -- function MyAddon:OnEnable()
359 | -- -- Hook the OnShow of FriendsFrame
360 | -- self:HookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
361 | -- end
362 | --
363 | -- function MyAddon:FriendsFrameOnShow(frame)
364 | -- print("The FriendsFrame was shown!")
365 | -- end
366 | function AceHook:HookScript(frame, script, handler)
367 | hook(self, frame, script, handler, true, false, false, false, "Usage: HookScript(object, method, [handler])")
368 | end
369 |
370 | --- RawHook a script handler on a frame.
371 | -- The hook created will be a "raw hook", that means that your handler will completly replace
372 | -- the original script, and your handler has to call the original script (or not, depending on your intentions).\\
373 | -- The original script will be stored in `self.hooks[frame][script]`.\\
374 | -- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
375 | -- or want to control execution of the original script.
376 | -- @paramsig frame, script, [handler]
377 | -- @param frame The Frame to hook the script on
378 | -- @param script The script to hook
379 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
380 | -- @usage
381 | -- -- create an addon with AceHook embeded
382 | -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
383 | --
384 | -- function MyAddon:OnEnable()
385 | -- -- Hook the OnShow of FriendsFrame
386 | -- self:RawHookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
387 | -- end
388 | --
389 | -- function MyAddon:FriendsFrameOnShow(frame)
390 | -- -- Call the original function
391 | -- self.hooks[frame].OnShow(frame)
392 | -- -- Do our processing
393 | -- -- .. stuff
394 | -- end
395 | function AceHook:RawHookScript(frame, script, handler)
396 | hook(self, frame, script, handler, true, false, true, false, "Usage: RawHookScript(object, method, [handler])")
397 | end
398 |
399 | --- SecureHook a script handler on a frame.
400 | -- This function is a wrapper around the `frame:HookScript` function in the WoW API. Using AceHook
401 | -- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
402 | -- required anymore, or the addon is being disabled.\\
403 | -- Secure Hooks should be used if the secure-status of the function is vital to its function,
404 | -- and taint would block execution. Secure Hooks are always called after the original function was called
405 | -- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
406 | -- @paramsig frame, script, [handler]
407 | -- @param frame The Frame to hook the script on
408 | -- @param script The script to hook
409 | -- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
410 | function AceHook:SecureHookScript(frame, script, handler)
411 | hook(self, frame, script, handler, true, true, false, false, "Usage: SecureHookScript(object, method, [handler])")
412 | end
413 |
414 | --- Unhook from the specified function, method or script.
415 | -- @paramsig [obj], method
416 | -- @param obj The object or frame to unhook from
417 | -- @param method The name of the method, function or script to unhook from.
418 | function AceHook:Unhook(obj, method)
419 | local usage = "Usage: Unhook([obj], method)"
420 | if type(obj) == "string" then
421 | method, obj = obj, nil
422 | end
423 |
424 | if obj and type(obj) ~= "table" then
425 | error(format("%s: 'obj' - expecting nil or table got %s", usage, type(obj)), 2)
426 | end
427 | if type(method) ~= "string" then
428 | error(format("%s: 'method' - expeting string got %s", usage, type(method)), 2)
429 | end
430 |
431 | local uid
432 | if obj then
433 | uid = registry[self][obj] and registry[self][obj][method]
434 | else
435 | uid = registry[self][method]
436 | end
437 |
438 | if not uid or not actives[uid] then
439 | -- Declining to error on an unneeded unhook since the end effect is the same and this would just be annoying.
440 | return false
441 | end
442 |
443 | actives[uid], handlers[uid] = nil, nil
444 |
445 | if obj then
446 | registry[self][obj][method] = nil
447 | registry[self][obj] = next(registry[self][obj]) and registry[self][obj] or nil
448 |
449 | -- if the hook reference doesnt exist, then its a secure hook, just bail out and dont do any unhooking
450 | if not self.hooks[obj] or not self.hooks[obj][method] then return true end
451 |
452 | if scripts[uid] and obj:GetScript(method) == uid then -- unhooks scripts
453 | obj:SetScript(method, self.hooks[obj][method] ~= donothing and self.hooks[obj][method] or nil)
454 | scripts[uid] = nil
455 | elseif obj and self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then -- unhooks methods
456 | obj[method] = self.hooks[obj][method]
457 | end
458 |
459 | self.hooks[obj][method] = nil
460 | self.hooks[obj] = next(self.hooks[obj]) and self.hooks[obj] or nil
461 | else
462 | registry[self][method] = nil
463 |
464 | -- if self.hooks[method] doesn't exist, then this is a SecureHook, just bail out
465 | if not self.hooks[method] then return true end
466 |
467 | if self.hooks[method] and _G[method] == uid then -- unhooks functions
468 | _G[method] = self.hooks[method]
469 | end
470 |
471 | self.hooks[method] = nil
472 | end
473 | return true
474 | end
475 |
476 | --- Unhook all existing hooks for this addon.
477 | function AceHook:UnhookAll()
478 | for key, value in pairs(registry[self]) do
479 | if type(key) == "table" then
480 | for method in pairs(value) do
481 | AceHook.Unhook(self, key, method)
482 | end
483 | else
484 | AceHook.Unhook(self, key)
485 | end
486 | end
487 | end
488 |
489 | --- Check if the specific function, method or script is already hooked.
490 | -- @paramsig [obj], method
491 | -- @param obj The object or frame to unhook from
492 | -- @param method The name of the method, function or script to unhook from.
493 | function AceHook:IsHooked(obj, method)
494 | -- we don't check if registry[self] exists, this is done by evil magicks in the metatable
495 | if type(obj) == "string" then
496 | if registry[self][obj] and actives[registry[self][obj]] then
497 | return true, handlers[registry[self][obj]]
498 | end
499 | else
500 | if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then
501 | return true, handlers[registry[self][obj][method]]
502 | end
503 | end
504 |
505 | return false, nil
506 | end
507 |
508 | --- Upgrade our old embeded
509 | for target, v in pairs( AceHook.embeded ) do
510 | AceHook:Embed( target )
511 | end
512 |
--------------------------------------------------------------------------------
/KKoreKonfer.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | KahLua Kore - loot distribution handling.
3 | WWW: http://kahluamod.com/kore
4 | Git: https://github.com/kahluamods/kore
5 | IRC: #KahLua on irc.freenode.net
6 | E-mail: me@cruciformer.com
7 |
8 | Please refer to the file LICENSE.txt for the Apache License, Version 2.0.
9 |
10 | Copyright 2008-2021 James Kean Johnston. All rights reserved.
11 |
12 | Licensed under the Apache License, Version 2.0 (the "License");
13 | you may not use this file except in compliance with the License.
14 | You may obtain a copy of the License at
15 |
16 | http://www.apache.org/licenses/LICENSE-2.0
17 |
18 | Unless required by applicable law or agreed to in writing, software
19 | distributed under the License is distributed on an "AS IS" BASIS,
20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 | See the License for the specific language governing permissions and
22 | limitations under the License.
23 | ]]
24 |
25 | local KKOREKONFER_MAJOR = "KKoreKonfer"
26 | local KKOREKONFER_MINOR = 4
27 | local KK, oldminor = LibStub:NewLibrary(KKOREKONFER_MAJOR, KKOREKONFER_MINOR)
28 |
29 | if (not KK) then
30 | return
31 | end
32 |
33 | KK.debug_id = KKOREKONFER_MAJOR
34 |
35 | local K, KM = LibStub:GetLibrary("KKore")
36 | assert(K, "KKoreKonfer requires KKore")
37 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKore r4 or later")
38 | K:RegisterExtension(KK, KKOREKONFER_MAJOR, KKOREKONFER_MINOR)
39 |
40 | local KUI, KM = LibStub:GetLibrary("KKoreUI")
41 | assert(KUI, "KKoreKonfer requires KKoreUI")
42 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKoreUI r4 or later")
43 |
44 | local H, KM = LibStub:GetLibrary("KKoreHash")
45 | assert(H, "KKoreKonfer requires KKoreHash")
46 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKoreHash r4 or later")
47 |
48 | local KRP, KM = LibStub:GetLibrary("KKoreParty")
49 | assert(KRP, "KKoreKonfer requires KKoreParty")
50 | assert(tonumber(KM) >= 4, "KKoreKonfer requires KKoreParty r4 or later")
51 |
52 | local ZL = LibStub:GetLibrary("LibDeflate")
53 | assert(ZL, "KKoreKonfer requires LibDeflate")
54 |
55 | local LS = LibStub:GetLibrary("LibSerialize")
56 | assert(LS, "KKoreKonfer requires LibSerialize")
57 |
58 | local L = LibStub("AceLocale-3.0"):GetLocale("KKore")
59 |
60 | KK.addons = {}
61 | KK.valid_callbacks = {
62 | }
63 |
64 | local printf = K.printf
65 | local tsort = table.sort
66 | local tinsert = table.insert
67 | local strfmt = string.format
68 | local strlen = string.len
69 | local strsub = string.sub
70 | local bor = bit.bor
71 | local band = bit.band
72 | local bxor = bit.bxor
73 | local lshift = bit.lshift
74 | local rshift = bit.rshift
75 | local MakeFrame= KUI.MakeFrame
76 |
77 | -- A static table with the list of possible player roles (from a konfer point
78 | -- of view - not to be confused with the in-game raid or player roles). This
79 | -- is used to restrict certain items to a particular type of raider. We also
80 | -- define constants for each role name.
81 | KK.ROLE_UNSET = 0
82 | KK.ROLE_HEALER = 1
83 | KK.ROLE_MELEE = 2
84 | KK.ROLE_RANGED = 3
85 | KK.ROLE_CASTER = 4
86 | KK.ROLE_TANK = 5
87 | KK.rolenames = {
88 | [KK.ROLE_UNSET] = L["Not Set"],
89 | [KK.ROLE_HEALER] = L["Healer"],
90 | [KK.ROLE_MELEE] = L["Melee DPS"],
91 | [KK.ROLE_RANGED] = L["Ranged DPS"],
92 | [KK.ROLE_CASTER] = L["Spellcaster"],
93 | [KK.ROLE_TANK] = L["Tank"],
94 | }
95 |
96 | -- The different configuration types supported, mainly for intra-mod comms.
97 | -- Currently there is only need for two types: guild and PUG.
98 | KK.CFGTYPE_GUILD = 1
99 | KK.CFGTYPE_PUG = 2
100 |
101 | --
102 | -- Global list of all Konfer modules.
103 | --
104 | _G["KKonfer"] = _G["KKonfer"] or {}
105 | local KKonfer = _G["KKonfer"]
106 | KKonfer["..."] = KKonfer["..."] or {}
107 |
108 | local function opens_on_loot(handle)
109 | if (not handle or handle == "") then
110 | return false
111 | end
112 |
113 | local me = KKonfer[handle]
114 | if (not me) then
115 | return false
116 | end
117 |
118 | return me.open_on_loot(handle) or false
119 | end
120 |
121 | local function check_duplicate_modules(me, insusp)
122 | local kchoice = KKonfer["..."]
123 | local tstr = strfmt("%s (v%s) - %s", me.title, me.version, me.desc)
124 |
125 | if (not insusp and kchoice.selected and kchoice.selected ~= me.handle) then
126 | KK.SetSuspended(me.handle, true)
127 | return
128 | end
129 |
130 | local nactive = 0
131 | for k,v in pairs(KKonfer) do
132 | if (k ~= "...") then
133 | if (not KK.IsSuspended(k)) then
134 | if (opens_on_loot(k)) then
135 | nactive = nactive + 1
136 | end
137 | end
138 | end
139 | end
140 |
141 | if (nactive <= 1) then
142 | return
143 | end
144 |
145 | --
146 | -- We have more than one KahLua Konfer module that is active for raids
147 | -- and set to auto-open on loot. We need to select which one is going to
148 | -- be the active one. Pop up the Konfer selection dialog.
149 | --
150 | if (insusp) then
151 | kchoice.actdialog.which:SetText(tstr)
152 | kchoice.actdialog.mod = me.handle
153 | kchoice.seldialog:Hide()
154 | kchoice.actdialog:Show()
155 | else
156 | kchoice.seldialog.RefreshList(me.party, me.raid)
157 | kchoice.actdialog:Hide()
158 | kchoice.seldialog:Show()
159 | end
160 | end
161 |
162 | function KK.IsSuspended(handle)
163 | if (not handle or handle == "") then
164 | return true
165 | end
166 |
167 | local me = KKonfer[handle]
168 | if (not me) then
169 | return true
170 | end
171 |
172 | return me.is_suspended(handle) or false
173 | end
174 |
175 | function KK.SetSuspended(handle, onoff)
176 | if (not handle or handle == "") then
177 | return
178 | end
179 |
180 | local me = KKonfer[handle]
181 | if (not me) then
182 | return
183 | end
184 |
185 | local cs = me.is_suspended(handle) or false
186 | local ts = onoff or false
187 |
188 | if (cs == ts) then
189 | return
190 | end
191 |
192 | me.set_suspended(handle, ts)
193 |
194 | local ds = L["KONFER_SUSPENDED"]
195 | if (not ts) then
196 | ds = L["KONFER_ACTIVE"]
197 | check_duplicate_modules(me, true)
198 | end
199 | K.printf(K.icolor, "%s: |cffffffff%s|r.", me.title, ds)
200 | end
201 |
202 | local function create_konfer_dialogs()
203 | local kchoice = KKonfer["..."]
204 | assert(kchoice)
205 | local ks = "|cffff2222<" .. K.KAHLUA ..">|r"
206 |
207 | local arg = {
208 | x = "CENTER", y = "MIDDLE", name = "KKonferModuleSelector",
209 | title = strfmt(L["KONFER_SEL_TITLE"], ks),
210 | canmove = true,
211 | canresize = false,
212 | escclose = true,
213 | xbutton = false,
214 | width = 450,
215 | height = 180,
216 | framelevel = 64,
217 | titlewidth = 300,
218 | border = true,
219 | blackbg = true,
220 | }
221 | kchoice.seldialog = KUI:CreateDialogFrame(arg)
222 |
223 | local ksd = kchoice.seldialog
224 |
225 | arg = {
226 | x = "CENTER", y = 0, width = 400, height = 96, autosize = false,
227 | font = "GameFontNormal",
228 | text = strfmt(L["KONFER_SEL_HEADER"], ks),
229 | }
230 | ksd.header = KUI:CreateStringLabel(arg, ksd)
231 |
232 | arg = {
233 | name = "KKonferModSelDD",
234 | x = 35, y = -105, dwidth = 350, justifyh = "CENTER", border = "THIN",
235 | mode = "SINGLE", itemheight = 16, items = KUI.emptydropdown,
236 | }
237 | ksd.seldd = KUI:CreateDropDown(arg, ksd)
238 | ksd.seldd:Catch("OnValueChanged", function(this, evt, val, usr)
239 | if (not usr) then
240 | return
241 | end
242 | local kkonfer = _G["KKonfer"]
243 | assert(kkonfer)
244 | for k,v in pairs(kkonfer) do
245 | if (k ~= "..." and k ~= val) then
246 | KK.SetSuspended(k, true)
247 | end
248 | end
249 | KK.SetSuspended(val, false)
250 | kkonfer["..."].seldialog:Hide()
251 | ksd.selected = val
252 | end)
253 |
254 | ksd.RefreshList = function(party, raid)
255 | local kkonfer = _G["KKonfer"] or {}
256 | local items = {}
257 | local kd = kkonfer["..."].seldialog.seldd
258 |
259 | tinsert(items, {
260 | text = L["KONFER_SEL_DDTITLE"], value = "", title = true,
261 | })
262 | for k,v in pairs(kkonfer) do
263 | if (k ~= "...") then
264 | if ((party and v.party) or (raid and v.raid)) then
265 | local item = {
266 | text = strfmt("%s (v%s) - %s", v.title, v.version, v.desc),
267 | value = k, checked = false,
268 | }
269 | tinsert(items, item)
270 | end
271 | end
272 | end
273 | kd:UpdateItems(items)
274 | kd:SetValue("", true)
275 | end
276 |
277 | arg = {
278 | x = "CENTER", y = "MIDDLE", name = "KKonferModuleDisable",
279 | title = strfmt(L["KONFER_SEL_TITLE"], ks),
280 | canmove = true,
281 | canresize = false,
282 | escclose = false,
283 | xbutton = false,
284 | width = 450,
285 | height = 240,
286 | framelevel = 64,
287 | titlewidth = 300,
288 | border = true,
289 | blackbg = true,
290 | okbutton = {},
291 | cancelbutton = {},
292 | }
293 | kchoice.actdialog = KUI:CreateDialogFrame(arg)
294 | kchoice.actdialog:Catch("OnAccept", function(this, evt)
295 | for k,v in pairs(KKonfer) do
296 | if (k ~= "..." and k ~= this.mod) then
297 | KK.SetSuspended(k, true)
298 | end
299 | end
300 | end)
301 |
302 | arg = {
303 | x = "CENTER", y = 0, autosize = false, border = true,
304 | width = 400, font = "GameFontHighlight", justifyh = "CENTER",
305 | }
306 | kchoice.actdialog.which = KUI:CreateStringLabel(arg, kchoice.actdialog)
307 |
308 | arg = {
309 | x = "CENTER", y = -24, width = 400, height = 128, autosize = false,
310 | font = "GameFontNormal",
311 | text = strfmt(L["KONFER_SUSPEND_OTHERS"], ks),
312 | }
313 | kchoice.actdialog.msg = KUI:CreateStringLabel(arg, kchoice.actdialog)
314 | end
315 |
316 | function KK:OnLateInit()
317 | if (self.initialised) then
318 | return
319 | end
320 |
321 | create_konfer_dialogs()
322 | self.initialised = true
323 | end
324 |
325 | function KK.TimeStamp()
326 | local tDate = date("*t")
327 | local mo = tDate["month"]
328 | local dy = tDate["day"]
329 | local yr = tDate["year"]
330 | local hh, mm = GetGameTime()
331 | return strfmt("%04d%02d%02d%02d%02d", yr, mo, dy, hh, mm), yr, mo, dy, hh, mm
332 | end
333 |
334 | function KK.CreateNewID(strtohash)
335 | local _, y, mo, d, h, m = KK.TimeStamp()
336 | local ts = strfmt("%02d%02d%02d", y-2000, mo, d)
337 | local crc = H:CRC32(ts, nil, false)
338 | crc = H:CRC32(tostring(h), crc, false)
339 | crc = H:CRC32(tostring(m), crc, false)
340 | crc = H:CRC32(strtohash, crc, true)
341 | ts = ts .. K.hexstr(crc)
342 | return ts
343 | end
344 |
345 | function KK.IsSenderMasterLooter(sender)
346 | if (KRP.in_party and KRP.master_looter and KRP.master_looter == sender) then
347 | return true
348 | end
349 | return false
350 | end
351 |
352 | function KK:OldProtoDialog()
353 | if (self.old_proto) then
354 | return
355 | end
356 |
357 | self.old_proto = true
358 |
359 | local arg = {
360 | name = self.konfer.handle .. "OldProtoDialog",
361 | x = "CENTER", y = "MIDDLE", border = true, blackbg = true,
362 | okbutton = { text = K.OK_STR }, canmove = false, canresize = false,
363 | escclose = false, width = 450, height = 100, title = self.title,
364 | }
365 | local dlg = KUI:CreateDialogFrame(arg)
366 | dlg.OnAccept = function(this)
367 | this:Hide()
368 | end
369 | dlg.OnCancel = function(this)
370 | this:Hide()
371 | end
372 |
373 | arg = {
374 | x = 8, y = -10, width = 410, height = 64, autosize = false,
375 | color = { r = 1, g = 0, b = 0, a = 1},
376 | text = self.konfer.title .. ": " .. strfmt(L["your version of %s is out of date. Please update it."], self.konfer.title),
377 | font = "GameFontNormal", justifyv = "TOP",
378 | }
379 | dlg.str1 = KUI:CreateStringLabel(arg, dlg)
380 |
381 | if (self.mainwin and self.mainwin:IsShown()) then
382 | self.mainwin:Hide()
383 | end
384 | dlg:Show()
385 | end
386 |
387 | --
388 | -- This is the function that is responsible for creating all internal addon
389 | -- messages we send. It implements all and any "protocol" we want to use to
390 | -- communicate between different instances of the addon. It has a reciprocal
391 | -- function comm_received below that should be used to decode all messages
392 | -- received by the addon. Thus, as long as these two functions can deal with
393 | -- changes between each other, pretty much any protocol can be used. For right
394 | -- now the basic protocol is that each message is always a string that begins
395 | -- with two lower case hexadecimal numbers, followed by a colon, followed
396 | -- immediately by the payload, which extends from this point to the end of the
397 | -- message.
398 | --
399 | -- The payload protocol is always a colon separated list in the form:
400 | -- command:cfgid:crc32:data
401 | --
402 | -- Each handler is called with the command, config ID, protocol version and
403 | -- then any other data.
404 | --
405 | local function send_addon_msg(self, cfg, cmd, prio, dist, target, ...)
406 | local proto = self.protocol
407 | local rcmd
408 |
409 | if (type(cmd) == "table") then
410 | proto = cmd.proto
411 | rcmd = cmd.cmd
412 | else
413 | rcmd = cmd
414 | end
415 |
416 | local serialised = LS:Serialize(...)
417 | if (not serialised) then
418 | self.debug(4, "failed to serialise data for %q", rcmd)
419 | return
420 | end
421 |
422 | local compressed = ZL:CompressDeflate(serialised, { level = 5 })
423 | if (not compressed) then
424 | self.debug(4, "failed to compress data for %q", rcmd)
425 | return
426 | end
427 |
428 | local encoded = ZL:EncodeForWoWAddonChannel(compressed)
429 | if (not encoded) then
430 | self.debug(4, "encode failed for %q", rcmd)
431 | return nil
432 | end
433 |
434 | local cfg = cfg or self.currentid or "0"
435 | local prio = prio or "ALERT"
436 | local fs = strfmt("%02x:%s:%s:", proto, rcmd, cfg)
437 | local crc = H:CRC32(fs, nil, false)
438 | crc = H:CRC32(encoded, crc, true)
439 | fs = fs .. K.hexstr(crc) .. ":" .. encoded
440 |
441 | self.debug(9, "send: dist=%s msg=%q", dist, strsub(fs, 1, 48))
442 |
443 | K:SendCommMessage(self.CHAT_MSG_PREFIX, fs, dist, target, prio)
444 | end
445 |
446 | -- Designed to process host addon's OnCommReceived with a dispatcher.
447 | local function comm_received(self, prefix, msg, dist, snd, dispatcher)
448 | local sender = K.CanonicalName(snd)
449 | if (sender == K.player.name) then
450 | return -- Ignore our own messages
451 | end
452 |
453 | self.debug(9, "recv: dist=%s snd=%s msg=%q", tostring(dist), tostring(snd), strsub(tostring(msg), 1, 48))
454 |
455 | if (dist == "UNKNOWN" and (sender ~= nil and sender ~= "")) then
456 | return
457 | end
458 |
459 | -- Create the itterator for splitting on a :
460 | local iter = gmatch(msg, "([^:]+)()")
461 |
462 | -- Get the protocol (should be 2 hex digits)
463 | local ps = iter()
464 | if (not ps) then
465 | self.debug(4, "bad msg received from %q", sender)
466 | return
467 | end
468 |
469 | local proto = tonumber(ps, 16)
470 |
471 | if (proto > self.protocol) then
472 | KK.OldProtoDialog(keg)
473 | return
474 | end
475 |
476 | -- Now get the command
477 | local cmd = iter()
478 | if (not cmd) then
479 | self.debug(4, "malformed cmd msg received from %q", sender)
480 | return
481 | end
482 |
483 | -- And the config this message is for
484 | local cfg = iter()
485 | if (not cfg) then
486 | self.debug(4, "malformed cfg msg received from %q", sender)
487 | return
488 | end
489 |
490 | -- Get the message checksum
491 | local msum, pos = iter()
492 | if (not msum) then
493 | self.debug(4, "malformed msum msg received from %q", sender)
494 | return
495 | end
496 |
497 | -- The rest of the message is the payload
498 | local data = strsub(msg, pos+1)
499 | if (not data) then
500 | self.debug(4, "malformed data msg received from %q", sender)
501 | return
502 | end
503 |
504 | local fs = strfmt("%02x:%s:%s:", proto, cmd, cfg)
505 | local crc = H:CRC32(fs, nil, false)
506 | crc = H:CRC32(data, crc, true)
507 |
508 | local mf = K.hexstr(crc)
509 |
510 | if (mf ~= msum) then
511 | local t = K.time()
512 | local n = userwarn[sender]
513 | if (n and ((n - t) >= 600)) then
514 | userwarn[sender] = nil
515 | end
516 |
517 | self.debug(1, "mismatch: cmd=%q mysum=%q theirsum=%q", tostring(cmd), tostring(mf), tostring(msum))
518 |
519 | if (not userwarn[sender]) then
520 | printf(K.ecolor, "WARNING: addon message from %q was truncated!", sender)
521 | userwarn[sender] = t
522 | end
523 | return
524 | end
525 |
526 | local decoded = ZL:DecodeForWoWAddonChannel(data)
527 | if (not decoded) then
528 | self.debug(4, "recv: decode failed for %q from %q", cmd, sender)
529 | return
530 | end
531 |
532 | local inflated = ZL:DecompressDeflate(decoded)
533 | if (not inflated) then
534 | self.debug(4, "recv: deflate failed for %q from %q", cmd, sender)
535 | return
536 | end
537 |
538 | dispatcher(self, sender, proto, cmd, cfg, LS:Deserialize(inflated))
539 | end
540 |
541 | local function send_to_raid_or_party_am_c(self, cfg, cmd, prio, ...)
542 | local cfgt = KK.CFGTYPE_PUG
543 | local cfg = cfg or self.currentid
544 |
545 | if (cfg and self.configs and self.configs[cfg]) then
546 | cfgt = self.configs[cfg].cfgtype or KK.CFGTYPE_PUG
547 | end
548 |
549 | local dist = nil
550 |
551 | if (cfgt == KK.CFGTYPE_GUILD and K.player.is_guilded) then
552 | dist = "GUILD"
553 | else
554 | if (KRP.in_party and self.konfer.party) then
555 | dist = "PARTY"
556 | end
557 |
558 | if (KRP.in_raid and self.konfer.raid) then
559 | dist = "RAID"
560 | end
561 | end
562 |
563 | if (not dist) then
564 | return
565 | end
566 |
567 | send_addon_msg(self, cfg, cmd, prio, dist, nil, ...)
568 | end
569 |
570 | local function send_to_raid_or_party_am(self, cmd, prio, ...)
571 | send_to_raid_or_party_am_c(self, nil, cmd, prio, ...)
572 | end
573 |
574 | local function send_to_guild_am_c(self, cfg, cmd, prio, ...)
575 | if (K.player.is_guilded) then
576 | send_addon_msg(self, cfg, cmd, prio, "GUILD", nil, ...)
577 | end
578 | end
579 |
580 | local function send_to_guild_am(self, cmd, prio, ...)
581 | if (K.player.is_guilded) then
582 | send_addon_msg(self, nil, cmd, prio, "GUILD", nil, ...)
583 | end
584 | end
585 |
586 | local function send_whisper_am_c(self, cfg, target, cmd, prio, ...)
587 | send_addon_msg(self, cfg, cmd, prio, "WHISPER", target, ...)
588 | end
589 |
590 | local function send_whisper_am(self, target, cmd, prio, ...)
591 | send_addon_msg(self, nil, cmd, prio, "WHISPER", target, ...)
592 | end
593 |
594 | local function send_plain_message(self, text)
595 | if (not KRP.in_party) then
596 | return
597 | end
598 |
599 | local dist = "PARTY"
600 | if (KRP.in_raid) then
601 | dist = "RAID"
602 | end
603 |
604 | SendChatMessage(text, dist)
605 | end
606 |
607 | local function send_guild_message(self, text)
608 | if (not K.player.is_guilded) then
609 | return
610 | end
611 | SendChatMessage(text, "GUILD")
612 | end
613 |
614 | local function send_whisper_message(self, text, target)
615 | SendChatMessage(text, "WHISPER", nil, target)
616 | end
617 |
618 | local function send_raid_warning(self, text)
619 | if (KRP.in_raid) then
620 | if (KRP.is_aorl) then
621 | SendChatMessage(text, "RAID_WARNING")
622 | else
623 | SendChatMessage("{skull}{skull} " .. text .. " {skull}{skull}", "RAID")
624 | end
625 | else
626 | SendChatMessage("{skull}{skull} " .. text .. " {skull}{skull}", "PARTY")
627 | end
628 | end
629 |
630 | local userwarn = userwarn or {}
631 |
632 | --
633 | -- Shared dialog for version checks.
634 | --
635 | local function vlist_newitem(objp, num)
636 | local kk = objp:GetParent():GetParent():GetParent().kkmod
637 | local bname = kk.konfer.handle .. "KKVCheckListButton" .. tostring(num)
638 | local rf = MakeFrame("Button", bname, objp.content)
639 | local nfn = "GameFontNormalSmallLeft"
640 | local hfn = "GameFontHighlightSmallLeft"
641 | local htn = "Interface/QuestFrame/UI-QuestTitleHighlight"
642 |
643 | rf:SetWidth(325)
644 | rf:SetHeight(16)
645 | rf:SetHighlightTexture(htn, "ADD")
646 |
647 | local who = rf:CreateFontString(nil, "BORDER", nfn)
648 | who:ClearAllPoints()
649 | who:SetPoint("TOPLEFT", rf, "TOPLEFT", 0, -2)
650 | who:SetPoint("BOTTOMLEFT", rf, "BOTTOMLEFT", 0, -2)
651 | who:SetWidth(168)
652 | who:SetJustifyH("LEFT")
653 | who:SetJustifyV("TOP")
654 | rf.who = who
655 |
656 | local version = rf:CreateFontString(nil, "BORDER", nfn)
657 | version:ClearAllPoints()
658 | version:SetPoint("TOPLEFT", who, "TOPRIGHT", 4, 0)
659 | version:SetPoint("BOTTOMLEFT", who, "BOTTOMRIGHT", 4, 0)
660 | version:SetWidth(95)
661 | version:SetJustifyH("LEFT")
662 | version:SetJustifyV("TOP")
663 | rf.version = version
664 |
665 | local raid = rf:CreateFontString(nil, "BORDER", nfn)
666 | raid:ClearAllPoints()
667 | raid:SetPoint("TOPLEFT", version, "TOPRIGHT", 4, 0)
668 | raid:SetPoint("BOTTOMLEFT", version, "BOTTOMRIGHT", 4, 0)
669 | raid:SetWidth(50)
670 | raid:SetJustifyH("LEFT")
671 | raid:SetJustifyV("TOP")
672 | rf.raid = raid
673 |
674 | rf.SetText = function(self, who, vers, raid)
675 | self.who:SetText(who)
676 | self.version:SetText(vers)
677 | if (raid) then
678 | self.raid:SetText(K.YES_STR)
679 | else
680 | self.raid:SetText(K.NO_STR)
681 | end
682 | end
683 |
684 | return rf
685 | end
686 |
687 | local function vlist_setitem(objp, idx, slot, btn)
688 | local kk = objp:GetParent():GetParent():GetParent().kkmod
689 | if (not kk or not kk.vcdlg or not kk.vcdlg.vcreplies) then
690 | return
691 | end
692 |
693 | local vcent = kk.vcdlg.vcreplies[idx]
694 | if (not vcent) then
695 | return
696 | end
697 | local name = kk.shortaclass(vcent)
698 | local vers = tonumber(vcent.version)
699 | local fn = kk.green
700 | if (vers < kk.version) then
701 | fn = kk.red
702 | end
703 |
704 | btn:SetText(name, fn(tostring(vers)), vcent.raid)
705 | btn:SetID(idx)
706 | btn:Show()
707 | end
708 |
709 | local function sort_vcreplies(self)
710 | tsort(self.vcdlg.vcreplies, function(a, b)
711 | if (a.raid and not b.raid) then
712 | return true
713 | end
714 | if (b.raid and not a.raid) then
715 | return false
716 | end
717 | if (a.version < b.version) then
718 | return true
719 | end
720 | if (b.version < a.version) then
721 | return false
722 | end
723 | return strlower(a.name) < strlower(b.name)
724 | end)
725 | self.vcdlg.slist.itemcount = #self.vcdlg.vcreplies
726 | self.vcdlg.slist:UpdateList()
727 | end
728 |
729 | local function kk_version_check(self)
730 | local vcdlg = self.vcdlg
731 | if (not vcdlg) then
732 | local ks = "|cffff2222<" .. K.KAHLUA ..">|r"
733 | local arg = {
734 | x = "CENTER", y = "MIDDLE",
735 | name = self.konfer.handle .. "KKVersionCheck",
736 | title = strfmt(L["VCTITLE"], ks, self.konfer.title),
737 | canmove = true,
738 | canresize = false,
739 | escclose = true,
740 | xbutton = false,
741 | width = 400,
742 | height = 350,
743 | framelevel = 64,
744 | titlewidth = 270,
745 | border = true,
746 | blackbg = true,
747 | okbutton = { text = K.OK_STR },
748 | }
749 | vcdlg = KUI:CreateDialogFrame(arg)
750 | vcdlg.kkmod = self
751 |
752 | vcdlg.OnAccept = function(this)
753 | this:Hide()
754 | if (this.mainshown) then
755 | this.kkmod.mainwin:Show()
756 | end
757 | this.mainshown = nil
758 | this.vcreplies = nil
759 | end
760 | vcdlg.OnCancel = vcdlg.OnAccept
761 |
762 | arg = {
763 | x = 5, y = 0, text = L["Who"], font = "GameFontNormal",
764 | }
765 | vcdlg.str1 = KUI:CreateStringLabel(arg, vcdlg)
766 |
767 | arg.x = 175
768 | arg.text = L["Version"]
769 | vcdlg.str2 = KUI:CreateStringLabel(arg, vcdlg)
770 |
771 | arg.x = 275
772 | arg.text = L["In Raid"]
773 | vcdlg.str3 = KUI:CreateStringLabel(arg, vcdlg)
774 |
775 | vcdlg.sframe = MakeFrame("Frame", nil, vcdlg.content)
776 | vcdlg.sframe:ClearAllPoints()
777 | vcdlg.sframe:SetPoint("TOPLEFT", vcdlg.content, "TOPLEFT", 5, -18)
778 | vcdlg.sframe:SetPoint("BOTTOMRIGHT", vcdlg.content, "BOTTOMRIGHT", 0, 0)
779 |
780 |
781 | arg = {
782 | name = self.konfer.handle .. "KKVersionScrollList",
783 | itemheight = 16, newitem = vlist_newitem, setitem = vlist_setitem,
784 | selectitem = function(objp, idx, slot, btn, onoff) return end,
785 | highlightitem = function(objp, idx, slot, btn, onoff)
786 | return KUI.HighlightItemHelper(objp, idx, slot, btn, onoff)
787 | end,
788 | }
789 | vcdlg.slist = KUI:CreateScrollList(arg, vcdlg.sframe)
790 |
791 | self.vcdlg = vcdlg
792 | end
793 |
794 | --
795 | -- Populate the expected replies with all current raid members and if we
796 | -- are in a guild, with all currently online guild members. We set the
797 | -- version to 0 to indicate no reply yet. As replies come in we change the
798 | -- version number and re-sort and refresh the list.
799 | --
800 |
801 | vcdlg.vcreplies = {}
802 |
803 | if (KRP.players) then
804 | for k, v in pairs(KRP.players) do
805 | local vce = { name = k, class = v.class, version = 0, raid = true }
806 | if (k == K.player.name) then
807 | vce.version = self.version
808 | end
809 | tinsert(vcdlg.vcreplies, vce)
810 | end
811 | end
812 |
813 | if (K.player.is_guilded) then
814 | for k, v in pairs(K.guild.roster.id) do
815 | if ((not KRP.players or not KRP.players[v.name]) and v.online) then
816 | local vce = { name = v.name, class = v.class, version = 0, raid = false }
817 | if (v.name == K.player.name) then
818 | vce.version = self.version
819 | end
820 | tinsert(vcdlg.vcreplies, vce)
821 | end
822 | end
823 | end
824 |
825 | sort_vcreplies(self)
826 |
827 | vcdlg.mainshown = self.mainwin:IsShown()
828 | self.mainwin:Hide()
829 | vcdlg:Show()
830 |
831 | self:SendAM({proto = 2, cmd = "VCHEK"}, nil)
832 | if (K.player.is_guilded) then
833 | self:SendGuildAM({proto = 2, cmd = "VCHEK"}, nil)
834 | end
835 | end
836 |
837 | local function kk_version_check_reply(self, sender, version)
838 | if (not self.vcdlg or not self.vcdlg.vcreplies) then
839 | return
840 | end
841 |
842 | for k, v in pairs(self.vcdlg.vcreplies) do
843 | if (v.name == sender) then
844 | v.version = version
845 | sort_vcreplies(self)
846 | return
847 | end
848 | end
849 | end
850 |
851 | --
852 | -- Register a new addon (like KSK) with the base Konfer system. The single
853 | -- argument to this function is a table with various parameters, as described
854 | -- below. Returns a handle to the mod, which is a table.
855 | --
856 | function KK.RegisterKonfer(kmod)
857 | local targ = kmod.konfer
858 | if (not targ or type(targ) ~= "table") then
859 | error("Invalid call to RegisterKonfer.", 2)
860 | end
861 |
862 | local me = KKonfer[targ.handle]
863 | if (me ~= nil) then
864 | return me
865 | end
866 |
867 | assert(kmod.protocol)
868 |
869 | kmod.konfer = targ
870 | kmod.CSendAM = send_to_raid_or_party_am_c
871 | kmod.SendAM = send_to_raid_or_party_am
872 | kmod.CSendGuildAM = send_to_guild_am_c
873 | kmod.SendGuildAM = send_to_guild_am
874 | kmod.CSendWhisperAM = send_whisper_am_c
875 | kmod.SendWhisperAM = send_whisper_am
876 | kmod.SendText = send_plain_message
877 | kmod.SendGuildText = send_guild_message
878 | kmod.SendWhisper = send_whisper_message
879 | kmod.SendWarning = send_raid_warning
880 | kmod.VersionCheck = kk_version_check
881 | kmod.VersionCheckReply = kk_version_check_reply
882 | kmod.KonferCommReceived = comm_received
883 |
884 | KKonfer[targ.handle] = targ
885 |
886 | check_duplicate_modules(targ, false)
887 | end
888 |
--------------------------------------------------------------------------------