(feat/libs): update Libraries (#49)
This commit is contained in:
@@ -1,250 +0,0 @@
|
||||
--- **AceConsole-3.0** provides registration facilities for slash commands.
|
||||
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
|
||||
-- to your addons individual needs.
|
||||
--
|
||||
-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceConsole itself.\\
|
||||
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
|
||||
-- make into AceConsole.
|
||||
-- @class file
|
||||
-- @name AceConsole-3.0
|
||||
-- @release $Id$
|
||||
local MAJOR,MINOR = "AceConsole-3.0", 7
|
||||
|
||||
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceConsole then return end -- No upgrade needed
|
||||
|
||||
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
|
||||
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
|
||||
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
|
||||
|
||||
-- Lua APIs
|
||||
local tconcat, tostring, select = table.concat, tostring, select
|
||||
local type, pairs, error = type, pairs, error
|
||||
local format, strfind, strsub = string.format, string.find, string.sub
|
||||
local max = math.max
|
||||
|
||||
-- WoW APIs
|
||||
local _G = _G
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
|
||||
|
||||
local tmp={}
|
||||
local function Print(self,frame,...)
|
||||
local n=0
|
||||
if self ~= AceConsole then
|
||||
n=n+1
|
||||
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
|
||||
end
|
||||
for i=1, select("#", ...) do
|
||||
n=n+1
|
||||
tmp[n] = tostring(select(i, ...))
|
||||
end
|
||||
frame:AddMessage( tconcat(tmp," ",1,n) )
|
||||
end
|
||||
|
||||
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||
-- @paramsig [chatframe ,] ...
|
||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||
-- @param ... List of any values to be printed
|
||||
function AceConsole:Print(...)
|
||||
local frame = ...
|
||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||
return Print(self, frame, select(2,...))
|
||||
else
|
||||
return Print(self, DEFAULT_CHAT_FRAME, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||
-- @paramsig [chatframe ,] "format"[, ...]
|
||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||
-- @param format Format string - same syntax as standard Lua format()
|
||||
-- @param ... Arguments to the format string
|
||||
function AceConsole:Printf(...)
|
||||
local frame = ...
|
||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||
return Print(self, frame, format(select(2,...)))
|
||||
else
|
||||
return Print(self, DEFAULT_CHAT_FRAME, format(...))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Register a simple chat command
|
||||
-- @param command Chat command to be registered WITHOUT leading "/"
|
||||
-- @param func Function to call when the slash command is being used (funcref or methodname)
|
||||
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
|
||||
function AceConsole:RegisterChatCommand( command, func, persist )
|
||||
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
|
||||
|
||||
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
|
||||
|
||||
local name = "ACECONSOLE_"..command:upper()
|
||||
|
||||
if type( func ) == "string" then
|
||||
SlashCmdList[name] = function(input, editBox)
|
||||
self[func](self, input, editBox)
|
||||
end
|
||||
else
|
||||
SlashCmdList[name] = func
|
||||
end
|
||||
_G["SLASH_"..name.."1"] = "/"..command:lower()
|
||||
AceConsole.commands[command] = name
|
||||
-- non-persisting commands are registered for enabling disabling
|
||||
if not persist then
|
||||
if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end
|
||||
AceConsole.weakcommands[self][command] = func
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Unregister a chatcommand
|
||||
-- @param command Chat command to be unregistered WITHOUT leading "/"
|
||||
function AceConsole:UnregisterChatCommand( command )
|
||||
local name = AceConsole.commands[command]
|
||||
if name then
|
||||
SlashCmdList[name] = nil
|
||||
_G["SLASH_" .. name .. "1"] = nil
|
||||
hash_SlashCmdList["/" .. command:upper()] = nil
|
||||
AceConsole.commands[command] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get an iterator over all Chat Commands registered with AceConsole
|
||||
-- @return Iterator (pairs) over all commands
|
||||
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
|
||||
|
||||
|
||||
local function nils(n, ...)
|
||||
if n>1 then
|
||||
return nil, nils(n-1, ...)
|
||||
elseif n==1 then
|
||||
return nil, ...
|
||||
else
|
||||
return ...
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Retreive one or more space-separated arguments from a string.
|
||||
-- Treats quoted strings and itemlinks as non-spaced.
|
||||
-- @param str The raw argument string
|
||||
-- @param numargs How many arguments to get (default 1)
|
||||
-- @param startpos Where in the string to start scanning (default 1)
|
||||
-- @return Returns arg1, arg2, ..., nextposition\\
|
||||
-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string.
|
||||
function AceConsole:GetArgs(str, numargs, startpos)
|
||||
numargs = numargs or 1
|
||||
startpos = max(startpos or 1, 1)
|
||||
|
||||
local pos=startpos
|
||||
|
||||
-- find start of new arg
|
||||
pos = strfind(str, "[^ ]", pos)
|
||||
if not pos then -- whoops, end of string
|
||||
return nils(numargs, 1e9)
|
||||
end
|
||||
|
||||
if numargs<1 then
|
||||
return pos
|
||||
end
|
||||
|
||||
-- quoted or space separated? find out which pattern to use
|
||||
local delim_or_pipe
|
||||
local ch = strsub(str, pos, pos)
|
||||
if ch=='"' then
|
||||
pos = pos + 1
|
||||
delim_or_pipe='([|"])'
|
||||
elseif ch=="'" then
|
||||
pos = pos + 1
|
||||
delim_or_pipe="([|'])"
|
||||
else
|
||||
delim_or_pipe="([| ])"
|
||||
end
|
||||
|
||||
startpos = pos
|
||||
|
||||
while true do
|
||||
-- find delimiter or hyperlink
|
||||
local ch,_
|
||||
pos,_,ch = strfind(str, delim_or_pipe, pos)
|
||||
|
||||
if not pos then break end
|
||||
|
||||
if ch=="|" then
|
||||
-- some kind of escape
|
||||
|
||||
if strsub(str,pos,pos+1)=="|H" then
|
||||
-- It's a |H....|hhyper link!|h
|
||||
pos=strfind(str, "|h", pos+2) -- first |h
|
||||
if not pos then break end
|
||||
|
||||
pos=strfind(str, "|h", pos+2) -- second |h
|
||||
if not pos then break end
|
||||
elseif strsub(str,pos, pos+1) == "|T" then
|
||||
-- It's a |T....|t texture
|
||||
pos=strfind(str, "|t", pos+2)
|
||||
if not pos then break end
|
||||
end
|
||||
|
||||
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
|
||||
|
||||
else
|
||||
-- found delimiter, done with this arg
|
||||
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
|
||||
return strsub(str, startpos), nils(numargs-1, 1e9)
|
||||
end
|
||||
|
||||
|
||||
--- embedding and embed handling
|
||||
|
||||
local mixins = {
|
||||
"Print",
|
||||
"Printf",
|
||||
"RegisterChatCommand",
|
||||
"UnregisterChatCommand",
|
||||
"GetArgs",
|
||||
}
|
||||
|
||||
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
|
||||
-- @param target target object to embed AceBucket in
|
||||
function AceConsole:Embed( target )
|
||||
for k, v in pairs( mixins ) do
|
||||
target[v] = self[v]
|
||||
end
|
||||
self.embeds[target] = true
|
||||
return target
|
||||
end
|
||||
|
||||
function AceConsole:OnEmbedEnable( target )
|
||||
if AceConsole.weakcommands[target] then
|
||||
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
||||
target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AceConsole:OnEmbedDisable( target )
|
||||
if AceConsole.weakcommands[target] then
|
||||
for command, func in pairs( AceConsole.weakcommands[target] ) do
|
||||
target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for addon in pairs(AceConsole.embeds) do
|
||||
AceConsole:Embed(addon)
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceConsole-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -1,126 +0,0 @@
|
||||
--- AceEvent-3.0 provides event registration and secure dispatching.
|
||||
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
|
||||
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
|
||||
--
|
||||
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
|
||||
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
|
||||
-- make into AceEvent.
|
||||
-- @class file
|
||||
-- @name AceEvent-3.0
|
||||
-- @release $Id$
|
||||
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
||||
|
||||
local MAJOR, MINOR = "AceEvent-3.0", 4
|
||||
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceEvent then return end
|
||||
|
||||
-- Lua APIs
|
||||
local pairs = pairs
|
||||
|
||||
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
||||
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
||||
|
||||
-- APIs and registry for blizzard events, using CallbackHandler lib
|
||||
if not AceEvent.events then
|
||||
AceEvent.events = CallbackHandler:New(AceEvent,
|
||||
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
||||
end
|
||||
|
||||
function AceEvent.events:OnUsed(target, eventname)
|
||||
AceEvent.frame:RegisterEvent(eventname)
|
||||
end
|
||||
|
||||
function AceEvent.events:OnUnused(target, eventname)
|
||||
AceEvent.frame:UnregisterEvent(eventname)
|
||||
end
|
||||
|
||||
|
||||
-- APIs and registry for IPC messages, using CallbackHandler lib
|
||||
if not AceEvent.messages then
|
||||
AceEvent.messages = CallbackHandler:New(AceEvent,
|
||||
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
||||
)
|
||||
AceEvent.SendMessage = AceEvent.messages.Fire
|
||||
end
|
||||
|
||||
--- embedding and embed handling
|
||||
local mixins = {
|
||||
"RegisterEvent", "UnregisterEvent",
|
||||
"RegisterMessage", "UnregisterMessage",
|
||||
"SendMessage",
|
||||
"UnregisterAllEvents", "UnregisterAllMessages",
|
||||
}
|
||||
|
||||
--- Register for a Blizzard Event.
|
||||
-- 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)
|
||||
-- Any arguments to the event will be passed on after that.
|
||||
-- @name AceEvent:RegisterEvent
|
||||
-- @class function
|
||||
-- @paramsig event[, callback [, arg]]
|
||||
-- @param event The event to register for
|
||||
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name)
|
||||
-- @param arg An optional argument to pass to the callback function
|
||||
|
||||
--- Unregister an event.
|
||||
-- @name AceEvent:UnregisterEvent
|
||||
-- @class function
|
||||
-- @paramsig event
|
||||
-- @param event The event to unregister
|
||||
|
||||
--- Register for a custom AceEvent-internal message.
|
||||
-- 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)
|
||||
-- Any arguments to the event will be passed on after that.
|
||||
-- @name AceEvent:RegisterMessage
|
||||
-- @class function
|
||||
-- @paramsig message[, callback [, arg]]
|
||||
-- @param message The message to register for
|
||||
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name)
|
||||
-- @param arg An optional argument to pass to the callback function
|
||||
|
||||
--- Unregister a message
|
||||
-- @name AceEvent:UnregisterMessage
|
||||
-- @class function
|
||||
-- @paramsig message
|
||||
-- @param message The message to unregister
|
||||
|
||||
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message.
|
||||
-- @name AceEvent:SendMessage
|
||||
-- @class function
|
||||
-- @paramsig message, ...
|
||||
-- @param message The message to send
|
||||
-- @param ... Any arguments to the message
|
||||
|
||||
|
||||
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:..
|
||||
-- @param target target object to embed AceEvent in
|
||||
function AceEvent:Embed(target)
|
||||
for k, v in pairs(mixins) do
|
||||
target[v] = self[v]
|
||||
end
|
||||
self.embeds[target] = true
|
||||
return target
|
||||
end
|
||||
|
||||
-- AceEvent:OnEmbedDisable( target )
|
||||
-- target (object) - target object that is being disabled
|
||||
--
|
||||
-- Unregister all events messages etc when the target disables.
|
||||
-- this method should be called by the target manually or by an addon framework
|
||||
function AceEvent:OnEmbedDisable(target)
|
||||
target:UnregisterAllEvents()
|
||||
target:UnregisterAllMessages()
|
||||
end
|
||||
|
||||
-- Script to fire blizzard events into the event listeners
|
||||
local events = AceEvent.events
|
||||
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
|
||||
events:Fire(event, ...)
|
||||
end)
|
||||
|
||||
--- Finally: upgrade our old embeds
|
||||
for target, v in pairs(AceEvent.embeds) do
|
||||
AceEvent:Embed(target)
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceEvent-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -10,8 +10,8 @@
|
||||
-- make into AceSerializer.
|
||||
-- @class file
|
||||
-- @name AceSerializer-3.0
|
||||
-- @release $Id$
|
||||
local MAJOR,MINOR = "AceSerializer-3.0", 3
|
||||
-- @release $Id: AceSerializer-3.0.lua 1284 2022-09-25 09:15:30Z nevcairiel $
|
||||
local MAJOR,MINOR = "AceSerializer-3.0", 5
|
||||
local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceSerializer then return end
|
||||
@@ -24,9 +24,11 @@ local pairs, select, frexp = pairs, select, math.frexp
|
||||
local tconcat = table.concat
|
||||
|
||||
-- quick copies of string representations of wonky numbers
|
||||
local serNaN = tostring(0/0)
|
||||
local serInf = tostring(1/0)
|
||||
local serNegInf = tostring(-1/0)
|
||||
local inf = math.huge
|
||||
|
||||
local serNaN = "-1.#IND"
|
||||
local serInf, serInfMac = "1.#INF", "inf"
|
||||
local serNegInf, serNegInfMac = "-1.#INF", "-inf"
|
||||
|
||||
|
||||
-- Serialization functions
|
||||
@@ -60,11 +62,15 @@ local function SerializeValue(v, res, nres)
|
||||
|
||||
elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
|
||||
local str = tostring(v)
|
||||
if tonumber(str)==v or str==serNaN or str==serInf or str==serNegInf then
|
||||
if tonumber(str)==v or str==serNaN then
|
||||
-- translates just fine, transmit as-is
|
||||
res[nres+1] = "^N"
|
||||
res[nres+2] = str
|
||||
nres=nres+2
|
||||
elseif v == inf or v == -inf then
|
||||
res[nres+1] = "^N"
|
||||
res[nres+2] = v == inf and serInf or serNegInf
|
||||
nres=nres+2
|
||||
else
|
||||
local m,e = frexp(v)
|
||||
res[nres+1] = "^F"
|
||||
@@ -77,9 +83,9 @@ local function SerializeValue(v, res, nres)
|
||||
elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
|
||||
nres=nres+1
|
||||
res[nres] = "^T"
|
||||
for k,v in pairs(v) do
|
||||
nres = SerializeValue(k, res, nres)
|
||||
nres = SerializeValue(v, res, nres)
|
||||
for key,value in pairs(v) do
|
||||
nres = SerializeValue(key, res, nres)
|
||||
nres = SerializeValue(value, res, nres)
|
||||
end
|
||||
nres=nres+1
|
||||
res[nres] = "^t"
|
||||
@@ -145,10 +151,10 @@ end
|
||||
local function DeserializeNumberHelper(number)
|
||||
if number == serNaN then
|
||||
return 0/0
|
||||
elseif number == serNegInf then
|
||||
return -1/0
|
||||
elseif number == serInf then
|
||||
return 1/0
|
||||
elseif number == serNegInf or number == serNegInfMac then
|
||||
return -inf
|
||||
elseif number == serInf or number == serInfMac then
|
||||
return inf
|
||||
else
|
||||
return tonumber(number)
|
||||
end
|
||||
|
||||
@@ -15,10 +15,10 @@ local LibDeflate = LibStub("LibDeflate")
|
||||
|
||||
do -- boilerplate & static values
|
||||
Archivist.buildDate = "@build-time@"
|
||||
Archivist.version = "5e673bb"
|
||||
--[===[@debug@
|
||||
Archivist.version = "v1.0.8"
|
||||
--[==[@debug@
|
||||
Archivist.debug = true
|
||||
--@end-debug@]===]
|
||||
--@end-debug@]==]
|
||||
|
||||
Archivist.prototypes = {}
|
||||
Archivist.storeMap = {}
|
||||
@@ -143,7 +143,8 @@ function Archivist:RegisterStoreType(prototype)
|
||||
Update = prototype.Update,
|
||||
Open = prototype.Open,
|
||||
Commit = prototype.Commit,
|
||||
Close = prototype.Close
|
||||
Close = prototype.Close,
|
||||
Delete = prototype.Delete
|
||||
}
|
||||
self.activeStores[prototype.id] = self.activeStores[prototype.id] or {}
|
||||
if self:IsInitialized() then
|
||||
@@ -279,10 +280,10 @@ function Archivist:Delete(storeType, id, force)
|
||||
end
|
||||
|
||||
if id and storeType and self.sv[storeType] then
|
||||
if self.prototypes[id] and self.prototypes[id].Delete then
|
||||
if self.prototypes[storeType] and self.prototypes[storeType].Delete and self.sv[storeType][id] then
|
||||
local image = self.activeStores[storeType][id]
|
||||
and self:Close(self.activeStores[storeType][id])
|
||||
or self:Dearchive(self.sv[storeType][id])
|
||||
or self:DeArchive(self.sv[storeType][id].data)
|
||||
self.prototypes[storeType]:Delete(image)
|
||||
end
|
||||
self.sv[storeType][id] = nil
|
||||
@@ -335,7 +336,7 @@ end
|
||||
-- Don't say I didn't warn you
|
||||
function Archivist:DeleteAll(storeType)
|
||||
if storeType then
|
||||
self.sv[storeType] = nil
|
||||
self.sv[storeType] = {}
|
||||
for id, store in pairs(self.activeStores[storeType]) do
|
||||
self.activeStores[storeType][id] = nil
|
||||
self.storeMap[store] = nil
|
||||
@@ -345,8 +346,8 @@ function Archivist:DeleteAll(storeType)
|
||||
self.sv[id] = {}
|
||||
self.activeStores[id] = {}
|
||||
end
|
||||
self.storeMap = {}
|
||||
end
|
||||
self.storeMap = {}
|
||||
end
|
||||
|
||||
-- deactivates store, with one last opportunity to commit data if the prototype chooses to do so
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
## Interface: 80300
|
||||
## Interface: 90005
|
||||
## Interface-Classic: 11307
|
||||
## Interface-BC: 20501
|
||||
## Title: Archivist
|
||||
## Author: emptyrivers
|
||||
## Version: 5e673bb
|
||||
## Version: v1.0.8
|
||||
## Notes: Flexible data archive.
|
||||
## SavedVariables: ACHV_DB
|
||||
## X-Curse-Project-ID: 354259
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
# Archivist
|
||||
|
||||
Archivist is a flexible data storage service for WoW AddOns. It is designed especially for addons which need to store a large amount of data, but only occasionally read or update this data. Data given to the archivist is stored in SavedVariables in a compressed format, to minimize addon load time.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Archivist](#archivist)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Using Archivist](#using-archivist)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Included Store Types](#included-store-types)
|
||||
- [Embedding the Archivist](#embedding-the-archivist)
|
||||
- [Custom Store Types](#custom-store-types)
|
||||
- [Store Type Methods Should be Functional](#store-type-methods-should-be-functional)
|
||||
- [Re-registering prototypes](#re-registering-prototypes)
|
||||
- [Performance](#performance)
|
||||
- [Full API List](#full-api-list)
|
||||
- [Limitations and "Gotchas"](#limitations-and-%22gotchas%22)
|
||||
- [Archivist Modifies Your Addon's Namespace](#archivist-modifies-your-addons-namespace)
|
||||
- [Archivist Can't Store Everything](#archivist-cant-store-everything)
|
||||
- [Archivist is Not a Library](#archivist-is-not-a-library)
|
||||
|
||||
## Using Archivist
|
||||
|
||||
Using the archivist is very simple. If you just wish to use the global archive, then install Archivist as a standalone addon. Note that if you use the global archive, it is recommended to set Archivist as a dependency in your .toc file.
|
||||
|
||||
### Getting Started
|
||||
|
||||
First, load your data store from the archive:
|
||||
|
||||
```lua
|
||||
|
||||
-- Load data from the archive. If the RawData store MyAddonArchive doesn't exist in the archive, then it is created automatically. Usually you will use this to obtain your store.
|
||||
myStore = Archivist:Load("RawData", "MyAddonArchive")
|
||||
|
||||
-- if you have good reason to believe that your store is archived, then you can use open:
|
||||
myStore = Archivist:Open("RawData", "MyAddonArchive") -- throws an error if "MyAddonArchive" doesn't exist in the archive
|
||||
|
||||
-- if you know that your store doesn't yet exist, then you can call Create:
|
||||
myStore = Archivist:Create("RawData", "MyAddonArchive")
|
||||
|
||||
-- If you wish to create an "anonymous" store for some reason, that is also supported. Archivist will auto-generate a random storeID for you. But don't lose the storeID, or it will be hard to find this data again later.
|
||||
myStore, storeID = Archivist:Create("RawData")
|
||||
|
||||
```
|
||||
|
||||
All of the basic Archivist Verbs (Create, Load, Open, Close, Commit) take as parameters the Store Type and Store ID. The Store Type must be a string, and identifies the kind of store object you wish to create. The store type must be registered via RegisterStoreType (see [Create New Store Types](#create-new-store-types) below for more information). The Store ID must be a string, and is unique for that given Store Type. You may have as many stores named `MyAddonArchive` as you like, so long as all of them have differing Store Types.
|
||||
|
||||
The RawData store type is just a table. You may mutate this table in any way you see fit. Once you are done with your reads and writes, you may close myStore:
|
||||
|
||||
```lua
|
||||
Archivist:CloseStore(myStore)
|
||||
-- or...
|
||||
Archivist:Close("RawData", "MyAddonArchive")
|
||||
```
|
||||
|
||||
Once closed, any changes to myStore will not be archived unless you reopen the store.
|
||||
|
||||
Some store types (see [Create New Store Types](#create-new-store-types) below) may operate in such a way that it makes sense to commit changes to the archive, without closing the store. Archivist supports this operation too:
|
||||
|
||||
```lua
|
||||
Archivist:CommitStore(myStore)
|
||||
-- or...
|
||||
Archivist:Commit("RawData", "MyAddonArchive") -- In the case of RawData this is not very useful, since its contents are also committed when the store is closed
|
||||
```
|
||||
|
||||
All open stores are automatically closed and archived on `PLAYER_LOGOUT`. You should not try to read or write data from a store on or after that event. The exact behavior at that point depends on the implementation of the Store Type, as well as the order in which WoW dispatches events to addon scripts, and is outside the Archivist specification.
|
||||
|
||||
### Included Store Types
|
||||
|
||||
Archivist comes prepackaged with some basic store types, both for your convenience and as an example for implementing your own. They are listed here:
|
||||
|
||||
- RawData
|
||||
- A simple table with no extra bells or whistles. The contents of this table are stored directly into the archive when this archive is committed or closed.
|
||||
|
||||
### Embedding the Archivist
|
||||
|
||||
In most use cases, you will want to embed Archivist, so that your addon's archive does not intersect with that of any other addon. Embedding the Archivist into your addon is very similar, but a few more steps are needed. First, create an archive addon:
|
||||
|
||||
```lua
|
||||
## Interface: 80300
|
||||
## Title: MyArchive
|
||||
## LoadOnDemand: 1
|
||||
## SavedVariables: MyArchiveSaved
|
||||
```
|
||||
|
||||
You may also use your addon's native SavedVariables file if you wish (instead of creating a secondary addon), but this will cause your addon to always load its archive from the disk, which may not be desirable.
|
||||
|
||||
Archivist follows the library standard (though it is [not a library](#archivist-is-not-a-library)) for making itself available to be embedded. To include Archivist in your addon, it is pretty easy if you already use other libraries.
|
||||
|
||||
First, include Archivist as an external in your .pkgmeta file:
|
||||
|
||||
```
|
||||
externals:
|
||||
MyAddon/Embeds/Archivist: https://github.com/emptyrivers/Archivist
|
||||
```
|
||||
|
||||
Then, in your embeds.xml (if you use one), you can include Archivist.xml:
|
||||
|
||||
```xml
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<!-- Other embedded addons...-->
|
||||
<Include file=".\Embeds\Archivist.xml"/>
|
||||
</Ui>
|
||||
|
||||
```
|
||||
|
||||
That's it! Archivist is now available in your addon releases.
|
||||
|
||||
You can also include Archivist via your .toc file instead, if you prefer.
|
||||
|
||||
To use Archivist in your addon code:
|
||||
|
||||
```lua
|
||||
local addon, ns = ...
|
||||
|
||||
-- Note that Archivist embeds itself in your addon's namespace.
|
||||
local Archivist = ns.Archivist
|
||||
|
||||
-- when you have need to access the archive, then load the data...
|
||||
LoadAddOn("MyArchive")
|
||||
-- and initialize Archivist with your archive.
|
||||
Archivist:Initialize(MyArchiveSaved)
|
||||
|
||||
```
|
||||
|
||||
Now, you may use the Archivist just like you would if it had been installed in a standalone form.
|
||||
|
||||
## Custom Store Types
|
||||
|
||||
Some use cases demand more sophisticated data management. Archivist can accomodate this need, with custom Store Types:
|
||||
|
||||
```lua
|
||||
local prototpye = {
|
||||
id = "MyStoreType",
|
||||
version = 1,
|
||||
Init = function() end,
|
||||
Create = function(...) end,
|
||||
Open = function(data) end,
|
||||
Update = function(data) end,
|
||||
Commit = function(store) end,
|
||||
Close = function(store) end,
|
||||
}
|
||||
|
||||
Archivist:RegisterStoreType(prototype)
|
||||
```
|
||||
|
||||
Prototype Fields are as follows:
|
||||
|
||||
- id
|
||||
- Unique Identifier of the store type, e.g. `RawData`.
|
||||
- version
|
||||
- Version number of store type. Useful if the prototype changes in a backwards incompatible way, and archived data needs to be massaged before use.
|
||||
|
||||
Prototype methods:
|
||||
|
||||
- Init
|
||||
- Optional function. Initialize your prototype. If provided, then Init is always guaranteed to run exactly once per game session, before any other method is run.
|
||||
- Create
|
||||
- Create a new, empty, store object. Extra arguments passed into Archivist:Create will be passed into this function if you wish to accomodate initial setup of the store object.
|
||||
- Must return non-nil store object.
|
||||
- Open
|
||||
- Create from data an active store object.
|
||||
- Must return non-nil store object
|
||||
- Update
|
||||
- Optional function. If provided, then archived data is replaced with the return value of Update. If no change is needed, then return nil.
|
||||
- Commit
|
||||
- Return image of data to be archived
|
||||
- Close
|
||||
- Deactivate store. Returned value will be written to archive. If no update to archive is needed, then return nil.
|
||||
- Once close is called on a store, Archivist will not update the archived data again unless the store is opened.
|
||||
|
||||
### Store Type Methods Should be Functional
|
||||
|
||||
With the exception of Init, all of these functions may be called at any time without warning. Thus, they should ideally be written as close to purely functional as possible, with few-to-no side effects. If you must have side effects, then do your best to write functions whose side effects are idempotent. This will help you avoid weird problems from functions being called in an order you didn't expect, and other hard-to-debug behaviors.
|
||||
|
||||
### Re-registering prototypes
|
||||
|
||||
If multiple independent codebases share an archive, and they both register the same store type, then what happens depends on the version number:
|
||||
|
||||
- If the second registration has equal or lower version number, then the second registration is ignored.
|
||||
- If the second registration has a higher version number, then:
|
||||
- Each active store is Closed using the old Close method
|
||||
- Each Archived store is Updated using the new Update method if provided
|
||||
- The new Init method is run.
|
||||
- Any previously open stores are opened.
|
||||
|
||||
Re-registering a store type is generally not recommended, as you risk data loss and other errors. If you are using a store type in a shared archive which you expect to be registered multiple times (e.g. by independent custom code auras in WeakAuras), then it is recommended to only keep stores of that type open for the time that you need them to be open. Alternatively, design the Initialization routine such that it can broadcast the re-initialization, so that any system that was holding a reference to any open store can re-obtain that reference.
|
||||
|
||||
## Performance
|
||||
|
||||
Archivist stores data in a compressed format, to minimize load times. This means that to open an archive, Archivist must fully decompress the data. Depending on the level of compression, and the size before compression, this can potentially take a long time to do.
|
||||
|
||||
To circumvent this, consider breaking your data into chunks (ideally into chunks that are meaningful for your code, and not arbitrarily based on data size), and archiving each piece in its own store, if you believe that your archive will contain a large amount of data. The `Check` verb is provided as a cheap way to check if a given storeID exists in the archive.
|
||||
|
||||
## Full API List
|
||||
|
||||
```lua
|
||||
|
||||
-- Main API
|
||||
|
||||
-- Opens (or creates) the given store. This is the main entry point for your addon's code.
|
||||
store = Archivist:Load(storeType, storeID)
|
||||
-- or... (though rarely useful - if storeID is nil then Load is an alias for Create)
|
||||
store, storeID = Archivist:Load(storeType)
|
||||
|
||||
-- Register store type. Store type must be registered before an archive can be accessed. All verbs will raise an error if called with an unregistered storeType.
|
||||
Archivist:RegisterStoreType(prototype)
|
||||
|
||||
|
||||
-- Archivist verbs
|
||||
-- These are the main "actions" that archivist knows how to do.
|
||||
|
||||
-- Creates a new archive, and returns an active store object. Raises an error if archive already exists. In most cases you'll want to use Load instead.
|
||||
store = Archivist:Create(storeType, storeID, ...)
|
||||
-- or... (though rarely useful)
|
||||
store, storeID = Archivist:Create(storeType)
|
||||
|
||||
-- Opens an archive, and returns active store object. Raises an error if archive doesn't yet exist. In most cases you'll want to use Load instead.
|
||||
store = Archivist:Open(storeType, storeID)
|
||||
|
||||
-- Commit to archive without closing store. This will cause a change in the archive.
|
||||
-- Store is still considered open after committing it.
|
||||
Archivist:CommitStore(store)
|
||||
-- or...
|
||||
Archivist:Commit(storeType, storeID)
|
||||
|
||||
-- Closes a store. Once closed, you may discard the store object.
|
||||
-- May also update archive, depending on the store type. Once closed, you should consider the store object obsolete.
|
||||
-- Occurs automatically on PLAYER_LOGOUT.
|
||||
Archivist:CloseStore(store)
|
||||
-- or...
|
||||
Archivist:Close(storeType, storeID)
|
||||
-- Note: once a store is closed, manipulating the old store object is considered outside of the Archivist specification, and behavior depends on the store type implementation. However, any changes after Close will *never* be archived
|
||||
|
||||
-- Check if a store exists.
|
||||
-- This verb is intended for performance-critical operations, where you only need to ensure that the store exists. Guaranteed to never call Archive/DeArchive, or invoke any prototype methods.
|
||||
-- Returns true if data for the given storeID exists in the archive, or an active store object exists.
|
||||
-- Otherwise, returns false.
|
||||
storeExists = Archivist:Check(storeType, storeID)
|
||||
|
||||
-- Close and delete store permanently. Use ONLY if you are absolutely sure you don't want the data anymore. Archivist cannot help you retrieve lost data once you invoke this.
|
||||
-- If force is truthy, then the Delete will go through even if the store type is not registered. This is useful if you decide to drop support for a storeType.
|
||||
Archivist:DeleteStore(store, force)
|
||||
-- or...
|
||||
Archivist:Delete(storeType, storeID, force)
|
||||
-- or... (if you want to delete everything)
|
||||
-- If storeType is given, then all stores of that type are deleted. If storeType is not given, then all stores in the entire archive are deleted. This is provided to assist in cases like, "the user wishes to destroy all data and start fresh".
|
||||
-- USE WITH CAUTION. YOUR DATA WILL BE LOST. YOU WILL NOT GET A SECOND CHANCE.
|
||||
Archivist:DeleteAll(storetype)
|
||||
|
||||
-- Create identical but independent copy of archive. If openstore is truthy, then also Opens the cloned archive and returns active store object.
|
||||
store = Archivist:CloneStore(store, openStore)
|
||||
-- or...
|
||||
store = Archivist:Clone(storeType, storeID, openStore)
|
||||
|
||||
|
||||
-- Plumbing Methods
|
||||
-- The following methods are used internally, and are usually not useful for addons using the Archivist.
|
||||
|
||||
-- Generate a random uuid. Used when Create is called without providing a storeID
|
||||
uuid = Archivist:GenerateID()
|
||||
|
||||
-- Compress data. Data passed in is not touched in any way, and calling code may retain ownership.
|
||||
compressedString = Archivist:Archive(data)
|
||||
|
||||
-- Decompress data. String is expected to have been compressed using Archivist:Archive.
|
||||
-- Compressing, and then decompressing the same data is essentially an expensive clone operation.
|
||||
data = Archivist:DeArchive(compressedString)
|
||||
|
||||
-- Close all stores immediately. Automatically called on PLAYER_LOGOUT. Not usually useful for addons using
|
||||
Archivist:CloseAllStores()
|
||||
|
||||
```
|
||||
|
||||
## Limitations and "Gotchas"
|
||||
|
||||
Like every other project, Archivist can't do everything. The major restrictions (and consequences of these limitations) are described here.
|
||||
|
||||
### Archivist Modifies Your Addon's Namespace
|
||||
|
||||
Specifically, when Archivist is embedded into your addon, then the Archivist field on your namespace table (the one you access with `local addon, ns = ...`) is set to the Archivist object. I don't expect many people have ever used that particular field on their namespace table, but please do be aware of this if you decide to use the archivist.
|
||||
|
||||
For this reason, you also shouldn't embed Archivist in a library intended for 3rd party consumption via LibStub. Anybody who embeds your addon may not be expecting their namespace to be modified, and unexpected changes to the internal namespace is pretty rude to force on someone.
|
||||
|
||||
### Archivist Can't Store Everything
|
||||
|
||||
Archivist is a WoW addon, and it cannot do anything that the WoW addon environment does not allow for. In practice, this means that Archivist cannot serialize functions or closures it receives, as there is no way to convert these to a string format. If your store type has functions which need to be stored (e.g. generated code), then instead of trying to archive the function directly, store all of the information which was used to generate the function, and in your Open procedure, re-generate the function based on the data image you receive.
|
||||
|
||||
Additionally, Archivist does not check for metatables. So, if your store type uses __index or __newindex metamethods in any way, then providing the store object when asked for a data image will likely result in data loss. To avoid this, ensure that your Close and Commit methods return objects that contain all of the data you need to archive directly, without needing to invoke any metamethods.
|
||||
|
||||
### Archivist is Not a Library
|
||||
|
||||
In the WoW addon world, we are used to embedded code that is "library-style". That is, everyone shares the same code, and shares the same versioning. This is where tools like LibStub and the Ace framework come into play. These are great when you just want to use code that somebody else already wrote, without worrying too much about implementation details. Archivist even uses this for compression, because I don't want to have to reinvent the wheel just to compress some data. Also, LibDeflate is really good.
|
||||
|
||||
Archivist explicitly does **not** follow this pattern. In a slightly different world, Archivist might have been designed to fit the LibStub paradigm, but the specifics of the WoW AddOn environment (and what Archivist is supposed to do) make that the wrong pattern to follow, in my opinion. Instead, Archivist is a service you may embed into your code, but it does not share its code or internal memory with other instances of Archivist in other addons. Don't rely on some other commonly installed addon to register a store type you need for you. That's bad practice in any scenario, and it just won't work with Archivist.
|
||||
|
||||
Please don't register Archivist with LibStub. This will "work", in that you won't get any errors. But if somebody else does the same thing, then one of you will be unable to access your own archive, since Archivist is designed to be run and initialized by each addon independently for each instance. If a user installs two addons which both use Archivist like this, then nothing will seem amiss for a while until one of these addons is disabled and suddenly the other "loses" all of its data. Then you get angry users, and nobody likes angry users.
|
||||
|
||||
You can set Archivist as a hard dependency in your .toc file, if you want to use a "global" archive. In this way, Archivist can behave nicely while still being "shared code". However, a global archive is like writing code with lots of global variables - not such a great idea. The standalone Archivist addon is distributed primarily so that it can be embedded into other addons easily via curseforge or packaging scripts, as well as for the use of individual users in private, non-shared setups that just need a place to store some data without too much hassle.
|
||||
@@ -61,9 +61,9 @@ Credits:
|
||||
--[[
|
||||
Curseforge auto-packaging replacements:
|
||||
|
||||
Project Date: 2020-06-09T17:59:15Z
|
||||
Project Hash: 5e673bb068bdc39d810e133f2d6e19115f95e532
|
||||
Project Version: 5e673bb
|
||||
Project Date: 2021-04-30T05:30:18Z
|
||||
Project Hash: b2924165b0be53fc68407fa5e4a293b0ff855f4d
|
||||
Project Version: v1.0.8
|
||||
--]]
|
||||
|
||||
local LibDeflate
|
||||
@@ -3049,7 +3049,7 @@ end
|
||||
local _addon_channel_codec
|
||||
|
||||
local function GenerateWoWAddonChannelCodec()
|
||||
return LibDeflate:CreateCodec("\000\124", "\001", "")
|
||||
return LibDeflate:CreateCodec("\000", "\001", "")
|
||||
end
|
||||
|
||||
--- Encode the string to make it ready to be transmitted in World of
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
## Interface: 70300
|
||||
## Interface: 90002
|
||||
|
||||
## Title: Lib: Compress
|
||||
## Notes: Compression and Decompression library
|
||||
## Author: Galmok at Stormrage-EU (Horde) and JJSheets
|
||||
## Version: r84-release
|
||||
## Version: r86-release
|
||||
## X-Website: http://www.wowace.com/addons/libcompress/
|
||||
## X-Category: Library
|
||||
## X-eMail: galmok AT gmail DOT com, sheets DOT jeff AT gmail DOT com
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
zlib License
|
||||
|
||||
(C) 2018-2021 Haoqian He
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
+2503
-2407
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
## Interface: 80300
|
||||
## Title: Lib: LibDeflate
|
||||
## Notes: Compressor and decompressor with high compression ratio using DEFLATE/zlib format.
|
||||
## Author: Haoqian He (WoW: Safetyy at Illidan-US (Horde))
|
||||
## Version: afc3b78
|
||||
## X-Website: https://wow.curseforge.com/projects/libdeflate
|
||||
## X-Category: Library
|
||||
## X-License: zlib
|
||||
## X-Curse-Project-ID: 293814
|
||||
## X-WoWI-ID: 25453
|
||||
## X-Wago-ID: Xb6X5wKp
|
||||
|
||||
LibStub\LibStub.lua
|
||||
lib.xml
|
||||
@@ -1,7 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
|
||||
<Script file="LibDBIcon-1.0.lua"/>
|
||||
|
||||
</Ui>
|
||||
|
||||
<Script file="LibDeflate.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,17 @@
|
||||
## Interface: 30300
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: GroupTalents-1.0
|
||||
## Notes: Library to help with querying unit talents.
|
||||
## Author: Zek
|
||||
## Version: $Rev: 51 $
|
||||
## OptionalDeps: Ace3, LibTalentQuery-1.0, LibBabble-TalentTree-3.0
|
||||
## X-Category: Library
|
||||
## X-ReleaseDate: $Date$
|
||||
## X-Website: http://wowace.com/wiki/LibGroupTalents-1.0
|
||||
## X-License: MIT
|
||||
## X-Curse-Packaged-Version: 3.3 Release 3
|
||||
## X-Curse-Project-Name: LibGroupTalents-1.0
|
||||
## X-Curse-Project-ID: libgrouptalents-1-0
|
||||
## X-Curse-Repository-ID: wow/libgrouptalents-1-0/mainline
|
||||
|
||||
lib.xml
|
||||
@@ -1,5 +1,6 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Include file="LibBabble-TalentTree-3.0\lib.xml"/>
|
||||
<Script file="LibTalentQuery-1.0.lua"/>
|
||||
<Script file="LibGroupTalents-1.0.lua"/>
|
||||
</Ui>
|
||||
@@ -1,4 +1,4 @@
|
||||
## Interface: 80000
|
||||
## Interface: 90005
|
||||
## Title: Lib: LibStub
|
||||
## Notes: Universal Library Stub
|
||||
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Title: WeakAuras
|
||||
## Author: The WeakAuras Team
|
||||
## Version: 5.19.10
|
||||
## IconTexture: Interface\AddOns\WeakAuras\Media\Textures\icon.blp
|
||||
## X-Flavor: 3.3.5
|
||||
## Notes: A powerful, comprehensive utility for displaying graphics and information based on buffs, debuffs, and other triggers.
|
||||
## Notes-esES: Potente y completa aplicación que te permitirá mostrar por pantalla múltiples diseños, basados en beneficios, perjuicios y otros activadores.
|
||||
|
||||
@@ -17,5 +17,4 @@
|
||||
<Include file="Libs\Archivist\Archivist.xml"/>
|
||||
<Include file="Libs\LibSerialize\lib.xml"/>
|
||||
<Include file="Libs\LibGroupTalents-1.0\lib.xml"/>
|
||||
<Include file="Libs\LibBabble-TalentTree-3.0\lib.xml"/>
|
||||
</Ui>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
## Title: WeakAuras Options
|
||||
## Author: The WeakAuras Team
|
||||
## Version: 5.19.10
|
||||
## IconTexture: Interface\AddOns\WeakAuras\Media\Textures\icon.blp
|
||||
## Notes: Options for WeakAuras
|
||||
## Notes-esES: Opciones para WeakAuras
|
||||
## Notes-esMX: Opciones para WeakAuras
|
||||
|
||||
Reference in New Issue
Block a user