/tags
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
SpellAPI.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
--[[ |
PROBLEM |
The search dingus can't find stuff because...? |
I don't actually know. I thought it was related to spec spell IDs, |
because those are the ones that fail |
However, it appears that the API consistently uses the global ID, not |
the spec ID for action bar buttons and for the spellbook |
So why isn't it matching??? |
]] |
--[[ |
DESIGN WORK |
bottom-up |
A Spell is an object with data globalID and getter methods Name(), |
Slot(), Status(), Link(), Known(), etc |
The metatable contains the actual methods, when attempting to index |
the object, the methods are called on the object |
spell.Name -> meta.Name(spell) -> GetSpellBookItemName(spell.Slot) |
BookID is an object that instantiates a new Spell object whenever a |
nonexistent index is accessed |
BookID[n] -> bookIDMeta.__index (t, n) -> setmetatable({ globalID = g }, SpellMeta) |
Why look up the slot each time with SpellBookSlotBySpellID? Because |
slots change. It's easier to always use the globalID than try to |
track spellbook ID changes dynamically. I've tried. |
Concern: Neither SpellBookIDs nor GlobalIDs are stable across spec changes. |
]] |
local addonName, private = ... |
local LA = private.LA |
-- backend data |
-- Metatable._method.Foo = true indicates that Foo will be called as object:Foo() rather than object.Foo |
local bookMeta = { } |
local globalMeta = { } |
local spellMeta = { _method = { Pickup = true } } |
local flyoutBookMeta = { } |
local flyoutMeta = { _method = { Pickup = true } } |
-- Lua optimization: locals are faster than globals |
local SpellInfo = GetSpellInfo |
local SpellKnown = IsSpellKnown |
local SpellBookItemInfo = GetSpellBookItemInfo |
local SpellBookSlotBySpellID = FindSpellBookSlotBySpellID |
local SpellLink = GetSpellLink |
local SpellPassive = IsPassiveSpell |
-- Top level |
LA.Spell = { |
Global = { }, |
Book = { }, |
Flyout = { }, |
} |
setmetatable(LA.Spell.Global, globalMeta) |
setmetatable(LA.Spell.Book, bookMeta) |
setmetatable(LA.Spell.Flyout, flyoutBookMeta) |
-- Spell Global ID object factory |
function globalMeta.__index (t, index) |
index = tonumber(index) |
assert(index > 0) |
local gID = LA.specToGlobal[index] or index -- get base spell ID |
local sID = LA.globalToSpec[index] or LA.globalToSpec[gID] or index -- get spec spell ID |
--local sID = select(2, LA:UnlinkSpell(SpellLink(gID))) -- get spec spell id |
local newSpell = { _gid = gID, _sid = sID } |
setmetatable(newSpell, spellMeta) |
-- rawset(t, index, newSpell) -- Save this object for faster future retrieval |
-- TODO -- Expiration mechanism for cached spell objects |
return newSpell |
end |
-- Spell Book ID object factory |
function bookMeta.__index(t, index) |
index = tonumber(index) |
assert(index > 0) |
local gType, gID = SpellBookItemInfo(index, BOOKTYPE_SPELL) |
if "SPELL" == gType or "FUTURESPELL" == gType then |
local spell = LA.Spell.Global[gID] |
spell._slot = index -- Remember which Spellbook slot the spell is in |
-- rawset(t, index, spell) -- Save this object for faster future retrieval |
-- TODO -- Expiration mechanism for cached spell objects |
return spell |
elseif "FLYOUT" == gType then |
return LA.Spell.Flyout[gID] |
else |
error("LearningAid.Spell.Book: Type of spellbook slot #"..tostring(index).." ("..tostring(gType)..") is not known", 2) |
end |
end |
-- Spell object instances |
function spellMeta.__index(spell, index) |
-- Use rawget to avoid an infinite loop if _gid doesn't exist for some reason |
-- LA:DebugPrint("SpellMeta "..index.."("..tostring(rawget(spell, "_gid"))..")") |
if not spellMeta[index] then |
error("SpellAPI: Invalid Spell object method '"..tostring(index).."'", 2) |
end |
if spellMeta._method[index] then |
-- return value will be called as a method, see spellMeta._method |
LA:DebugPrint("SpellMeta "..index.."("..tostring(spell)..")") |
return spellMeta[index] |
else |
-- simple return value |
local result = spellMeta[index](spell) |
LA:DebugPrint(tostring(result).." = SpellMeta "..tostring(index).."("..tostring(spell)..")") |
return result |
end |
end |
function spellMeta.__eq(spell1, spell2) |
return spell1._gid == spell2._gid |
end |
function spellMeta.Name(spell) |
return select(1, SpellInfo(spell._gid)) |
end |
function spellMeta.SpecName(spell) |
return select(1, SpellInfo(spell._sid)) |
end |
function spellMeta.Info(spell) |
local info = { } |
info.name, info.rank, info.icon, info.powerCost, info.isFunnel, info.powerType, info.castingTime, info.minRange, info.maxRange = |
SpellInfo(spell._gid) |
return info |
end |
function spellMeta.SpecInfo(spell) |
local info = { } |
info.name, info.rank, info.icon, info.powerCost, info.isFunnel, info.powerType, info.castingTime, info.minRange, info.maxRange = |
SpellInfo(spell._sid) |
return info |
end |
function spellMeta.SubName(spell) |
return select(2, SpellInfo(spell._gid)) |
end |
function spellMeta.SpecSubName(spell) |
return select(2, SpellInfo(spell._sid)) |
end |
function spellMeta.SpecID(spell) |
return spell._sid |
end |
function spellMeta.Known(spell) |
-- Only works on gid, not sid. SpellKnown(sid) will always return nil |
return SpellKnown(spell._gid) |
end |
function spellMeta.Status(spell) |
local slot = spell.Slot |
assert(spell.Slot, LA.name..": Spell #"..spell._gid.." slot unknown in Spell.Status.") |
return SpellBookItemInfo(slot, BOOKTYPE_SPELL) |
end |
function spellMeta.ID(spell) |
return spell._gid |
end |
function spellMeta.Slot(spell) |
--local name = spell.Name |
--local infoName = spell.Info.name |
local globalID = spell._gid |
-- use rawget because _slot might be nil, which would call metatable._slot as a method and fail |
local oldSlot = rawget(spell, "_slot") |
local slot = SpellBookSlotBySpellID(globalID) |
if slot then |
if slot ~= oldSlot then |
-- tostring to guard against nil values |
LA:DebugPrint("Spell ".. globalID.. " slot changed from ".. tostring(oldSlot).. " to ".. tostring(slot)) |
end |
spell._slot = slot |
return slot |
end |
return oldSlot |
end |
function spellMeta.Link(spell) |
return SpellLink(spell._gid) or "" |
end |
function spellMeta.SpecLink(spell) |
return SpellLink(spell._sid) or "" |
end |
-- use spell link in debug printing statements |
spellMeta.__tostring = spellMeta.SpecLink |
function spellMeta.Spec(spell) |
return select(1, IsSpellClassOrSpec(spell.Slot, BOOKTYPE_SPELL)) |
end |
function spellMeta.Class(spell) |
return select(2, IsSpellClassOrSpec(spell.Slot, BOOKTYPE_SPELL)) |
end |
function spellMeta.Selected(spell) |
return IsSelectedSpellBookItem(spell.Slot, BOOKTYPE_SPELL) |
end |
function spellMeta.Perk(spell) |
return LA.guildSpells[spell._gid] |
end |
function spellMeta.Passive(spell) |
return SpellPassive(spell._gid) |
end |
function spellMeta:Pickup() |
PickupSpell(self._gid) |
end |
function spellMeta.Texture(spell) |
return GetSpellTexture(spell._gid) |
end |
function spellMeta.SpecTexture(spell) |
-- GetSpellTexture returns two values, one for the base spell and one for the override spell |
return select(2, GetSpellTexture(spell._sid)) |
end |
-- Flyout object factory |
-- Note: Uses flyout IDs which are discontinuous, analogous to global spellIDs. |
-- Does not use flyout indexes, which are continuous and run from 1..GetNumFlyouts() |
function flyoutBookMeta.__index(t, index) |
index = tonumber(index) |
assert(index > 0) |
local newFlyout = { _fid = index } |
setmetatable(newFlyout, flyoutMeta) |
return newFlyout |
end |
-- Flyout object instances |
function flyoutMeta.__index(flyout, index) |
if "string" == type(index) then |
-- call method (index) on the flyout object |
assert(flyoutMeta[index], index.." is not a known flyout method") |
if flyoutMeta._method[index] then |
LA:DebugPrint("FlyoutMeta "..index.."("..tostring(rawget(flyout, "_fid"))..")") |
return flyoutMeta[index] -- return value will be called as a method, see flyoutMeta._method |
else |
return flyoutMeta[index](flyout) |
end |
elseif "number" == type(index) then |
-- get the spell number (index) from the flyout |
local globalID, isKnown = GetFlyoutSlotInfo(flyout._fid, index) |
if globalID then |
return LA.Spell.Global[globalID] |
else |
return nil |
end |
end |
end |
function flyoutMeta.__eq(flyout1, flyout2) |
return flyout1._fid == flyout2._fid |
end |
function flyoutMeta.ID(flyout) |
return flyout._fid |
end |
function flyoutMeta.Info(flyout) |
return LA:FlyoutInfo(flyout._fid) |
end |
function flyoutMeta.Size(flyout) |
--local name, description, size, flyoutKnown = GetFlyoutInfo(flyoutID) |
return select(3, GetFlyoutInfo(flyout._fid)) |
end |
function flyoutMeta.Status(flyout) |
return "FLYOUT" |
end |
function flyoutMeta.Name(flyout) |
--local name, description, size, flyoutKnown = GetFlyoutInfo(flyoutID) |
return GetFlyoutInfo(flyout._fid) -- passes on only the first return value, which is the localized name |
end |
flyoutMeta.__tostring = flyoutMeta.Name |
function flyoutMeta.SubName(flyout) |
return "" |
end |
function flyoutMeta.Selected(flyout) |
-- FIXME TODO activate when flyout is open -- FIXME TODO |
end |
function flyoutMeta.Known(flyout) |
--local name, description, size, flyoutKnown = GetFlyoutInfo(flyoutID) |
return select(4, GetFlyoutInfo(flyout._fid)) |
end |
-- Pickup is a method, call as flyout:Pickup() rather than flyout.Pickup |
function flyoutMeta:Pickup() |
PickupSpellBookItem(self.Slot, BOOKTYPE_SPELL) |
end |
function flyoutMeta.Slot(flyout) |
return LA.flyoutCache[flyout._fid] |
end |
-- Usage: for spell in flyout.Spells do x y z end |
function flyoutMeta.Spells(flyout) |
local size = flyout.Size |
local index = 0 |
return function() |
index = index + 1 |
if index <= size then |
return flyout[index] |
end |
end |
end |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
SpellButton.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
local function hideButton(spellButton, mouseButton, down) |
if not InCombatLockdown() then |
LA:ClearButtonIndex(spellButton.index) |
end |
end |
local function linkSpell(spellButton, ...) |
if "SPELL" == spellButton.item.Status then |
LA:SpellButton_OnModifiedClick(spellButton, ...) |
end |
end |
local function toggleIgnore(spellButton, mouseButton, down) |
if "SPELL" == spellButton.item.Status then |
LA:ToggleIgnore(spellButton.item) |
LA:UpdateButton(spellButton) |
end |
end |
local function toggleFlyout(spellButton, mouseButton, down) |
if not InCombatLockdown() then |
-- SpellFlyout:Toggle(flyoutID, parent, direction, distance, isActionBar, specID, showFullTooltip) |
SpellFlyout:Toggle(spellButton.item.ID, spellButton, "LEFT", 1, false); |
SpellFlyout:SetBorderColor(181/256, 162/256, 90/256); |
end |
end |
function LA:CreateButton() |
--[[ if self.state.retalenting then -- DEBUG FIXME |
print("CreateButton called during retalenting!") -- DEBUG FIXME |
end ]]-- DEBUG FIXME |
local buttons = self.buttons |
local count = #buttons |
-- button global variable names start with "SpellButton" to work around an |
-- issue with the Blizzard Feedback Tool used in beta and on the PTR |
local name = "SpellButton_LearningAid_"..(count + 1) |
local button = CreateFrame("CheckButton", name, self.frame, "LearningAidSpellButtonTemplate") |
local background = _G[name.."Background"] |
background:Hide() |
local subSpellName = _G[name.."SubSpellName"] |
subSpellName:SetTextColor(NORMAL_FONT_COLOR.r - 0.1, NORMAL_FONT_COLOR.g - 0.1, NORMAL_FONT_COLOR.b - 0.1) |
buttons[count + 1] = button |
button.index = count + 1 |
button:SetAttribute("type3", "hideButton") |
button:SetAttribute("alt-type*", "hideButton") |
button:SetAttribute("shift-type1", "linkSpell") |
button:SetAttribute("ctrl-type*", "toggleIgnore") |
button.hideButton = hideButton |
button.linkSpell = linkSpell |
button.toggleIgnore = toggleIgnore |
button.toggleFlyout = toggleFlyout |
button.iconTexture = _G[name.."IconTexture"] |
button.cooldown = _G[name.."Cooldown"] |
button.spellName = _G[name.."SpellName"] |
button.subSpellName = subSpellName |
return button |
end |
function LA:AddButton(item) |
assert(item) |
--[[ if self.state.retalenting then -- DEBUG FIXME |
print("AddButton called during retalenting!") -- DEBUG FIXME |
end ]]-- DEBUG FIXME |
-- print("AddButton: "..item.Name) -- DEBUG FIXME |
-- local thing = self.Spell.Global[id] -- could be spell or flyout |
local itemType = strlower(item.Status) -- SPELL or FLYOUT |
assert("spell" == itemType or "flyout" == itemType, "Attempt to add invalid item "..item.ID.." of type "..itemType) |
local buttons = self.buttons |
local visible = self:GetVisible() |
for i = 1, visible do |
if buttons[i].item == item then -- already exists, no action needed |
return |
end |
end |
local button |
-- if bar is full |
if visible == #buttons then |
button = self:CreateButton() |
self:DebugPrint("Adding "..item.Status.." button with id "..item.ID.." to index "..button.index) |
else |
-- if bar has free buttons |
button = buttons[self:GetVisible() + 1] |
self:DebugPrint("Changing button index "..(self:GetVisible() + 1).." from "..button.item.Status.." "..button.item.ID.." to "..item.Status.." "..item.ID) |
button:Show() |
end |
button.item = item |
button:SetAttribute("spell*", item.ID) |
if "flyout" == itemType then |
button:SetAttribute("flyout*", item.ID) |
button:SetAttribute("type*", "toggleFlyout") |
else |
button:SetAttribute("type*", itemType) |
end |
self:SetVisible(visible + 1) |
button:SetChecked(false) |
if "spell" == itemType and item.Selected then |
button:SetChecked(true) |
end |
--[[ MOP |
elseif kind == "MOUNT" or kind == "CRITTER" then |
-- button.Companion = name |
local creatureID, creatureName, creatureSpellID, icon, isSummoned = GetCompanionInfo(kind, id) |
if isSummoned then |
button:SetChecked(true) |
end |
else |
self:DebugPrint("AddButton(): Invalid button type "..kind) |
end |
]] |
self:UpdateButton(button) |
self:AutoSetMaxHeight() |
self.frame:Show() |
end |
function LA:ClearButtonID(item) |
local buttons = self.buttons |
local i = 1 |
-- not using a for loop because self.visible may change during the loop execution |
while i <= self:GetVisible() do |
if buttons[i].item == item then -- buttons[i].kind == kind and |
self:DebugPrint("Clearing button "..i.." with item "..buttons[i].item.Name) |
self:ClearButtonIndex(i) |
else |
--self:DebugPrint("Button "..i.." has id "..buttons[i]:GetID().." which does not match "..id) |
i = i + 1 |
end |
end |
end |
function LA:SetMaxHeight(newMaxHeight) -- in buttons, not pixels |
self.maxHeight = newMaxHeight |
self:ReshapeFrame() |
end |
function LA:GetMaxHeight() |
return self.maxHeight |
end |
function LA:AutoSetMaxHeight() |
local screenHeight = UIParent:GetHeight() |
self:DebugPrint("Screen Height = ".. screenHeight) |
local newMaxHeight = math.floor((UIParent:GetHeight()-self.titleHeight)/(self.buttonSize+self.verticalSpacing) - 3) |
self:DebugPrint("Setting MaxHeight to " .. newMaxHeight) |
self:SetMaxHeight(newMaxHeight) |
return newMaxHeight |
end |
function LA:ReshapeFrame() |
local newHeight |
local newWidth |
local maxHeight = self.maxHeight |
local visible = self:GetVisible() |
if visible > maxHeight then |
newHeight = maxHeight |
newWidth = math.ceil(visible / maxHeight) |
else |
newHeight = visible |
newWidth = 1 |
end |
local frame = self.frame |
frame:SetHeight(self.titleHeight + self.framePadding + (self.buttonSize + self.verticalSpacing) * newHeight) |
frame:SetWidth(self.framePadding + (self.buttonSize + self.horizontalSpacing) * newWidth) |
self.height = newHeight |
self.width = newWidth |
self:ParentButtons() |
end |
function LA:ParentButtons() |
local buttons = self.buttons |
local visible = self:GetVisible() |
if visible >= 1 then |
buttons[1]:SetPoint("TOPLEFT", self.titleBar, "BOTTOMLEFT", 16, 0) |
end |
for i = 2, visible do |
if i <= self.height then |
buttons[i]:SetPoint("TOPLEFT", buttons[i-1], "BOTTOMLEFT", 0, -self.verticalSpacing) |
else |
buttons[i]:SetPoint("TOPLEFT", buttons[i-self.height], "TOPRIGHT", self.horizontalSpacing, 0) |
end |
end |
end |
function LA:ClearButtonIndex(index) |
-- I have buttons 1 2 3 (4 5) |
-- I remove button 2 |
-- I want 1 3 (3 4 5) |
-- before, visible = 3 |
-- after, visible = 2 |
local frame = self.frame |
local buttons = self.buttons |
local visible = self:GetVisible() |
for i = index, visible - 1 do |
local button = buttons[i] |
local nextButton = buttons[i + 1] |
button.item = nextButton.item |
button:SetAttribute("type*", nextButton:GetAttribute("type*")) |
button:SetChecked(nextButton:GetChecked()) |
button.iconTexture:SetVertexColor(nextButton.iconTexture:GetVertexColor()) |
local cooldown = button.cooldown |
local nextCooldown = nextButton.cooldown |
cooldown.start = nextCooldown.start |
cooldown.duration = nextCooldown.duration |
cooldown.enable = nextCooldown.enable |
if cooldown.start and cooldown.duration and cooldown.enable then |
CooldownFrame_SetTimer(cooldown, cooldown.start, cooldown.duration, cooldown.enable) |
else |
cooldown:Hide() |
end |
--if buttons[i]:IsShown() then |
self:UpdateButton(button) |
--end |
end |
buttons[visible]:Hide() |
self:SetVisible(visible - 1) |
self:ReshapeFrame() |
end |
function LA:SetVisible(visible) |
local frame = self.frame |
self.visible = visible |
local top, left = frame:GetTop(), frame:GetLeft() |
frame:SetHeight(self.titleHeight + 10 + (self.buttonSize + self.verticalSpacing) * visible) |
frame:ClearAllPoints() |
frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top) |
if visible == 0 then |
frame:Hide() |
end |
end |
function LA:GetVisible() |
return self.visible |
end |
function LA:Hide() |
if not InCombatLockdown() then |
for i = 1, self:GetVisible() do |
self.buttons[i]:SetChecked(false) |
self.buttons[i]:Hide() |
end |
self:SetVisible(0) |
else |
table.insert(self.queue, { kind = "HIDE" }) |
end |
end |
-- Adapted from SpellBookFrame.lua |
function LA:UpdateButton(button) |
--local id = button:GetID() |
local item = button.item |
local name = button:GetName() |
local id = item.ID |
local iconTexture = _G[name.."IconTexture"] |
local spellString = _G[name.."SpellName"] |
local subSpellString = _G[name.."SubSpellName"] |
local cooldown = _G[name.."Cooldown"] |
local autoCastableTexture = _G[name.."AutoCastable"] |
local highlightTexture = _G[name.."Highlight"] |
-- CATA -- local normalTexture = _G[name.."NormalTexture"] |
if not InCombatLockdown() then |
button:Enable() |
end |
local texture = GetSpellBookItemTexture(item.Slot, BOOKTYPE_SPELL); |
-- If no spell, hide everything and return |
if ( not texture or (strlen(texture) == 0) ) then |
iconTexture:Hide() |
spellString:Hide() |
subSpellString:Hide() |
cooldown:Hide() |
autoCastableTexture:Hide() |
SpellBook_ReleaseAutoCastShine(button.shine) |
button.shine = nil |
highlightTexture:SetTexture("Interface\\Buttons\\ButtonHilight-Square") |
button:SetChecked(false) |
-- CATA -- normalTexture:SetVertexColor(1.0, 1.0, 1.0) |
return; |
end |
local spellName = item.Name |
local subSpellName = item.SubName |
if "SPELL" == item.Status then |
spellName = item.SpecName |
subSpellName = item.SpecSubName |
local start, duration, enable = GetSpellCooldown(id) |
CooldownFrame_SetTimer(cooldown, start, duration, enable) |
cooldown.start = start |
cooldown.duration = duration |
cooldown.enable = enable |
if ( enable == 1 ) then |
iconTexture:SetVertexColor(1.0, 1.0, 1.0) |
else |
iconTexture:SetVertexColor(0.4, 0.4, 0.4) |
end |
end |
-- MOP -- local globalID = select(2, GetSpellBookItemInfo(id, BOOKTYPE_SPELL)) |
-- CATA -- normalTexture:SetVertexColor(1.0, 1.0, 1.0) |
highlightTexture:SetTexture("Interface\\Buttons\\ButtonHilight-Square") |
spellString:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) |
--Set Secure Action Button attribute |
--if not InCombatLockdown()then |
-- local itemType = strlower(item.Status) |
-- button:SetAttribute(itemType.."*", id) -- spell* or flyout* |
--end |
iconTexture:SetTexture(texture) |
spellString:SetText(spellName) |
subSpellString:SetText(spellSubName) |
if ( spellSubName ~= "" ) then |
spellString:SetPoint("LEFT", button, "RIGHT", 4, 4) |
else |
spellString:SetPoint("LEFT", button, "RIGHT", 4, 2) |
end |
if self:IsIgnored(item) then |
iconTexture:SetVertexColor(0.8, 0.1, 0.1) -- red color cribbed from Bartender4 |
end |
iconTexture:Show() |
spellString:Show() |
subSpellString:Show() |
--SpellButton_UpdateSelection(self) |
end |
function LA:SpellButton_OnDrag(button) |
if not InCombatLockdown() then |
button.item:Pickup() |
end |
end |
function LA._SpellButton_OnEnter(button) |
LA:SpellButton_OnEnter(button) |
end |
-- Adapted from SpellBookFrame.lua |
function LA:SpellButton_OnEnter(button) |
GameTooltip:SetOwner(button, "ANCHOR_RIGHT") |
if GameTooltip:SetSpellBookItem(button.item.Slot, BOOKTYPE_SPELL) then |
button.UpdateTooltip = LA._SpellButton_OnEnter |
else |
button.UpdateTooltip = nil |
end |
GameTooltip:AddLine("dummy") |
_G["GameTooltipTextLeft"..GameTooltip:NumLines()]:SetText(self:GetText("ctrlToIgnore")) |
GameTooltip:Show() |
end |
-- Adapted from SpellBookFrame.lua |
function LA:SpellButton_UpdateSelection(button) |
if IsSelectedSpellBookItem(button.item.Slot, "BOOKTYPE_SPELL") then |
button:SetChecked(true) |
else |
button:SetChecked(false) |
end |
end |
-- Adapted from SpellBookFrame.lua and heavily modified |
function LA:SpellButton_OnModifiedClick(spellButton, mouseButton) |
local item = spellButton.item |
local itemName = item.SpecName |
local itemSubName = item.SpecSubName |
if IsModifiedClick("CHATLINK") and "SPELL" == spellButton.item.Status then |
if MacroFrame and MacroFrame:IsShown() then |
if not item.Passive then |
if strlen(itemSubName) > 0 then |
ChatEdit_InsertLink(itemName.."("..itemSubName..")") |
else |
ChatEdit_InsertLink(itemName) |
end |
end |
else |
local link = item.SpecLink |
if link then |
ChatEdit_InsertLink(link) |
end |
end |
elseif IsModifiedClick("PICKUPACTION") then |
item:Pickup() |
end |
end |
function LA:SpellButton_OnHide(button) |
self:DebugPrint("Hiding button "..button.index) |
button:SetChecked(false) |
button.iconTexture:SetVertexColor(1, 1, 1) |
button.cooldown:Hide() |
end |
function LA:UpdateButtons() |
for i = 1, self:GetVisible() do |
self:UpdateButton(self.buttons[i]) |
end |
end |
--[[ |
Learning Aid version 1.12 Beta 3 |
Compatible with World of Warcraft version 6.2.0 |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
LearningAid.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
private.debug = 0 |
private.debugCount = 0 |
private.debugLimit = 10000 -- how many lines of log to keep before deleting earliest line |
-- FIXME TODO -- Have a way to configure this |
private.debugWindow = ChatFrame6 |
private.debugWindow:SetMaxLines(private.debugLimit) |
-- FIXME TODO -- |
-- FIXME TODO -- |
private.logAllEvents = false |
private.shadow = { } |
private.wrappers = { } |
private.debugFlags = { } |
private.tokenCount = { } |
private.noLog = { -- do not log calls to these functions even when call logging is enabled |
GetVisible = true, |
GetText = true, |
ListJoin = true, |
UnlinkSpell = true, |
RealSpellBookItemInfo = true |
--SpellInfo = true, |
--SpellBookInfo = true, |
} |
local LA = { |
version = GetAddOnMetadata(addonName, "Version"), |
dataVersion = 1, |
name = addonName, |
titleHeight = 40, -- pixels |
frameWidth = 200, -- pixels |
framePadding = 10, -- pixels |
verticalSpacing = 5, -- pixels |
horizontalSpacing = 153, -- pixels |
buttonSize = 37, -- pixels |
width = 1, -- button columns |
height = 0, -- button rows |
visible = 0, -- buttons |
strings = { }, |
FILTER_SHOW_ALL = 0, |
FILTER_SUMMARIZE = 1, -- default |
FILTER_SHOW_NONE = 2, |
CONFIRM_TRAINER_BUY_ALL = 732297, -- magic number to prevent users from accidentally spending hundreds of gold at a trainer |
patterns = { |
learnAbility = ERR_LEARN_ABILITY_S, |
learnSpell = ERR_LEARN_SPELL_S, |
unlearnSpell = ERR_SPELL_UNLEARNED_S, |
petLearnAbility = ERR_PET_LEARN_ABILITY_S, |
petLearnSpell = ERR_PET_LEARN_SPELL_S, |
petUnlearnSpell = ERR_PET_SPELL_UNLEARNED_S, |
-- MoP 5.0.4 pre-patch added "You have learned a new passive effect: %s" |
learnPassive = ERR_LEARN_PASSIVE_S |
-- add tradeskill learning stuff here |
}, |
defaults = { -- default savedvariables contents |
macros = true, |
totem = true, |
enabled = true, |
restoreActions = true, |
filterSpam = 1, -- FILTER_SUMMARIZE |
debugFlags = { }, |
ignore = { } |
}, |
menuHideDelay = 5, -- seconds |
pendingBuyCount = 0, |
state = { |
--inCombat = false, -- InCombatLockdown() made this obsolete |
retalenting = false, |
untalenting = false, |
learning = false |
}, |
-- petLearning = false, |
activatePrimarySpec = 63645, -- global spellID |
activateSecondarySpec = 63644, -- global spellID |
autoAttack = 6603, -- global spellID |
autoShot = 75, -- global spellID |
racialSpell = 20549, -- War Stomp (Tauren). Used to determine the subName text for racials. |
racialPassiveSpell = 20550, -- Endurance (Tauren). Used to determine the subName text for racial passives. |
ridingSpells = { |
[33388] = true, -- Apprentice (60% ground speed) |
[33391] = true, -- Journeyman (100% ground speed) |
[34090] = true, -- Expert (150% flying speed) |
[34091] = true, -- Artisan (280% flying speed) |
[90265] = true, -- Master (310% flying speed) |
[90267] = true, -- Flight Master's License (EK, Kalimdor, Deepholm) |
[54197] = true, -- Cold Weather Flying (Northrend) |
[115913] = true, -- Wisdom of the Four Winds (Pandaria) |
[130487] = true -- Cloud Serpent Riding (Pandaria) |
}, |
origin = { |
profession = "profession", |
class = "class", |
guild = "guild", |
riding = "riding", |
race = "race" |
}, |
numProfessions = 6, |
buttons = { }, |
queue = { }, |
availableServices = { }, |
petLearned = { }, |
petUnlearned = { }, |
--[[ PANDARIA |
companionCache = { |
MOUNT = { }, |
CRITTER = { } |
}, |
]] |
ignore = { }, |
--[[ SPELL API |
spellBookCache = { }, |
oldSpellBookCache = { }, |
spellInfoCache = { }, |
]] |
spellsLearned = { }, |
spellsUnlearned = { }, |
flyoutCache = { }, |
numSpells = 0, |
guildSpells = { }, |
knownSpells = { }, |
oldKnownSpells = { }, |
specToGlobal = { }, -- keys are spec spell IDs, values are global spell IDs |
globalToSpec = { }, -- keys are global spell IDs, values are spec spell IDs |
nameToGlobal = { }, -- keys are spell names (lowercased), values are global spell IDs |
backdrop = { |
bgFile = "Interface/DialogFrame/UI-DialogBox-Background", |
edgeFile = "Interface/DialogFrame/UI-DialogBox-Gold-Border", |
tile = false, tileSize = 16, edgeSize = 16, |
insets = { left = 4, right = 4, top = 4, bottom = 4 } |
} |
} |
private.LA = LA |
_G[addonName] = LA |
LibStub("AceConsole-3.0"):Embed(LA) |
function private.onEvent(frame, event, ...) |
LA:DebugPrint("EVENT", event, ...) |
if LA[event] then |
LA[event](LA, ...) |
end |
end |
LA.frame = CreateFrame("Frame", nil, UIParent) |
LA.frame:SetScript("OnEvent", private.onEvent) |
LA.frame:RegisterEvent("ADDON_LOADED") |
for name, pattern in pairs(LA.patterns) do |
LA.patterns[name] = string.gsub(pattern, "%%s", "(.+)") |
end |
function LA:Init() |
--self:DebugPrint("Initialize()") |
self.localClass, self.enClass = UnitClass("player") |
self.tocVersion = select(4, GetBuildInfo()) |
self.locale = GetLocale() |
self:SetDefaultSettings() |
if private.logAllEvents then |
self:Debug("CALL", true) |
self.frame:RegisterAllEvents() |
end |
-- Collect a list of guild perk spells so that LearningAid doesn't |
-- spam them onscreen when they jump into and out of the spellbook, |
-- which they have been known to do |
-- The second return value of GetGuildPerkInfo is the global spellID |
-- of the perk, as it appears in the spellbook |
for perk = 1, GetNumGuildPerks() do |
self.guildSpells[select(2, GetGuildPerkInfo(perk))] = perk |
end |
-- set up main frame |
local frame = self.frame |
frame:Hide() |
frame:SetClampedToScreen(true) |
frame:SetWidth(self.frameWidth) |
frame:SetHeight(self.titleHeight) |
frame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -200, -200) |
frame:SetMovable(true) |
frame:SetScript("OnShow", function () self:OnShow() end) |
frame:SetScript("OnHide", function () self:OnHide() end) |
frame:SetBackdrop(self.backdrop) |
-- create title bar |
local titleBar = CreateFrame("Frame", nil, frame) |
self.titleBar = titleBar |
titleBar:SetPoint("TOPLEFT") |
titleBar:SetPoint("TOPRIGHT") |
titleBar:SetHeight(self.titleHeight) |
titleBar:RegisterForDrag("LeftButton") |
titleBar:EnableMouse() |
titleBar.text = titleBar:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") |
titleBar.text:SetText(self:GetText("title")) |
titleBar.text:SetPoint("CENTER", titleBar, "CENTER", 0, 0) |
-- create close button in the upper right corner of the frame |
local closeButton = CreateFrame("Button", nil, titleBar) |
self.closeButton = closeButton |
closeButton:SetWidth(32) |
closeButton:SetHeight(32) |
closeButton:SetPoint("RIGHT", titleBar, "RIGHT", -2, 0) |
closeButton:SetNormalTexture("Interface/BUTTONS/UI-Panel-MinimizeButton-Up") |
closeButton:SetPushedTexture("Interface/BUTTONS/UI-Panel-MinimizeButton-Down") |
closeButton:SetDisabledTexture("Interface/BUTTONS/UI-Panel-MinimizeButton-Disabled") |
closeButton:SetHighlightTexture("Interface/BUTTONS/UI-Panel-MinimizeButton-Highlight") |
closeButton:SetScript("OnClick", function () self:Hide() end) |
-- create lock button in the upper left corner of the frame |
local lockButton = CreateFrame("Button", nil, titleBar) |
self.lockButton = lockButton |
lockButton:SetWidth(24) |
lockButton:SetHeight(24) |
lockButton:SetPoint("LEFT", titleBar, "LEFT", 18, -3) |
lockButton:SetNormalTexture("Interface/LFGFrame/UI-LFG-ICON-LOCK") |
lockButton:SetScript("OnClick", function() if self.saved.locked then self:Unlock() else self:Lock() end end) |
-- initialize right-click menu |
self.menuTable = { |
{ text = self:GetText("lockPosition"), |
func = function () self:ToggleLock() end }--, |
-- { text = self:GetText("close"), |
-- func = function () self:Hide() end } |
} |
local menu = CreateFrame("Frame", "LearningAid_Menu", titleBar, "UIDropDownMenuTemplate") |
-- set drag and click handlers for the title bar |
titleBar:SetScript( |
"OnDragStart", |
function (bar, button) |
if not self.saved.locked then |
bar:GetParent():StartMoving() |
end |
end |
) |
titleBar:SetScript( |
"OnDragStop", |
function (bar) |
local parent = bar:GetParent() |
parent:StopMovingOrSizing() |
self.saved.x = parent:GetLeft() |
self.saved.y = parent:GetTop() |
end |
) |
titleBar:SetScript( |
"OnMouseUp", |
function (bar, button) |
if button == "MiddleButton" then |
self:Hide() |
elseif button == "RightButton" then |
EasyMenu(self.menuTable, menu, "cursor", 0, 8, "MENU", self.menuHideDelay) |
end |
end |
) |
--[[ |
Due to lack of foresight, some options have negative implications. |
shapeshift, if true, |
]] |
self.options = { |
handler = self, |
type = "group", |
args = { |
lock = { |
name = self:GetText("lockWindow"), |
desc = self:GetText("lockWindowHelp"), |
type = "toggle", |
set = function(info, val) if val then self:Lock() else self:Unlock() end end, |
get = function(info) return self.saved.locked end, |
width = "full", |
order = 40 |
}, |
restoreactions = { |
name = self:GetText("restoreActions"), |
desc = self:GetText("restoreActionsHelp"), |
type = "toggle", |
set = function(info, val) self.saved.restoreActions = val end, |
get = function(info) return self.saved.restoreActions end, |
width = "full", |
order = 30 |
}, |
--[[ No longer needed as of patch 6.2.0! |
filter = { |
name = self:GetText("showLearnSpam"), |
desc = self:GetText("showLearnSpamHelp"), |
type = "select", |
values = { |
[self.FILTER_SHOW_ALL ] = self:GetText("showAll"), |
[self.FILTER_SUMMARIZE] = self:GetText("summarize"), |
[self.FILTER_SHOW_NONE] = self:GetText("showNone") |
}, |
set = function(info, val) |
local old = self.saved.filterSpam |
if old ~= val then |
self.saved.filterSpam = val |
if val == self.FILTER_SHOW_ALL then |
self:DebugPrint("Removing chat filter for CHAT_MSG_SYSTEM") |
ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", private.spellSpamFilter) |
elseif old == self.FILTER_SHOW_ALL then |
self:DebugPrint("Adding chat filter for CHAT_MSG_SYSTEM") |
ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", private.spellSpamFilter) |
end |
end |
end, |
get = function(info) return self.saved.filterSpam end, |
order = 20, |
}, |
--]] |
reset = { |
name = self:GetText("resetPosition"), |
desc = self:GetText("resetPositionHelp"), |
type = "execute", |
func = "ResetFramePosition", |
--width = "full", |
order = 41 |
}, |
missing = { |
type = "group", |
inline = true, |
name = self:GetText("findMissingAbilities"), |
order = 10, |
args = { |
search = { |
name = self:GetText("searchMissing"), |
desc = self:GetText("searchMissingHelp"), |
type = "execute", |
func = "FindMissingActions", |
-- width = "full", |
order = 1 |
}, |
shapeshift = { |
name = self:GetText("findShapeshift"), |
desc = self:GetText("findShapeshiftHelp"), |
type = "toggle", |
set = function(info, val) self.saved.shapeshift = val end, |
get = function(info) return self.saved.shapeshift end, |
width = "full", |
order = 4 |
}, |
macros = { |
name = self:GetText("searchInsideMacros"), |
desc = self:GetText("searchInsideMacrosHelp"), |
type = "toggle", |
set = function(info, val) self.saved.macros = val end, |
get = function(info) return self.saved.macros end, |
width = "full", |
order = 3 |
}, |
ignore = { |
name = self:GetText("ignore"), |
desc = self:GetText("ignoreHelp"), |
type = "input", |
guiHidden = true, |
set = "ChatCommandIgnore", |
order = 10 |
}, |
unignore = { |
name = self:GetText("unignore"), |
desc = self:GetText("unignoreHelp"), |
type = "input", |
guiHidden = true, |
set = "ChatCommandUnignore", |
order = 11 |
}, |
unignoreall = { |
order = 12, |
name = self:GetText("unignoreAll"), |
desc = self:GetText("unignoreAllHelp"), |
type = "execute", |
-- width = "full", |
func = "UnignoreAll" |
} |
} |
}, |
unlock = { |
name = self:GetText("unlockWindow"), |
desc = self:GetText("unlockWindowHelp"), |
type = "execute", |
guiHidden = true, |
func = "Unlock" |
}, |
config = { |
name = self:GetText("configure"), |
desc = self:GetText("configureHelp"), |
type = "execute", |
func = function() InterfaceOptionsFrame_OpenToCategory(self.optionsFrame) end, |
guiHidden = true |
}, |
copybar = { |
name = self:GetText("copyActionBar"), |
desc = self:GetText("copyActionBarHelp"), |
guiHidden = true, |
type = "input", |
set = function (info, val) LA:CopyActionBar(val) end |
}, |
pastebar = { |
name = self:GetText("pasteActionBar"), |
desc = self:GetText("pasteActionBarHelp"), |
guiHidden = true, |
type = "input", |
set = function (info, val) LA:PasteActionBar(val) end |
}, |
advanced = { |
type = "group", |
inline = true, |
name = self:GetText("advanced"), |
args = { |
framestrata = { |
name = self:GetText("frameStrata"), |
desc = self:GetText("frameStrataHelp"), |
type = "select", |
values = { |
-- PARENT = "Parent", |
BACKGROUND = "Background", |
LOW = "Low", |
MEDIUM = "Medium", |
HIGH = "High", |
DIALOG = "Dialog", |
FULLSCREEN = "Fullscreen", |
FULLSCREEN_DIALOG = "Fullscreen Dialog", |
TOOLTIP = "Tooltip" |
}, |
set = function(info, val) |
self.saved.frameStrata = val |
self.frame:SetFrameStrata(val) |
end, |
get = function(info) return self.frame:GetFrameStrata() end, |
order = 1 |
}, |
-- only display debugging options if debugging is enabled |
debug = { |
name = self:GetText("debugOutput"), |
desc = self:GetText("debugOutputHelp"), |
values = { SET = "Assignment", GET = "Access", CALL = "Function Calls" }, |
type = "multiselect", |
set = function(info, key, val) self:Debug(key, val) end, |
get = function(info, key) return self:Debug(key) end, |
width = "full", |
order = 99, |
guiHidden = (0 == private.debug) |
} or nil |
} |
}, |
test = { |
type = "group", |
name = "Test", |
desc = "Perform various tests with Learning Aid.", |
hidden = true, |
guiHidden = true, |
args = { |
add = { |
type = "group", |
name = "Add", |
desc = "Add a button to the Learning Aid window.", |
args = { |
spell = { |
type = "input", |
name = "Spell", |
pattern = "^%d+$", |
set = function(info, val) |
self:AddButton(self.Spell.Book[tonumber(val)]) |
end |
}, |
all = { |
name = "All", |
desc = "The Kitchen Sink", |
type = "execute", |
func = function () |
local i = 1 |
local spellName = GetSpellBookItemName(i, BOOKTYPE_SPELL) |
while spellName do |
self:AddButton(self.Spell.Book[i]) |
i = i + 1 |
spellName = GetSpellBookItemName(i, BOOKTYPE_SPELL) |
end |
end |
} |
} |
}, |
remove = { |
type = "group", |
name = "Remove", |
desc = "Remove a button from the Learning Aid window.", |
args = { |
spell = { |
type = "input", |
name = "Spell", |
pattern = "^%d+$", |
set = function(info, val) |
self:ClearButtonID(BOOKTYPE_SPELL, tonumber(val)) |
end |
}, |
button = { |
type = "input", |
name = "Button", |
pattern = "^%d+$", |
set = function(info, val) |
self:ClearButtonIndex(tonumber(val)) |
end |
} |
} |
} |
} |
} |
} |
} |
if self.enClass == "SHAMAN" then |
self.options.args.missing.args.totem = { |
name = self:GetText("findTotem"), |
desc = self:GetText("findTotemHelp"), |
type = "toggle", |
set = function(info, val) self.saved.totem = val end, |
get = function(info) return self.saved.totem end, |
width = "full", |
order = 4 |
} |
end |
LibStub("AceConfig-3.0"):RegisterOptionsTable("LearningAidConfig", self.options, {"la", "learningaid"}) |
self:DebugPrint("Registering with AceConfig under '"..self:GetText("title").." "..self.version.."'") |
self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("LearningAidConfig", self:GetText("title").." "..self.version) |
hooksecurefunc("ConfirmTalentWipe", function() |
self:DebugPrint("ConfirmTalentWipe") |
self:SaveActionBars() |
self.state.untalenting = true |
--self.spellsUnlearned = {} |
self:RegisterEvent("ACTIONBAR_SLOT_CHANGED", "OnEvent") |
self:RegisterEvent("PLAYER_TALENT_UPDATE", "OnEvent") |
-- self:RegisterEvent("UI_ERROR_MESSAGE", "OnEvent") |
end) |
--[[ PANDARIA |
hooksecurefunc("LearnPreviewTalents", function(pet) |
self:DebugPrint("LearnPreviewTalents", pet) |
if pet then |
-- self.petLearning = true |
else |
self:RegisterEvent("PLAYER_TALENT_UPDATE", "OnEvent") |
--wipe(self.spellsLearned) |
--wipe(self.spellsUnlearned) |
self.state.learning = true |
end |
end) |
]] |
hooksecurefunc("SetCVar", function (cvar, value) |
cvar = string.lower(tostring(cvar)) |
value = tostring(value) |
self:DebugPrint("SetCVar("..cvar..", "..value..")") |
if cvar == "uiscale" or cvar == "useuiscale" then |
self:AutoSetMaxHeight() |
end |
end) |
--self.LearnTalent = LearnTalent |
self.pendingTalents = {} |
self.pendingTalentCount = 0 |
--[[-- TODO FIXME Rewrite entire talent handling code FIXME TODO -- |
hooksecurefunc("LearnTalent", function(tab, talent, pet, group, ...) |
self:DebugPrint("LearnTalent", tab, talent, pet, group, ...) |
local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq, unknown1, unknown2 = GetTalentInfo(tab, talent, false, pet, group) |
self:DebugPrint("GetTalentInfo", name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq, unknown1, unknown2) |
--self.LearnTalent(tab, talent, pet, group, ...) |
if rank < maxRank and meetsPrereq and not pet then |
--wipe(self.spellsLearned) |
--self.state.learning = true |
if self.pendingTalentCount == 0 then wipe(self.pendingTalents) end |
self:RegisterEvent("PLAYER_TALENT_UPDATE") |
local id = (group or GetActiveSpecGroup()).."."..tab.."."..talent.."."..rank |
if not self.pendingTalents[id] then |
self.pendingTalents[id] = true |
self.pendingTalentCount = self.pendingTalentCount + 1 |
end |
--self:DebugPrint(GetTalentInfo(tab, talent, false, pet, group)) |
end |
end) |
]]-- TODO FIXME Rewrite entire talent handling code FIXME TODO -- |
self:RegisterChatCommand("la", "AceSlashCommand") |
self:RegisterChatCommand("learningaid", "AceSlashCommand") |
--self:SetEnabledState(self.saved.enabled) |
--self.saved.enabled = true |
--self:DebugPrint("OnEnable()") |
local baseEvents = { |
"ACTIVE_TALENT_GROUP_CHANGED", |
"ADDON_LOADED", |
-- DRAENOR 6.2 -- "CHAT_MSG_SYSTEM", |
-- PANDARIA -- "COMPANION_LEARNED", |
-- PANDARIA -- "COMPANION_UPDATE", |
"PET_TALENT_UPDATE", |
"PLAYER_LEAVING_WORLD", |
"PLAYER_LEVEL_UP", |
"PLAYER_LOGIN", |
"PLAYER_LOGOUT", |
-- MOP -- "PLAYER_GUILD_UPDATE", |
"PLAYER_REGEN_DISABLED", |
"PLAYER_REGEN_ENABLED", |
-- "SPELLS_CHANGED", -- wait until PLAYER_LOGIN |
"UNIT_SPELLCAST_START", |
"UI_SCALE_CHANGED", |
-- "UPDATE_BINDINGS", -- PANDARIA -- not needed because of companion/mount removal |
"VARIABLES_LOADED" |
--[[ |
"CURRENT_SPELL_CAST_CHANGED", |
"SPELL_UPDATE_COOLDOWN", |
"TRADE_SKILL_CLOSE", |
"TRADE_SKILL_SHOW", |
"UNIT_SPELLCAST_SUCCEEDED" |
--]] |
} |
--if private.logAllEvents then |
-- self.frame:RegisterAllEvents() |
--else |
for i, event in ipairs(baseEvents) do |
self:RegisterEvent(event, "OnEvent") |
end |
--end |
--self:UpdateSpellBook() |
--PANDARIA |
--self:UpdateCompanions() |
self:DiffActionBars() |
self:SaveActionBars() |
if self.saved.filterSpam ~= LA.FILTER_SHOW_ALL then |
self:DebugPrint("Initially adding chat filter for CHAT_MSG_SYSTEM") |
ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", private.spellSpamFilter) |
end |
if self.saved.locked then |
self.menuTable[1].text = self:GetText("unlockPosition") |
else |
self.saved.locked = false |
end |
if self.saved.frameStrata then |
self.frame:SetFrameStrata(self.saved.frameStrata) |
end |
end |
--[[ No longer needed as of patch 6.2.0! |
-- this is a function |
function private.spellSpamFilter(...) return LA:spellSpamFilter(...) end |
-- this is a method |
function LA:spellSpamFilter(chatFrame, event, message, ...) |
-- local spell -- unused |
local patterns = self.patterns |
if (self.saved.filterSpam ~= self.FILTER_SHOW_ALL) and ( |
--( |
--self.state.untalenting or |
--self.state.retalenting or |
--(self.pendingTalentCount > 0) or |
--(self.saved.filterSpam == self.FILTER_SHOW_NONE) or |
--self.state.learning or |
-- self.petLearning or |
--(self.pendingBuyCount > 0) |
--) and ( |
string.match(message, patterns.learnSpell) or |
string.match(message, patterns.learnAbility) or |
string.match(message, patterns.learnPassive) or |
string.match(message, patterns.unlearnSpell) or |
-- ) |
--) or |
string.match(message, patterns.petLearnAbility) or |
string.match(message, patterns.petLearnSpell) or |
string.match(message, patterns.petUnlearnSpell) |
) then |
self:DebugPrint("Suppressing message") |
return true -- do not display the message |
else |
self:DebugPrint("Allowing message") |
return false, message, ... -- pass the message along |
end |
end |
--]] |
function LA:GetText(id, ...) |
if not id then |
if self.DebugPrint then |
self:DebugPrint("Nil supplied to GetText()") |
end |
return "Nil" |
end |
local result = "Invalid String ID '" .. id .. "'" |
if self.strings[self.locale] and self.strings[self.locale][id] then |
result = self.strings[self.locale][id] |
elseif self.strings.enUS[id] then |
result = self.strings.enUS[id] |
else |
self:DebugPrint(result) |
end |
return format(result, ...) |
end |
function LA:SetDefaultSettings() |
LearningAid_Saved = LearningAid_Saved or {} |
LearningAid_Character = LearningAid_Character or {} |
self.saved = LearningAid_Saved |
self.character = LearningAid_Character |
self.saved.version = self.version |
self.character.version = self.version |
self.saved.dataVersion = self.dataVersion |
self.character.dataVersion = self.dataVersion |
for key, value in pairs(self.defaults) do |
if self.saved[key] == nil then |
self.saved[key] = value |
end |
end |
self.saved.ignore[self.enClass] = self.saved.ignore[self.enClass] or { } |
self.saved.ignore.profession = self.saved.ignore.profession or { } |
self.saved.ignore.guild = self.saved.ignore.guild or { } |
self.saved.ignore.race = self.saved.ignore.race or { } |
self.ignore.class = self.saved.ignore[self.enClass] |
self.ignore.profession = self.saved.ignore.profession |
self.ignore.guild = self.saved.ignore.guild |
self.ignore.race = self.saved.ignore.race |
-- update with new debug option format as of 1.11 |
if self.saved.debug ~= nil then |
if self.saved.debug then |
self.saved.debugFlags = { SET = true, GET = true, CALL = true } |
end |
self.saved.debug = nil |
end |
for k, v in pairs(self.saved.debugFlags) do |
if v then |
self:Debug() |
break |
end |
end |
end |
function LA:RegisterEvent(event) |
self.frame:RegisterEvent(event) |
-- self.events[event] = true -- EVENT DEBUGGING |
end |
function LA:UnregisterEvent(event) |
self.frame:UnregisterEvent(event) |
-- self.events[event] = false -- EVENT DEBUGGING |
end |
function LA:UpgradeIgnoreList() |
local ignore = self.saved.ignore |
if ignore[self.localClass] then |
local oldIgnore = ignore[self.localClass] |
for spellLower, spellName in pairs(oldIgnore) do |
if type(spellLower) == "string" then -- old-style ignore list |
if self:ChatCommandIgnore(nil, spellName) then -- successfully converted format |
oldIgnore[spellLower] = nil |
end |
end |
end |
if self.localClass ~= self.enClass and not next(oldIgnore) then -- converted all old entries |
ignore[self.localClass] = nil |
end |
end |
end |
function LA:Ignore(spell) |
if "SPELL" == spell.Status then |
self.ignore[spell.ID] = true |
end |
-- FIXME FIXME FIXME -- do something with flyouts |
--[[ |
--local bookItem = self.spellBookCache[globalID] |
--local spell = self.Spell.Global[globalID] |
if spell and not spell.Passive then -- self.ignore[bookItem.origin] and |
--if bookItem.origin == self.origin.profession then |
self.ignore[bookItem.origin][bookItem.info.name] = true |
else |
self.ignore[bookItem.origin][globalID] = true |
end |
self:UpdateButtons() |
return true |
end |
return false |
]] |
end |
function LA:ChatCommandIgnore(info, str) |
str = strtrim(str) |
if #str == 0 then |
-- print ignore list to the chat frame |
for origin, t in pairs(self.ignore) do |
for globalID, v in pairs(t) do |
DEFAULT_CHAT_FRAME:AddMessage(self:GetText("title")..": ".. self:GetText("listIgnored", GetSpellLink(globalID))) |
end |
end |
else |
local status, globalID = GetSpellBookItemInfo(str, BOOKTYPE_SPELL) |
-- globalID = globalID or select(2, self:UnlinkSpell(str)) -- redundant |
if "SPELL" == status then |
return self:Ignore(self.Spell.Global[globalID]) |
end |
end |
end |
function LA:ChatCommandUnignore(info, str) |
local status, globalID = GetSpellBookItemInfo(str:trim(), BOOKTYPE_SPELL) |
-- globalID = globalID or select(2, self:UnlinkSpell(str)) |
if "SPELL" == status then |
self:Unignore(self.Spell.Global[globalID]) |
end |
end |
function LA:Unignore(spell) |
-- local spell = self.Spell.Book[globalID] |
-- if bookItem and self.ignore[bookItem.origin] then |
-- if bookItem.origin == self.origin.profession then |
-- self.ignore[bookItem.origin][bookItem.info.name] = nil |
self.ignore[spell.ID] = nil |
-- elseif self.ignore[bookItem.origin][globalID] then |
-- self.ignore[bookItem.origin][globalID] = nil |
-- end |
self:UpdateButtons() |
-- return true |
-- end |
-- return false |
end |
function LA:IsIgnored(spell) |
--local bookItem = self.spellBookCache[globalID] |
--if bookItem and self.ignore[bookItem.origin] then |
-- if bookItem.origin == self.origin.profession then |
-- return self.ignore[bookItem.origin][bookItem.info.name] |
-- else |
-- return self.ignore[bookItem.origin][globalID] |
return self.ignore[spell.ID] |
-- end |
--end |
end |
function LA:ToggleIgnore(spell) |
if self:IsIgnored(spell) then |
self:Unignore(spell) |
else |
self:Ignore(spell) |
end |
end |
function LA:UnignoreAll() |
--for kind, list in pairs(self.ignore) do |
-- wipe(list) |
wipe(self.ignore) |
--end |
end |
function LA:ResetFramePosition() |
local frame = self.frame |
frame:ClearAllPoints() |
frame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -200, -200) |
self.saved.x = frame:GetLeft() |
self.saved.y = frame:GetTop() |
end |
function LA:AceSlashCommand(msg) |
LibStub("AceConfigCmd-3.0").HandleCommand(LearningAid, "la", "LearningAidConfig", msg) |
end |
function LA:SystemPrint(message) |
local systemInfo = ChatTypeInfo["SYSTEM"] |
DEFAULT_CHAT_FRAME:AddMessage(LA:GetText("title")..": "..message, systemInfo.r, systemInfo.g, systemInfo.b, systemInfo.id) |
end |
function LA:ProcessQueue() |
if InCombatLockdown() then |
self:DebugPrint("ProcessQueue(): Cannot process action queue during combat.") |
else |
self.queue = self.queue or { } |
local queue = self.queue |
for index = 1, #queue do |
local item = queue[index] |
if item.action == "SHOW" then |
self:AddButton(self.Spell.Global[item.id]) |
elseif item.action == "CLEAR" then |
self:ClearButtonID(item.id) |
-- elseif item.kind == BOOKTYPE_SPELL then |
if item.action == "LEARN" then |
self:AddSpell(item.id) |
elseif item.action == "FORGET" then |
self:RemoveSpell(item.id) |
else |
self:DebugPrint("ProcessQueue(): Invalid action type " .. item.action) |
end |
--[[ PANDARIA |
elseif item.kind == "CRITTER" or item.kind == "MOUNT" then |
if item.action == "LEARN" then |
self:AddCompanion(item.kind, item.id) |
else |
self:DebugPrint("ProcessQueue(): Invalid action type " .. item.action) |
end |
]] |
elseif item.action == "HIDE" then |
self:Hide() |
else |
self:DebugPrint("ProcessQueue(): Invalid entry type " .. item.action) |
end |
end |
wipe(self.queue) |
end |
end |
-- Possibly obsolete as of 6.2.0 |
function LA:FormatSpells(t) |
local str = "" |
for i, spell in ipairs(t) do |
str = str .. ("|T%s:0|t"):format(spell.SpecTexture) .. spell.SpecLink .. ", " |
end |
if #t > 0 then |
return string.sub(str, 1, -3) -- trim off final ", " |
else |
return nil |
end |
end |
local function spellCompare (a,b) |
return a.SpecName < b.SpecName |
end |
--[[ No longer needed as of patch 6.2.0! |
function LA:PrintPending() |
local learned = self.spellsLearned |
local unlearned = self.spellsUnlearned |
self:DebugPrint('Learned '..(#learned)..', unlearned '..#unlearned) |
if self.saved.filterSpam == self.FILTER_SUMMARIZE then |
-- lots of work just to remove stuff that's unlearned and then immediately relearned |
if #learned > 0 and #unlearned > 0 then |
self:DebugPrint('Removing duplicate spells') |
local spells = { } |
local learnedDupes = { } |
local unlearnedDupes = { } |
local specName |
-- Create a lookup table of "SpellSpecName" = n for each index in |
-- LA.spellsLearned[] and LA.spellsUnlearned[] |
for index, spell in ipairs(learned) do |
spells[spell.SpecName] = index |
end |
for index, spell in ipairs(unlearned) do |
specName = spell.SpecName |
if spells[specName] then |
-- do not disturb the table while traversing it |
tinsert(learnedDupes, spells[specName]) |
tinsert(unlearnedDupes, index) |
end |
end |
-- Ensure the indices are in order |
table.sort(learnedDupes) |
-- go backwards so later indices don't change when removing earlier elements |
for i = #learnedDupes, 1, -1 do |
tremove(learned, learnedDupes[i]) |
end |
table.sort(unlearnedDupes) |
for i = #unlearnedDupes, 1, -1 do |
tremove(unlearned, unlearnedDupes[i]) |
end |
-- phew! |
end |
table.sort(learned, spellCompare) |
table.sort(unlearned, spellCompare) |
local learnedString = self:FormatSpells(learned) |
self:DebugPrint("learned", learnedString) |
local unlearnedString = self:FormatSpells(unlearned) |
self:DebugPrint("unlearned", unlearnedString) |
if unlearnedString then self:SystemPrint(self:GetText("youHaveUnlearned", unlearnedString)) end |
if learnedString then self:SystemPrint(self:GetText("youHaveLearned", learnedString)) end |
if #self.petUnlearned > 0 then |
table.sort(self.petUnlearned) |
self:SystemPrint(self:GetText("yourPetHasUnlearned", self:ListJoin(self.petUnlearned))) |
end |
if #self.petLearned > 0 then |
table.sort(self.petLearned) |
self:SystemPrint(self:GetText("yourPetHasLearned", self:ListJoin(self.petLearned))) |
end |
end |
wipe(self.petLearned) |
wipe(self.petUnlearned) |
wipe(self.spellsLearned) |
wipe(self.spellsUnlearned) |
wipe(self.pendingTalents) |
end |
--]] |
function LA:OnShow() |
-- PANDARIA -- self:RegisterEvent("COMPANION_UPDATE", "OnEvent") |
self:RegisterEvent("TRADE_SKILL_SHOW", "OnEvent") |
self:RegisterEvent("TRADE_SKILL_CLOSE", "OnEvent") |
self:RegisterEvent("SPELL_UPDATE_COOLDOWN", "OnEvent") |
self:RegisterEvent("CURRENT_SPELL_CAST_CHANGED", "OnEvent") |
end |
function LA:OnHide() |
-- PANDARIA -- self:UnregisterEvent("COMPANION_UPDATE") |
self:UnregisterEvent("TRADE_SKILL_SHOW") |
self:UnregisterEvent("TRADE_SKILL_CLOSE") |
self:UnregisterEvent("SPELL_UPDATE_COOLDOWN") |
self:UnregisterEvent("CURRENT_SPELL_CAST_CHANGED") |
end |
function LA:Lock() |
self.saved.locked = true |
self.menuTable[1].text = self:GetText("unlockPosition") |
end |
function LA:Unlock() |
self.saved.locked = false |
self.menuTable[1].text = self:GetText("lockPosition") |
end |
function LA:ToggleLock() |
if self.saved.locked then |
self:Unlock() |
else |
self:Lock() |
end |
end |
function LA:PurgeConfig() |
wipe(self.saved) |
wipe(self.character) |
self:SetDefaultSettings() |
end |
<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="Libs\LibStub\LibStub.lua" /> |
<Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml" /> |
<Include file="Libs\AceGUI-3.0\AceGUI-3.0.xml" /> |
<Include file="Libs\AceConfig-3.0\AceConfig-3.0.xml" /> |
<Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml" /> |
</Ui> |
local addonName, private = ... |
local LA = private.LA |
LA.strings.enUS = { |
title = "Learning Aid", |
lockPosition = "Lock Position", |
unlockPosition = "Unlock Position", |
close = "Close Window", |
youHaveLearned = "You have learned %s.", |
youHaveUnlearned = "You have unlearned %s.", |
yourPetHasLearned = "Your pet has learned %s.", |
yourPetHasUnlearned = "Your pet has unlearned %s.", |
lockWindow = "Lock Window", |
lockWindowHelp = "Locks the Learning Aid window so it cannot by moved by accident.", |
restoreActions = "Restore Actions", |
restoreActionsHelp = "When re-learning talent-based abilities, restore their position on your action bars.", |
--showLearnSpam = "Show Learn / Unlearn Messages", |
--showLearnSpamHelp = 'Show All: Blizzard default. Summarize: Reduce the messages to a one or two line compact form. Show None: Do not display any learn/unlearn messages in the chat log.', |
--showAll = "Show All", |
--summarize = "Summarize", |
--showNone = "Show None", |
debugOutput = "Debug Output", |
debugOutputHelp = "Enables / disables printing debugging information to the chat window.", |
resetPosition = "Reset Position", |
resetPositionHelp = "Reset the position of the Learning Aid window to the default.", |
findMissingAbilities = "Find Missing Abilities", |
searchMissing = "Search", |
searchMissingHelp = "Search the spellbook and action bars to find spells or abilities which are not on any action bar.", |
findTracking = "Find Tracking Abilities", |
findTrackingHelp = "If enabled, Find Missing Abilities will search for tracking abilities as well.", |
findShapeshift = "Find Shapeshift Forms", |
findShapeshiftHelp = "If enabled, Find Missing Abilities will search for forms, auras, stances, presences, etc.", |
searchInsideMacros = "Search Inside Macros", |
searchInsideMacrosHelp = "If enabled, Find Missing Abilities will search inside macros for spells.", |
unlockWindow = "Unlock Window", |
unlockWindowHelp = "Unlocks the Learning Aid window so it can be moved.", |
configure = "Configure", |
configureHelp = "Open the Learning Aid configuration panel.", |
errorInCombat = "Cannot do that in combat.", |
findTotem = "Find Totem Spells", |
findTotemHelp = "If enabled, Find Missing Abilities will search for totem summoning spells.", |
-- Strings added in 1.09 |
ignore = "Ignore", |
ignoreHelp = "Find Missing Abilities will ignore this ability.", |
unignore = "Unignore", |
unignoreHelp = "Find Missing Abilities will no longer ignore this ability.", |
unignoreAll = "Unignore All", |
unignoreAllHelp = "Clear the ignore list.", |
listIgnored = "Ability %s is ignored.", |
ctrlToIgnore = "|cFFFF2222Ctrl-click|r |cFFFFFFFFto toggle ignore.|r", |
-- Strings added in 1.11 |
trainAllButton = "Train All", |
trainAllPopup = "Train all available skills for", |
frameStrata = "Frame Strata", |
frameStrataHelp = "Adjust what frames Learning Aid appears above and below. Only change this if you have problems with frames overlapping each other.", |
advanced = "Advanced settings" |
} |
local addonName, private = ... |
local LA = private.LA |
LA.strings.zhTW = { |
title = "Learning Aid", |
lockPosition = "éå®ä½ç½®", |
unlockPosition = "解éä½ç½®", |
close = "ééçªé«", |
youHaveLearned = "妳已å¸å¾ %s.", |
youHaveUnlearned = "妳已éºå¿ %s.", |
yourPetHasLearned = "妳ç寵ç©å¸å¾ %s.", |
yourPetHasUnlearned = "妳ç寵ç©éºå¿ %s.", |
lockWindow = "éå®çªé«", |
lockWindowHelp = "éå® Learning Aid çªé«.", |
restoreActions = "æ¢å¾©åä½æ¢", |
restoreActionsHelp = "ç¶éæ°å¸å¾åºæ¼å¤©è³¦çæè½, æ¢å¾©ä»åå¨å¦³åä½æ¢ä¸çä½ç½®.", |
--showLearnSpam = "顯示已å¸/éºå¿è¨æ¯", |
--showLearnSpamHelp = 'é¡¯ç¤ºå ¨é¨: æ´éªé»èª. 總æ¬: 精簡è¨æ¯çº1å°2è¡ç表å®. ä¸é¡¯ç¤º: ä¸é¡¯ç¤ºä»»ä½è¨æ¯.', |
--showAll = "é¡¯ç¤ºå ¨é¨", |
--summarize = "顯示總æ¬", |
--showNone = "ä¸é¡¯ç¤º", |
debugOutput = "Debug 輸åº", |
debugOutputHelp = "éå / ç¦ç¨å¨è天æ¡è¼¸åºDebuffè¨æ¯.", |
resetPosition = "éç½®ä½ç½®", |
resetPositionHelp = "éç½® Learning Aid çªé«çä½ç½®çºé»èª.", |
findMissingAbilities = "å°æ¾ç¼ºå¤±çæè½", |
searchMissing = "æç´¢", |
searchMissingHelp = "æç´¢æ³è¡æ¸ååä½æ¢ä¾å°æ¾ä¸å¨ä»»ä½åä½æ¢ä¸çæ³è¡ææè½.", |
findTracking = "å°æ¾è¿½è¹¤æè½", |
findTrackingHelp = "å¦æåç¨, å°æ¾ç¼ºå¤±æè½ä¹ææ索追蹤é¡æè½.", |
findShapeshift = "å°æ¾è®å½¢æè½", |
findShapeshiftHelp = "å¦æåç¨, å°æ¾ç¼ºå¤±æè½ä¹ææç´¢è®å½¢ãå ç°ã姿æ çæè½.", |
searchInsideMacros = "æ索巨éå §æ³è¡", |
searchInsideMacrosHelp = "å¦æåç¨, å°æ¾ç¼ºå¤±æè½ä¹ææ索巨éå §çæ³è¡.", |
unlockWindow = "解éçªé«", |
unlockWindowHelp = "解é Learning Aid çªé«ä¾ç§»å.", |
configure = "é ç½®", |
configureHelp = "æé Learning Aid é ç½®é¢æ¿.", |
errorInCombat = "ä¸è½å¨æ°é¬¥ä¸é£éº½å.", |
findTotem = "å°æ¾å騰æ³è¡", |
findTotemHelp = "å¦æåç¨, å°æ¾ç¼ºå¤±æè½ä¹ææç´¢å騰å¬åæ³è¡.", |
-- Strings added in 1.09 |
ignore = "忽ç¥", |
ignoreHelp = "å°æ¾ç¼ºå¤±æè½å°å¿½ç¥éåæè½.", |
unignore = "ä¸å忽ç¥", |
unignoreHelp = "å°æ¾ç¼ºå¤±æè½å°ä¸å忽ç¥éåæè½..", |
unignoreAll = "ä¸å忽ç¥å ¨é¨", |
unignoreAllHelp = "æ¸ é¤å¿½ç¥å表.", |
listIgnored = "æè½ %s 已被忽ç¥.", |
ctrlToIgnore = "|cFFFF2222Ctrl-é»æ|r |cFFFFFFFFä¾å¿½ç¥/ä¸å¿½ç¥.|r" |
} |
local addonName, private = ... |
local LA = private.LA |
LA.strings.deDE = { |
Notes = "Zeigt eine Leiste mit Zauberspr\195\188chen, F\195\164higkeiten, Berufsfertigkeiten, Reittieren und Fahrzeugen, oder Begleitern, die du gerade erst gelernt hast. Von Jamash (Kil'jaeden-US)", |
close = "Fenster schlie\195\159en", |
configure = "Einstellungen", |
configureHelp = "\195\150ffnet das \"Learning Aid\"-Konfigurations-Fenster.", |
ctrlToIgnore = " |cFFFF2222Strg-Klick|r |cFFFFFFFFzum Umschalten von Ignorieren.|r", |
debugOutput = "Ausgabe debuggen", |
debugOutputHelp = "Aktiviert / deaktiviert die Ausgabe von Debugging-Informationen in das Chatfenster", |
errorInCombat = "Im Kampf nicht m\195\182glich.", |
findMissingAbilities = "Suche fehlende F\195\164higkeiten", |
findShapeshift = "Finde Gestaltwandel-Formen", |
findShapeshiftHelp = "Wenn diese Option aktiviert ist, sucht \"Finde fehlende F\195\164higkeiten\" gleichzeitig auch Gestaltwandel-Formen, Auren, Haltungen, Pr\195\164senzen, etc.", |
findTotem = "Suche Totem-Zauber", |
findTotemHelp = "Wenn eingeschaltet, wird \"Suche fehlende F\195\164higkeiten\" nach F\195\164higkeiten zum Herbeizaubern von Totems suchen.", |
findTracking = "Finde Aufsp\195\188r-F\195\164higkeiten", |
findTrackingHelp = "Wenn diese Option aktiviert ist, sucht \"Finde fehlende F\195\164higkeiten\" gleichzeitig auch Aufsp\195\188r-F\195\164higkeiten.", |
ignore = "Ignorieren", |
ignoreHelp = "Finde fehlende F\195\164higkeiten wird diese F\195\164higkeit ignorieren.", |
listIgnored = "F\195\164higkeit %s wird ignoriert.", |
lockPosition = "Position fixieren", |
lockWindow = "Fenster fixieren", |
lockWindowHelp = "Fixiert das \"Learning Aid\"-Fenster, um es nicht zuf\195\164llig zu verschieben.", |
resetPosition = "Position zur\195\188cksetzen", |
resetPositionHelp = "Setzt die Position des Learning Aid Fensters auf Standard zur\195\188ck.", |
restoreActions = "Handlungen wiederherstellen", |
restoreActionsHelp = "Positionen auf den Aktionsleisten wiederherstellen, wenn talentbasierte F\195\164higkeiten erneut gelernt werden.", |
searchInsideMacros = "Suche innerhalb von Makros", |
searchInsideMacrosHelp = "Wenn diese Option aktiviert ist, sucht \"Finde fehlende F\195\164higkeiten\" gleichzeitig auch innerhalb von Makros nach Zauberspr\195\188chen.", |
searchMissing = "Suche", |
searchMissingHelp = "Durchsucht das Zauberbuch und die Aktionsleisten, um Zauberspr\195\188che und F\195\164higkeiten zu finden, die auf keiner Aktionsleiste vorhanden sind.", |
--showAll = "Zeige alles", |
--showLearnSpam = "Zeige gelernt / verlernt Nachrichten", |
--showLearnSpamHelp = "Zeige alles: Blizzard (standardm\195\164\195\159ig) Zusammenfassen: Reduziert die Nachrichten auf eine ein- oder zweizeilige kompakte Form. Zeige nichts: Zeige keine gelernt/verlernt Nachrichten im Chat Log.", |
--showNone = "Zeige nichts", |
--summarize = "Zusammenfassen", |
title = "Learning Aid", |
unignore = "Ignorieren ausschalten", |
unignoreAll = "Ignorieren \195\188berall ausschallten", |
unignoreAllHelp = "Ignorierliste l\195\182schen", |
unignoreHelp = "Finde fehlende F\195\164higkeiten wird diese F\195\164higkeit nicht mehr ignorieren.", |
unlockPosition = "Position freigeben", |
unlockWindow = "Fenster freisetzen", |
unlockWindowHelp = "Setzt das \"Learning Aid\"- Fenster frei, damit es bewegt werden kann.", |
youHaveLearned = "Du hast %s gelernt.", |
youHaveUnlearned = "Du hast %s verlernt.", |
yourPetHasLearned = "Dein Tier hat %s gelernt.", |
yourPetHasUnlearned = "Dein Tier hat %s verlernt.", |
} |
local addonName, private = ... |
local LA = private.LA |
LA.strings.zhCN = { |
title = "Learning Aid", |
lockPosition = "éå®ä½ç½®", |
unlockPosition = "解éä½ç½®", |
close = "å ³éçªå£", |
youHaveLearned = "ä½ å·²å¦å¾ %s.", |
youHaveUnlearned = "ä½ å·²éå¿ %s.", |
yourPetHasLearned = "ä½ çå® ç©å¦å¾ %s.", |
yourPetHasUnlearned = "ä½ çå® ç©éå¿ %s.", |
lockWindow = "éå®çªå£", |
lockWindowHelp = "éå® Learning Aid çªä½.", |
restoreActions = "æ¢å¤å¨ä½æ¡", |
restoreActionsHelp = "å½éæ°å¦å¾åºäºå¤©èµçæè½, æ¢å¤ä»ä»¬å¨ä½ å¨ä½æ¡ä¸çä½ç½®.", |
--showLearnSpam = "æ¾ç¤ºå·²å¦/éå¿è®¯æ¯", |
--showLearnSpamHelp = 'æ¾ç¤ºå ¨é¨: æ´éªé»è®¤. æ»æ½: ç²¾ç®è®¯æ¯ä¸º1å°2è¡ç表å. ä¸æ¾ç¤º: ä¸æ¾ç¤ºä»»ä½è®¯æ¯.', |
--showAll = "æ¾ç¤ºå ¨é¨", |
--summarize = "æ¾ç¤ºæ»æ½", |
--showNone = "ä¸æ¾ç¤º", |
debugOutput = "Debug è¾åº", |
debugOutputHelp = "å¼å¯ / ç¦ç¨å¨è天æ¡è¾åºDebuff讯æ¯.", |
resetPosition = "éç½®ä½ç½®", |
resetPositionHelp = "éç½® Learning Aid çªå£çä½ç½®ä¸ºé»è®¤.", |
findMissingAbilities = "寻æ¾ç¼ºå¤±çæè½", |
searchMissing = "æç´¢", |
searchMissingHelp = "æç´¢æ³æ¯ä¹¦åå¨ä½æ¡æ¥å¯»æ¾ä¸å¨ä»»ä½å¨ä½æ¡ä¸çæ³æ¯ææè½.", |
findTracking = "寻æ¾è¿½è¸ªæè½", |
findTrackingHelp = "å¦æå¯ç¨, 寻æ¾ç¼ºå¤±æè½ä¹ä¼æ索追踪类æè½.", |
findShapeshift = "寻æ¾åå½¢æè½", |
findShapeshiftHelp = "å¦æå¯ç¨, 寻æ¾ç¼ºå¤±æè½ä¹ä¼æç´¢åå½¢ãå ç¯ã姿æçæè½.", |
searchInsideMacros = "æç´¢å®å æ³æ¯", |
searchInsideMacrosHelp = "å¦æå¯ç¨, 寻æ¾ç¼ºå¤±æè½ä¹ä¼æç´¢å®å çæ³æ¯.", |
unlockWindow = "解éçªå£", |
unlockWindowHelp = "解é Learning Aid çªå£æ¥ç§»å¨.", |
configure = "é ç½®", |
configureHelp = "æå¼ Learning Aid é ç½®é¢æ¿.", |
errorInCombat = "ä¸è½å¨ææä¸é£ä¹å.", |
findTotem = "寻æ¾å¾è ¾æ³æ¯", |
findTotemHelp = "å¦æå¯ç¨, 寻æ¾ç¼ºå¤±æè½ä¹ä¼æç´¢å¾è ¾å¬å¤æ³æ¯.", |
-- Strings added in 1.09 |
ignore = "忽ç¥", |
ignoreHelp = "寻æ¾ç¼ºå¤±æè½å°å¿½ç¥è¿ä¸ªæè½.", |
unignore = "ä¸å忽ç¥", |
unignoreHelp = "寻æ¾ç¼ºå¤±æè½å°ä¸å忽ç¥è¿ä¸ªæè½..", |
unignoreAll = "ä¸å忽ç¥å ¨é¨", |
unignoreAllHelp = "æ¸ é¤å¿½ç¥å表.", |
listIgnored = "æè½ %s 已被忽ç¥.", |
ctrlToIgnore = "|cFFFF2222Ctrl-ç¹å»|r |cFFFFFFFFæ¥å¿½ç¥/ä¸å¿½ç¥.|r" |
} |
local addonName, private = ... |
local LA = private.LA |
LA.strings.koKR = { |
close = "ì°½ ë«ê¸°", |
configure = "ì¤ì ", |
configureHelp = "Learning Aid ì¤ì í¨ëì ì½ëë¤.", |
ctrlToIgnore = "ë¥ë ¥ 무ì를 ì ííë ¤ë©´ |cFFFF2222Ctrl-í´ë¦|r |cFFFFFFFF íììì¤.", |
debugOutput = "ëë²ê·¸ ì¶ë ¥", |
debugOutputHelp = "ëíì°½ì ëë²ê¹ ì 보를 ì¶ë ¥íë 기ë¥ì ì¬ì©/ì¬ì©íì§ ììµëë¤.", |
errorInCombat = "ì í¬ì¤ìë ê·¸ê²ì í ì ììµëë¤.", |
findMissingAbilities = "ëë½ë ë¥ë ¥ 찾기", |
findShapeshift = "ë³ì íì 찾기", |
findShapeshiftHelp = "ì¬ì©ì, ëë½ë ë¥ë ¥ì ì°¾ë ëìì, ë³ì , ì¤ë¼, íì¸, presences ë±ì ë§ì°¬ê°ì§ë¡ ê²ìí©ëë¤.", |
findTotem = "í í 주문 찾기", |
findTotemHelp = "ì¬ì©ì, ëë½ë ë¥ë ¥ì ì°¾ë ëìì, í í ìí 주문ë ë§ì°¬ê°ì§ë¡ ê²ìí©ëë¤.", |
findTracking = "ì¶ì ë¥ë ¥ 찾기", |
findTrackingHelp = "ì¬ì©ì, ëë½ë ë¥ë ¥ì ì°¾ë ëìì, ì¶ì ë¥ë ¥ì ëí´ìë ë§ì°¬ê°ì§ë¡ ê²ìí©ëë¤.", |
ignore = "무ì", |
ignoreHelp = "ëë½ë ë¥ë ¥ì ì°¾ë ëìì, ì´ ë¥ë ¥ì 무ìí©ëë¤.", |
listIgnored = "ë¥ë ¥ %s|ì´;ê°; 무ìëììµëë¤.", |
lockPosition = "ìì¹ ì 그기", |
lockWindow = "ì°½ ì 그기", |
lockWindowHelp = "Learning Aid ì°½ì ì못íì¬ ì´ëìí¬ ì ìëë¡ ì ê¸ëë¤.", |
Notes = "íì¬ ìµëí 주문, ë¥ë ¥, ì 문기ì , íê² í¹ì ì ìë물ì ë°ë¥¼ íìí©ëë¤. ë§ë ì´: Jamash (Kil'jaeden-US)", |
resetPosition = "ìì¹ ì´ê¸°í", |
resetPositionHelp = "Learning Aid ì°½ì 기본 ìì¹ë¡ ì´ê¸°í í©ëë¤.", |
restoreActions = "íë 복구", |
restoreActionsHelp = "í¹ì±ì 기ë°í ë¥ë ¥ì ì¬ìµëí ê²½ì°ì, íë ë¨ì¶ë°ì ê·¸ ìì¹ë¥¼ 복구í©ëë¤.", |
searchInsideMacros = "ë´ë¶ 매í¬ë¡ 찾기", |
searchInsideMacrosHelp = "ì¬ì©ì, ëë½ë ë¥ë ¥ì ì°¾ë ëìì, 주문ì ìí ë´ë¶ 매í¬ë¡ë ë§ì°¬ê°ì§ë¡ ê²ìí©ëë¤.", |
searchMissing = "ê²ì", |
searchMissingHelp = "모ë íë ë¨ì¶ë°ì ìë 주문 í¹ì ë¥ë ¥ì 찾기 ìí´ ë§ë²ì± ê³¼ íë ë¨ì¶ë°ë¥¼ ê²ìí©ëë¤.", |
--showAll = "모ë ë³´ì´ê¸°", |
--showLearnSpam = "ìµë/ìµë ì·¨ì ë©ìì§ ë³´ì´ê¸°", |
--showLearnSpamHelp = "모ë ë³´ì´ê¸°: ë¸ë¦¬ìë 기본ì ë°ë¼ íìí©ëë¤. ìì½: íë í¹ì ëì¤ì ê°ê²°í íìì¼ë¡ ë©ìì§ë¥¼ ì¤ì ëë¤. ë³´ì´ì§ ì기: ëíì°½ 기ë¡ì ì´ë¤ ìµë/ìµë ì·¨ì ë©ìì§ë íìíì§ ììµëë¤.", |
--showNone = "ë³´ì´ì§ ì기", |
--summarize = "ìì½", |
title = "Learning Aid", |
unignore = "무ì ì기", |
unignoreAll = "모ë 무ì ì기", |
unignoreAllHelp = "무ì 목ë¡ì ìì í©ëë¤.", |
unignoreHelp = "ëë½ë ë¥ë ¥ì ì°¾ë ëìì, ë ì´ì ì´ ë¥ë ¥ì 무ìíì§ ììµëë¤.", |
unlockPosition = "ìì¹ í기", |
unlockWindow = "ì°½ í기", |
unlockWindowHelp = "Learning Aid ì°½ì ì´ëí ì ìëë¡ ì ê¹ ìí를 íëë¤.", |
youHaveLearned = "%s|1ì;를; ìµëíìµëë¤.", |
youHaveUnlearned = "%s ìµëì ì·¨ìíìµëë¤.", |
yourPetHasLearned = "ëì ìíìê° %s|1ì;를; ìµëíìµëë¤.", |
yourPetHasUnlearned = "ëì ìíìê° %s ìµëì ì·¨ìíìµëë¤.", |
} |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
Spell.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
-- Transforms a spellbook ID into a global spell ID |
function LA:SpellGlobalID(id) |
-- CATA -- |
-- local link = GetSpellLink(id, BOOKTYPE_SPELL) |
-- if link then |
-- local globalID = string.match(link, "Hspell:([^\124]+)\124") |
-- return tonumber(globalID) |
-- end |
return select(2, GetSpellBookItemInfo(id, BOOKTYPE_SPELL)) |
end |
-- GetSpellLink(bookID, "spell") will return current spec link, which will fail when fed to IsSpellKnown and company |
function LA:UnlinkSpell(link) |
assert(link, "LearningAid:UnlinkSpell(link): bad link", tostring(link)) |
local globalID, name = string.match(link, "Hspell:([^|]+)|h%[([^]]+)%]") |
return name, tonumber(globalID) |
end |
function LA:RealSpellBookItemInfo(slot, bookType) |
-- returns status, globalID, spec-specific name, spec-specific globalID |
if not bookType then bookType = BOOKTYPE_SPELL end |
assert(slot and slot > 0, "LearningAid:RealSpellBookItemInfo(spellBookID [, bookType]): bad spellBookID") |
local spellStatus, spellGlobalID = GetSpellBookItemInfo(slot, bookType) |
if "SPELL" == spellStatus or "FUTURESPELL" == spellStatus then |
return spellStatus, spellGlobalID, self:UnlinkSpell(GetSpellLink(slot, bookType)) |
end |
return spellStatus, spellGlobalID |
end |
function LA:FlyoutInfo(flyoutID) |
local flyoutName, flyoutDescription, numFlyoutSpells, flyoutKnown = GetFlyoutInfo(flyoutID) |
return { |
known = flyoutKnown, |
name = flyoutName, |
count = numFlyoutSpells, |
description = flyoutDescription |
} |
end |
function LA:UpdateSpellBook() |
wipe(self.flyoutCache) |
local known = self.knownSpells |
wipe(known) |
local total = 0 |
local professions = { GetProfessions() } |
-- { Primary1, Primary2, Archaeology, Fishing, Cooking, First Aid } |
local numKnown = 0 |
for i = 1, self.numProfessions do |
if professions[i] then |
local name, texture, rank, maxRank, numSpells, spellOffset, skillLine, rankModifier = GetProfessionInfo(professions[i]) |
numKnown = numKnown + numSpells |
total = total + numSpells |
end |
end |
--local racial = self.Spell.Global[self.racialSpell].SubName |
--local racialPassive = self.Spell.Global[self.racialPassiveSpell].SubName |
-- tab 1 is general, tab 2 is current spec, tabs 3, 4 (and possibly 5 if druid) |
-- are other specs. The remaining tabs are all professions, I think. |
local s2g = self.specToGlobal |
local g2s = self.globalToSpec |
for tab = 1, 2 do |
local tabName, tabTexture, tabOffset, tabSpells, tabIsGuild, offspecID = GetSpellTabInfo(tab) |
for slot = tabOffset + 1, tabOffset + tabSpells do |
-- using RealSpellBookItemInfo instead of Spell.Book for spells |
-- because this is where the SpellAPI gets specToGlobal and globalToSpec |
local status, globalID, specName, specGlobalID = self:RealSpellBookItemInfo(slot, BOOKTYPE_SPELL) |
if "FLYOUT" == status then |
-- flyout spells are not included in the regular spell tabs, they're |
-- in gaps between the last index of one tab and the first index of |
-- the next tab |
local flyout = self.Spell.Book[slot] |
-- SpellBookSlotBySpellID doesn't work on flyouts. Storing slot |
-- information in self.flyoutCache provides an equivalent mechanism |
-- for flyouts |
self.flyoutCache[globalID] = slot |
for spell in flyout.Spells do |
-- all flyout spells are class-based as of 4.1.0 |
if spell.Known then -- should always be true, but why take chances? |
numKnown = numKnown + 1 |
total = total + 1 |
end |
end |
elseif "SPELL" == status then -- unknown spells would have status "FUTURESPELL" |
known[globalID] = slot |
if specGlobalID ~= globalID then |
-- sometimes it's difficult to tell which spells are which (Mangle in particular) |
s2g[specGlobalID] = globalID |
g2s[globalID] = specGlobalID |
self.nameToGlobal[strlower(specName)] = globalID |
else |
-- Both IDs are the same. |
-- Sometimes certain spells, such as Monk's Flying Serpent Kick go |
-- from having different IDs to having the same ID. |
-- Check for old invalid cache entries. |
-- If new ID is N, old global id is G, and old spec id is S |
--- s2g[G] == S |
--- g2s[S] == G |
--- Both entries are now wrong and should be deleted |
--- if N == S, then s2g[N] == G, so delete g2s[s2g[N]] and s2g[N] |
if s2g[globalID] then |
g2s[s2g[globalID]] = nil -- |
s2g[globalID] = nil |
--- if N == G, then g2s[N] == S, so delete s2g[g2s[N]] and g2s[N] |
elseif g2s[globalID] then |
s2g[g2s[globalID]] = nil |
g2s[globalID] = nil |
end |
-- Are you confused yet? I know I am. |
end |
numKnown = numKnown + 1 |
end |
total = total + 1 |
end |
end |
self:DebugPrint("Updated Spellbook, "..total.." entries found, "..numKnown.." spells known.") |
self.numSpells = total |
end |
-- if new is true, a spell has been added to the spellbook |
-- if new is false, an existing spell has been newly learned |
function LA:AddSpell(id, new) |
--[[ if self.state.retalenting then -- DEBUG FIXME |
print("AddSpell called during retalent!") -- DEBUG FIXME |
end ]]-- DEBUG FIXME |
local action = "SHOW" |
if new then |
action = "LEARN" |
end |
if InCombatLockdown() then |
table.insert(self.queue, { action = action, id = id }) |
else |
if new then |
self:LearnSpell(id) |
end |
local spell = self.Spell.Global[id] |
if (not self.state.retalenting) and |
(not spell.Passive) --and |
then |
-- Display button with draggable spell icon |
self:AddButton(spell) |
end |
end |
end |
-- a spell has been removed from the spellbook |
function LA:RemoveSpell(id) |
if InCombatLockdown() then |
table.insert(self.queue, { action = "FORGET", id = id }) |
else |
self:ClearButtonID(id) |
self:ForgetSpell(id) |
end |
end |
function LA:DiffSpellBook() |
-- print("Diffing spellbook!") -- DEBUG FIXME |
if self.state.retalenting then |
-- print("DiffSpellBook called during retalent!") -- DEBUG FIXME |
return 0 |
end |
-- swap caches |
--self.oldSpellBookCache, self.spellBookCache = self.spellBookCache, self.oldSpellBookCache |
self.oldKnownSpells, self.knownSpells = self.knownSpells, self.oldKnownSpells |
self:UpdateSpellBook() |
--local old = self.oldSpellBookCache |
--local new = self.spellBookCache |
local old = self.oldKnownSpells |
local new = self.knownSpells |
local updated = 0 |
for newID, newItem in pairs(new) do -- look for things learned |
if newItem then |
if not old[newID] then -- spell added to spellbook |
updated = updated + 1 |
self:AddSpell(newID, true) |
--elseif not old[newID].known then -- spell changed from unkown to known |
-- self:AddSpell(newID) |
-- updated = updated + 1 |
end |
end |
end |
for oldID, oldItem in pairs(old) do -- look for things forgotten |
if oldItem then |
if not new[oldID] then |
self:RemoveSpell(oldID) |
updated = updated + 1 |
--elseif not new[oldID].known then |
-- self:DebugPrint("Spell "..oldItem.info.name.." with globalID "..oldID.." forgotten but not removed") |
-- updated = updated + 1 |
end |
end |
end |
if updated > 1 then |
self:DebugPrint("Multiple updates ("..updated..") in DiffSpellBook") |
end |
-- TODO: Detect flyout changes |
return updated |
end |
-- A new spellbook ID has been added, bumping existing spellbook IDs up by one |
function LA:LearnSpell(id) |
local frame = self.frame |
local buttons = self.buttons |
--[[ MOP using global ids, don't need to munge book ids anymore, yay! |
for i = 1, self:GetVisible() do |
local button = buttons[i] |
local buttonID = button:GetID() |
if button.kind == kind and buttonID >= bookID then |
button:SetID(buttonID + 1) |
self:UpdateButton(button) |
end |
end |
]] |
local spec = GetActiveSpecGroup() |
if self.saved.restoreActions and |
(not self.state.retalenting) and |
-- MOP -- kind == BOOKTYPE_SPELL and |
self.character.unlearned and |
self.character.unlearned[spec] then |
--local globalID = self:SpellBookInfo(bookID).info.globalID |
for slot, oldIDs in pairs(self.character.unlearned[spec]) do |
local actionType = GetActionInfo(slot) -- local actionType, actionID, actionSubType, globalID = GetActionInfo(slot) |
for oldID in pairs(oldIDs) do |
if oldID == id and actionType == nil then |
PickupSpell(id) |
PlaceAction(slot) |
self.character.unlearned[spec][slot][oldID] = nil |
end |
end |
end |
end |
end |
-- An old spellbook ID has been deleted, shifting spellbook IDs down by one |
function LA:ForgetSpell(bookID) |
--[[local frame = self.frame |
local buttons = self.buttons |
for i = 1, self:GetVisible() do |
local button = buttons[i] |
local buttonID = button:GetID() |
if button.kind == BOOKTYPE_SPELL and buttonID > bookID then |
button:SetID(buttonID - 1) |
self:UpdateButton(button) |
end |
end]] |
end |
GNU GENERAL PUBLIC LICENSE |
Version 3, 29 June 2007 |
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
Everyone is permitted to copy and distribute verbatim copies |
of this license document, but changing it is not allowed. |
Preamble |
The GNU General Public License is a free, copyleft license for |
software and other kinds of works. |
The licenses for most software and other practical works are designed |
to take away your freedom to share and change the works. By contrast, |
the GNU General Public License is intended to guarantee your freedom to |
share and change all versions of a program--to make sure it remains free |
software for all its users. We, the Free Software Foundation, use the |
GNU General Public License for most of our software; it applies also to |
any other work released this way by its authors. You can apply it to |
your programs, too. |
When we speak of free software, we are referring to freedom, not |
price. Our General Public Licenses are designed to make sure that you |
have the freedom to distribute copies of free software (and charge for |
them if you wish), that you receive source code or can get it if you |
want it, that you can change the software or use pieces of it in new |
free programs, and that you know you can do these things. |
To protect your rights, we need to prevent others from denying you |
these rights or asking you to surrender the rights. Therefore, you have |
certain responsibilities if you distribute copies of the software, or if |
you modify it: responsibilities to respect the freedom of others. |
For example, if you distribute copies of such a program, whether |
gratis or for a fee, you must pass on to the recipients the same |
freedoms that you received. You must make sure that they, too, receive |
or can get the source code. And you must show them these terms so they |
know their rights. |
Developers that use the GNU GPL protect your rights with two steps: |
(1) assert copyright on the software, and (2) offer you this License |
giving you legal permission to copy, distribute and/or modify it. |
For the developers' and authors' protection, the GPL clearly explains |
that there is no warranty for this free software. For both users' and |
authors' sake, the GPL requires that modified versions be marked as |
changed, so that their problems will not be attributed erroneously to |
authors of previous versions. |
Some devices are designed to deny users access to install or run |
modified versions of the software inside them, although the manufacturer |
can do so. This is fundamentally incompatible with the aim of |
protecting users' freedom to change the software. The systematic |
pattern of such abuse occurs in the area of products for individuals to |
use, which is precisely where it is most unacceptable. Therefore, we |
have designed this version of the GPL to prohibit the practice for those |
products. If such problems arise substantially in other domains, we |
stand ready to extend this provision to those domains in future versions |
of the GPL, as needed to protect the freedom of users. |
Finally, every program is threatened constantly by software patents. |
States should not allow patents to restrict development and use of |
software on general-purpose computers, but in those that do, we wish to |
avoid the special danger that patents applied to a free program could |
make it effectively proprietary. To prevent this, the GPL assures that |
patents cannot be used to render the program non-free. |
The precise terms and conditions for copying, distribution and |
modification follow. |
TERMS AND CONDITIONS |
0. Definitions. |
"This License" refers to version 3 of the GNU General Public License. |
"Copyright" also means copyright-like laws that apply to other kinds of |
works, such as semiconductor masks. |
"The Program" refers to any copyrightable work licensed under this |
License. Each licensee is addressed as "you". "Licensees" and |
"recipients" may be individuals or organizations. |
To "modify" a work means to copy from or adapt all or part of the work |
in a fashion requiring copyright permission, other than the making of an |
exact copy. The resulting work is called a "modified version" of the |
earlier work or a work "based on" the earlier work. |
A "covered work" means either the unmodified Program or a work based |
on the Program. |
To "propagate" a work means to do anything with it that, without |
permission, would make you directly or secondarily liable for |
infringement under applicable copyright law, except executing it on a |
computer or modifying a private copy. Propagation includes copying, |
distribution (with or without modification), making available to the |
public, and in some countries other activities as well. |
To "convey" a work means any kind of propagation that enables other |
parties to make or receive copies. Mere interaction with a user through |
a computer network, with no transfer of a copy, is not conveying. |
An interactive user interface displays "Appropriate Legal Notices" |
to the extent that it includes a convenient and prominently visible |
feature that (1) displays an appropriate copyright notice, and (2) |
tells the user that there is no warranty for the work (except to the |
extent that warranties are provided), that licensees may convey the |
work under this License, and how to view a copy of this License. If |
the interface presents a list of user commands or options, such as a |
menu, a prominent item in the list meets this criterion. |
1. Source Code. |
The "source code" for a work means the preferred form of the work |
for making modifications to it. "Object code" means any non-source |
form of a work. |
A "Standard Interface" means an interface that either is an official |
standard defined by a recognized standards body, or, in the case of |
interfaces specified for a particular programming language, one that |
is widely used among developers working in that language. |
The "System Libraries" of an executable work include anything, other |
than the work as a whole, that (a) is included in the normal form of |
packaging a Major Component, but which is not part of that Major |
Component, and (b) serves only to enable use of the work with that |
Major Component, or to implement a Standard Interface for which an |
implementation is available to the public in source code form. A |
"Major Component", in this context, means a major essential component |
(kernel, window system, and so on) of the specific operating system |
(if any) on which the executable work runs, or a compiler used to |
produce the work, or an object code interpreter used to run it. |
The "Corresponding Source" for a work in object code form means all |
the source code needed to generate, install, and (for an executable |
work) run the object code and to modify the work, including scripts to |
control those activities. However, it does not include the work's |
System Libraries, or general-purpose tools or generally available free |
programs which are used unmodified in performing those activities but |
which are not part of the work. For example, Corresponding Source |
includes interface definition files associated with source files for |
the work, and the source code for shared libraries and dynamically |
linked subprograms that the work is specifically designed to require, |
such as by intimate data communication or control flow between those |
subprograms and other parts of the work. |
The Corresponding Source need not include anything that users |
can regenerate automatically from other parts of the Corresponding |
Source. |
The Corresponding Source for a work in source code form is that |
same work. |
2. Basic Permissions. |
All rights granted under this License are granted for the term of |
copyright on the Program, and are irrevocable provided the stated |
conditions are met. This License explicitly affirms your unlimited |
permission to run the unmodified Program. The output from running a |
covered work is covered by this License only if the output, given its |
content, constitutes a covered work. This License acknowledges your |
rights of fair use or other equivalent, as provided by copyright law. |
You may make, run and propagate covered works that you do not |
convey, without conditions so long as your license otherwise remains |
in force. You may convey covered works to others for the sole purpose |
of having them make modifications exclusively for you, or provide you |
with facilities for running those works, provided that you comply with |
the terms of this License in conveying all material for which you do |
not control copyright. Those thus making or running the covered works |
for you must do so exclusively on your behalf, under your direction |
and control, on terms that prohibit them from making any copies of |
your copyrighted material outside their relationship with you. |
Conveying under any other circumstances is permitted solely under |
the conditions stated below. Sublicensing is not allowed; section 10 |
makes it unnecessary. |
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
No covered work shall be deemed part of an effective technological |
measure under any applicable law fulfilling obligations under article |
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
similar laws prohibiting or restricting circumvention of such |
measures. |
When you convey a covered work, you waive any legal power to forbid |
circumvention of technological measures to the extent such circumvention |
is effected by exercising rights under this License with respect to |
the covered work, and you disclaim any intention to limit operation or |
modification of the work as a means of enforcing, against the work's |
users, your or third parties' legal rights to forbid circumvention of |
technological measures. |
4. Conveying Verbatim Copies. |
You may convey verbatim copies of the Program's source code as you |
receive it, in any medium, provided that you conspicuously and |
appropriately publish on each copy an appropriate copyright notice; |
keep intact all notices stating that this License and any |
non-permissive terms added in accord with section 7 apply to the code; |
keep intact all notices of the absence of any warranty; and give all |
recipients a copy of this License along with the Program. |
You may charge any price or no price for each copy that you convey, |
and you may offer support or warranty protection for a fee. |
5. Conveying Modified Source Versions. |
You may convey a work based on the Program, or the modifications to |
produce it from the Program, in the form of source code under the |
terms of section 4, provided that you also meet all of these conditions: |
a) The work must carry prominent notices stating that you modified |
it, and giving a relevant date. |
b) The work must carry prominent notices stating that it is |
released under this License and any conditions added under section |
7. This requirement modifies the requirement in section 4 to |
"keep intact all notices". |
c) You must license the entire work, as a whole, under this |
License to anyone who comes into possession of a copy. This |
License will therefore apply, along with any applicable section 7 |
additional terms, to the whole of the work, and all its parts, |
regardless of how they are packaged. This License gives no |
permission to license the work in any other way, but it does not |
invalidate such permission if you have separately received it. |
d) If the work has interactive user interfaces, each must display |
Appropriate Legal Notices; however, if the Program has interactive |
interfaces that do not display Appropriate Legal Notices, your |
work need not make them do so. |
A compilation of a covered work with other separate and independent |
works, which are not by their nature extensions of the covered work, |
and which are not combined with it such as to form a larger program, |
in or on a volume of a storage or distribution medium, is called an |
"aggregate" if the compilation and its resulting copyright are not |
used to limit the access or legal rights of the compilation's users |
beyond what the individual works permit. Inclusion of a covered work |
in an aggregate does not cause this License to apply to the other |
parts of the aggregate. |
6. Conveying Non-Source Forms. |
You may convey a covered work in object code form under the terms |
of sections 4 and 5, provided that you also convey the |
machine-readable Corresponding Source under the terms of this License, |
in one of these ways: |
a) Convey the object code in, or embodied in, a physical product |
(including a physical distribution medium), accompanied by the |
Corresponding Source fixed on a durable physical medium |
customarily used for software interchange. |
b) Convey the object code in, or embodied in, a physical product |
(including a physical distribution medium), accompanied by a |
written offer, valid for at least three years and valid for as |
long as you offer spare parts or customer support for that product |
model, to give anyone who possesses the object code either (1) a |
copy of the Corresponding Source for all the software in the |
product that is covered by this License, on a durable physical |
medium customarily used for software interchange, for a price no |
more than your reasonable cost of physically performing this |
conveying of source, or (2) access to copy the |
Corresponding Source from a network server at no charge. |
c) Convey individual copies of the object code with a copy of the |
written offer to provide the Corresponding Source. This |
alternative is allowed only occasionally and noncommercially, and |
only if you received the object code with such an offer, in accord |
with subsection 6b. |
d) Convey the object code by offering access from a designated |
place (gratis or for a charge), and offer equivalent access to the |
Corresponding Source in the same way through the same place at no |
further charge. You need not require recipients to copy the |
Corresponding Source along with the object code. If the place to |
copy the object code is a network server, the Corresponding Source |
may be on a different server (operated by you or a third party) |
that supports equivalent copying facilities, provided you maintain |
clear directions next to the object code saying where to find the |
Corresponding Source. Regardless of what server hosts the |
Corresponding Source, you remain obligated to ensure that it is |
available for as long as needed to satisfy these requirements. |
e) Convey the object code using peer-to-peer transmission, provided |
you inform other peers where the object code and Corresponding |
Source of the work are being offered to the general public at no |
charge under subsection 6d. |
A separable portion of the object code, whose source code is excluded |
from the Corresponding Source as a System Library, need not be |
included in conveying the object code work. |
A "User Product" is either (1) a "consumer product", which means any |
tangible personal property which is normally used for personal, family, |
or household purposes, or (2) anything designed or sold for incorporation |
into a dwelling. In determining whether a product is a consumer product, |
doubtful cases shall be resolved in favor of coverage. For a particular |
product received by a particular user, "normally used" refers to a |
typical or common use of that class of product, regardless of the status |
of the particular user or of the way in which the particular user |
actually uses, or expects or is expected to use, the product. A product |
is a consumer product regardless of whether the product has substantial |
commercial, industrial or non-consumer uses, unless such uses represent |
the only significant mode of use of the product. |
"Installation Information" for a User Product means any methods, |
procedures, authorization keys, or other information required to install |
and execute modified versions of a covered work in that User Product from |
a modified version of its Corresponding Source. The information must |
suffice to ensure that the continued functioning of the modified object |
code is in no case prevented or interfered with solely because |
modification has been made. |
If you convey an object code work under this section in, or with, or |
specifically for use in, a User Product, and the conveying occurs as |
part of a transaction in which the right of possession and use of the |
User Product is transferred to the recipient in perpetuity or for a |
fixed term (regardless of how the transaction is characterized), the |
Corresponding Source conveyed under this section must be accompanied |
by the Installation Information. But this requirement does not apply |
if neither you nor any third party retains the ability to install |
modified object code on the User Product (for example, the work has |
been installed in ROM). |
The requirement to provide Installation Information does not include a |
requirement to continue to provide support service, warranty, or updates |
for a work that has been modified or installed by the recipient, or for |
the User Product in which it has been modified or installed. Access to a |
network may be denied when the modification itself materially and |
adversely affects the operation of the network or violates the rules and |
protocols for communication across the network. |
Corresponding Source conveyed, and Installation Information provided, |
in accord with this section must be in a format that is publicly |
documented (and with an implementation available to the public in |
source code form), and must require no special password or key for |
unpacking, reading or copying. |
7. Additional Terms. |
"Additional permissions" are terms that supplement the terms of this |
License by making exceptions from one or more of its conditions. |
Additional permissions that are applicable to the entire Program shall |
be treated as though they were included in this License, to the extent |
that they are valid under applicable law. If additional permissions |
apply only to part of the Program, that part may be used separately |
under those permissions, but the entire Program remains governed by |
this License without regard to the additional permissions. |
When you convey a copy of a covered work, you may at your option |
remove any additional permissions from that copy, or from any part of |
it. (Additional permissions may be written to require their own |
removal in certain cases when you modify the work.) You may place |
additional permissions on material, added by you to a covered work, |
for which you have or can give appropriate copyright permission. |
Notwithstanding any other provision of this License, for material you |
add to a covered work, you may (if authorized by the copyright holders of |
that material) supplement the terms of this License with terms: |
a) Disclaiming warranty or limiting liability differently from the |
terms of sections 15 and 16 of this License; or |
b) Requiring preservation of specified reasonable legal notices or |
author attributions in that material or in the Appropriate Legal |
Notices displayed by works containing it; or |
c) Prohibiting misrepresentation of the origin of that material, or |
requiring that modified versions of such material be marked in |
reasonable ways as different from the original version; or |
d) Limiting the use for publicity purposes of names of licensors or |
authors of the material; or |
e) Declining to grant rights under trademark law for use of some |
trade names, trademarks, or service marks; or |
f) Requiring indemnification of licensors and authors of that |
material by anyone who conveys the material (or modified versions of |
it) with contractual assumptions of liability to the recipient, for |
any liability that these contractual assumptions directly impose on |
those licensors and authors. |
All other non-permissive additional terms are considered "further |
restrictions" within the meaning of section 10. If the Program as you |
received it, or any part of it, contains a notice stating that it is |
governed by this License along with a term that is a further |
restriction, you may remove that term. If a license document contains |
a further restriction but permits relicensing or conveying under this |
License, you may add to a covered work material governed by the terms |
of that license document, provided that the further restriction does |
not survive such relicensing or conveying. |
If you add terms to a covered work in accord with this section, you |
must place, in the relevant source files, a statement of the |
additional terms that apply to those files, or a notice indicating |
where to find the applicable terms. |
Additional terms, permissive or non-permissive, may be stated in the |
form of a separately written license, or stated as exceptions; |
the above requirements apply either way. |
8. Termination. |
You may not propagate or modify a covered work except as expressly |
provided under this License. Any attempt otherwise to propagate or |
modify it is void, and will automatically terminate your rights under |
this License (including any patent licenses granted under the third |
paragraph of section 11). |
However, if you cease all violation of this License, then your |
license from a particular copyright holder is reinstated (a) |
provisionally, unless and until the copyright holder explicitly and |
finally terminates your license, and (b) permanently, if the copyright |
holder fails to notify you of the violation by some reasonable means |
prior to 60 days after the cessation. |
Moreover, your license from a particular copyright holder is |
reinstated permanently if the copyright holder notifies you of the |
violation by some reasonable means, this is the first time you have |
received notice of violation of this License (for any work) from that |
copyright holder, and you cure the violation prior to 30 days after |
your receipt of the notice. |
Termination of your rights under this section does not terminate the |
licenses of parties who have received copies or rights from you under |
this License. If your rights have been terminated and not permanently |
reinstated, you do not qualify to receive new licenses for the same |
material under section 10. |
9. Acceptance Not Required for Having Copies. |
You are not required to accept this License in order to receive or |
run a copy of the Program. Ancillary propagation of a covered work |
occurring solely as a consequence of using peer-to-peer transmission |
to receive a copy likewise does not require acceptance. However, |
nothing other than this License grants you permission to propagate or |
modify any covered work. These actions infringe copyright if you do |
not accept this License. Therefore, by modifying or propagating a |
covered work, you indicate your acceptance of this License to do so. |
10. Automatic Licensing of Downstream Recipients. |
Each time you convey a covered work, the recipient automatically |
receives a license from the original licensors, to run, modify and |
propagate that work, subject to this License. You are not responsible |
for enforcing compliance by third parties with this License. |
An "entity transaction" is a transaction transferring control of an |
organization, or substantially all assets of one, or subdividing an |
organization, or merging organizations. If propagation of a covered |
work results from an entity transaction, each party to that |
transaction who receives a copy of the work also receives whatever |
licenses to the work the party's predecessor in interest had or could |
give under the previous paragraph, plus a right to possession of the |
Corresponding Source of the work from the predecessor in interest, if |
the predecessor has it or can get it with reasonable efforts. |
You may not impose any further restrictions on the exercise of the |
rights granted or affirmed under this License. For example, you may |
not impose a license fee, royalty, or other charge for exercise of |
rights granted under this License, and you may not initiate litigation |
(including a cross-claim or counterclaim in a lawsuit) alleging that |
any patent claim is infringed by making, using, selling, offering for |
sale, or importing the Program or any portion of it. |
11. Patents. |
A "contributor" is a copyright holder who authorizes use under this |
License of the Program or a work on which the Program is based. The |
work thus licensed is called the contributor's "contributor version". |
A contributor's "essential patent claims" are all patent claims |
owned or controlled by the contributor, whether already acquired or |
hereafter acquired, that would be infringed by some manner, permitted |
by this License, of making, using, or selling its contributor version, |
but do not include claims that would be infringed only as a |
consequence of further modification of the contributor version. For |
purposes of this definition, "control" includes the right to grant |
patent sublicenses in a manner consistent with the requirements of |
this License. |
Each contributor grants you a non-exclusive, worldwide, royalty-free |
patent license under the contributor's essential patent claims, to |
make, use, sell, offer for sale, import and otherwise run, modify and |
propagate the contents of its contributor version. |
In the following three paragraphs, a "patent license" is any express |
agreement or commitment, however denominated, not to enforce a patent |
(such as an express permission to practice a patent or covenant not to |
sue for patent infringement). To "grant" such a patent license to a |
party means to make such an agreement or commitment not to enforce a |
patent against the party. |
If you convey a covered work, knowingly relying on a patent license, |
and the Corresponding Source of the work is not available for anyone |
to copy, free of charge and under the terms of this License, through a |
publicly available network server or other readily accessible means, |
then you must either (1) cause the Corresponding Source to be so |
available, or (2) arrange to deprive yourself of the benefit of the |
patent license for this particular work, or (3) arrange, in a manner |
consistent with the requirements of this License, to extend the patent |
license to downstream recipients. "Knowingly relying" means you have |
actual knowledge that, but for the patent license, your conveying the |
covered work in a country, or your recipient's use of the covered work |
in a country, would infringe one or more identifiable patents in that |
country that you have reason to believe are valid. |
If, pursuant to or in connection with a single transaction or |
arrangement, you convey, or propagate by procuring conveyance of, a |
covered work, and grant a patent license to some of the parties |
receiving the covered work authorizing them to use, propagate, modify |
or convey a specific copy of the covered work, then the patent license |
you grant is automatically extended to all recipients of the covered |
work and works based on it. |
A patent license is "discriminatory" if it does not include within |
the scope of its coverage, prohibits the exercise of, or is |
conditioned on the non-exercise of one or more of the rights that are |
specifically granted under this License. You may not convey a covered |
work if you are a party to an arrangement with a third party that is |
in the business of distributing software, under which you make payment |
to the third party based on the extent of your activity of conveying |
the work, and under which the third party grants, to any of the |
parties who would receive the covered work from you, a discriminatory |
patent license (a) in connection with copies of the covered work |
conveyed by you (or copies made from those copies), or (b) primarily |
for and in connection with specific products or compilations that |
contain the covered work, unless you entered into that arrangement, |
or that patent license was granted, prior to 28 March 2007. |
Nothing in this License shall be construed as excluding or limiting |
any implied license or other defenses to infringement that may |
otherwise be available to you under applicable patent law. |
12. No Surrender of Others' Freedom. |
If conditions are imposed on you (whether by court order, agreement or |
otherwise) that contradict the conditions of this License, they do not |
excuse you from the conditions of this License. If you cannot convey a |
covered work so as to satisfy simultaneously your obligations under this |
License and any other pertinent obligations, then as a consequence you may |
not convey it at all. For example, if you agree to terms that obligate you |
to collect a royalty for further conveying from those to whom you convey |
the Program, the only way you could satisfy both those terms and this |
License would be to refrain entirely from conveying the Program. |
13. Use with the GNU Affero General Public License. |
Notwithstanding any other provision of this License, you have |
permission to link or combine any covered work with a work licensed |
under version 3 of the GNU Affero General Public License into a single |
combined work, and to convey the resulting work. The terms of this |
License will continue to apply to the part which is the covered work, |
but the special requirements of the GNU Affero General Public License, |
section 13, concerning interaction through a network will apply to the |
combination as such. |
14. Revised Versions of this License. |
The Free Software Foundation may publish revised and/or new versions of |
the GNU General Public License from time to time. Such new versions will |
be similar in spirit to the present version, but may differ in detail to |
address new problems or concerns. |
Each version is given a distinguishing version number. If the |
Program specifies that a certain numbered version of the GNU General |
Public License "or any later version" applies to it, you have the |
option of following the terms and conditions either of that numbered |
version or of any later version published by the Free Software |
Foundation. If the Program does not specify a version number of the |
GNU General Public License, you may choose any version ever published |
by the Free Software Foundation. |
If the Program specifies that a proxy can decide which future |
versions of the GNU General Public License can be used, that proxy's |
public statement of acceptance of a version permanently authorizes you |
to choose that version for the Program. |
Later license versions may give you additional or different |
permissions. However, no additional obligations are imposed on any |
author or copyright holder as a result of your choosing to follow a |
later version. |
15. Disclaimer of Warranty. |
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
16. Limitation of Liability. |
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGES. |
17. Interpretation of Sections 15 and 16. |
If the disclaimer of warranty and limitation of liability provided |
above cannot be given local legal effect according to their terms, |
reviewing courts shall apply local law that most closely approximates |
an absolute waiver of all civil liability in connection with the |
Program, unless a warranty or assumption of liability accompanies a |
copy of the Program in return for a fee. |
END OF TERMS AND CONDITIONS |
How to Apply These Terms to Your New Programs |
If you develop a new program, and you want it to be of the greatest |
possible use to the public, the best way to achieve this is to make it |
free software which everyone can redistribute and change under these terms. |
To do so, attach the following notices to the program. It is safest |
to attach them to the start of each source file to most effectively |
state the exclusion of warranty; and each file should have at least |
the "copyright" line and a pointer to where the full notice is found. |
<one line to give the program's name and a brief idea of what it does.> |
Copyright (C) <year> <name of author> |
This program is free software: you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation, either version 3 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program. If not, see <http://www.gnu.org/licenses/>. |
Also add information on how to contact you by electronic and paper mail. |
If the program does terminal interaction, make it output a short |
notice like this when it starts in an interactive mode: |
<program> Copyright (C) <year> <name of author> |
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
This is free software, and you are welcome to redistribute it |
under certain conditions; type `show c' for details. |
The hypothetical commands `show w' and `show c' should show the appropriate |
parts of the General Public License. Of course, your program's commands |
might be different; for a GUI interface, you would use an "about box". |
You should also get your employer (if you work as a programmer) or school, |
if any, to sign a "copyright disclaimer" for the program, if necessary. |
For more information on this, and how to apply and follow the GNU GPL, see |
<http://www.gnu.org/licenses/>. |
The GNU General Public License does not permit incorporating your program |
into proprietary programs. If your program is a subroutine library, you |
may consider it more useful to permit linking proprietary applications with |
the library. If this is what you want to do, use the GNU Lesser General |
Public License instead of this License. But first, please read |
<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
## Interface: 60200 |
## Title: Learning Aid v1.12 Beta 3 |
## Notes: Displays a bar of spells, abilities, or tradeskills you've just learned. By Jamash (Kil'jaeden-US) |
## Notes-deDE: Zeigt eine Leiste mit Zaubersprüchen, Fähigkeiten, oder Berufsfertigkeiten, die du gerade erst gelernt hast. Von Jamash (Kil'jaeden-US) |
## Notes-koKR: íì¬ ìµëí 주문, ë¥ë ¥, ì 문기ì , íê² í¹ì ì ìë물ì ë°ë¥¼ íìí©ëë¤. ë§ë ì´: Jamash (Kil'jaeden-US) |
## SavedVariables: LearningAid_Saved |
## SavedVariablesPerCharacter: LearningAid_Character |
## Author: Jamash (Kil'jaeden-US) |
## Version: 1.12b3 |
## X-Website: http://wow.curseforge.com/addons/learningaid/ |
## X-Embeds: Ace3 |
## X-Category: Interface Enhancements |
embeds.xml |
LearningAid.xml |
LearningAid.lua |
Debug.lua |
Events.lua |
SpellButton.lua |
SpellAPI.lua |
Spell.lua |
ActionBar.lua |
Trainer.lua |
Locale\deDE.lua |
Locale\enUS.lua |
Locale\koKR.lua |
Locale\zhCN.lua |
Locale\zhTW.lua |
# 1.12b1 |
Update TOC for Warlords of Draenor pre-patch 6.0.2 |
# 1.12a5 |
Talents that override existing spells, like Strangulate/Asphyxiate now |
work correctly. |
Retalenting spam filter is working, and now does not display redundant |
entries for spells that are unlearned and then immediately relearned |
when Summarize is selected. |
Ignore list is still broken. Do not use. |
SpellAPI is more comprehensive and robust. |
Deleted a lot of old, commented-out code from previous expansions. |
# 1.12a4 |
Mangle is working. |
Flyouts are working. |
/la search is working. |
Ignore list is broken. Do not use. |
# 1.12a3 |
Actually fixed the issue with spec-specific spells appearing in /la search |
results whether or not they were on an action bar, for reals this time. |
Added new object-oriented SpellAPI.lua, which may become an independent |
library at some point. |
Pulled out a lot of obsolete code that has succumbed to bitrot. |
There's still a lot left. |
Known Issue: If you change specs, all the spec-specific spells for the |
spec you didn't start the current game session in will appear. Probably |
not that difficult to fix, but it's late and I want to get something at |
least semi-functional out tonight. |
# 1.12a2 |
Fixed the issue with spec-specific spells breaking /la search |
[Update: Did not actually fix the issue with spec-specific spells] |
# 1.12a1 |
Updated for WoW 5.0.4 (Pre-Mists of Pandaria patch) |
Removed support for mini-pets and mounts due to persistent bugs. |
If you want to see support for mini-pets and mounts return, please |
contact the author at jamash.kj@gmail.com. |
# 1.11 |
Updated for WoW 4.0.1 (Pre-Cataclysm patch) |
Includes new "Train All" button on skill and class trainers. |
Interface updated to 40000 |
# 1.10.1 |
Chinese localizations added (zhCN and zhTW), kindly provided by wowuicn. |
# 1.10 |
The Learning Aid window now grows wider when there are too many |
abilities on it to fit in one column. |
Korean localization added, kindly provided by talkswind. |
# 1.09 |
The "Find Shapeshift forms" toggle was backwards. Fixed. |
New Ignore Ability function. Ctrl-click on an icon in the Learning Aid |
window to ignore that ability when using the Search feature. |
You can also use /la ignore Ability Name and /la unignore Ability Name |
/la unignoreall to reset the list of ignored abilities |
When the option "Show Learn/Unlearn Messages" is set to "Summarize", |
spells that are unlearned and immediately relearned while swapping |
talent specs are not printed to the chat log. |
# 1.08 |
Added an option to toggle whether to search for Shaman totems when |
searching for abilities missing from action bars. |
Fixed wrong companion bug (again). |
# 1.07.3 |
Fixed a bug that caused incorrect text to appear on the context menu. |
# 1.07.2 |
Added German translation kindly provided by Freydis88. |
# 1.07.1 |
Fixed long-standing bug that caused the wrong companion to appear when |
learning a new companion under laggy conditions. |
# 1.07 Completed Features |
When the player unlearns a spell or ability due to a talent reset, |
remember where on the player's action bars that spell or ability was. |
When the spell or ability is relearned, put it back on the player's |
action bar in the same place, as long as that slot is empty. |
Same as above, but due to a server-side talent reset. |
Remember multiple sets of unlearned ability to action bar button |
mappings. |
Filter "You have learned" and "You have unlearned" spam down to two |
lines, saying "You have unlearned A, B, C." and "You have learned X, Y, |
Z." |
Dual Spec Swapping: Complete |
Unlearning Talents: Complete |
Batch-learning Talents with the Talent Preview system: Complete |
Batch-learning Talents with an addon: Complete |
Singly-learning Talents with multiple ranks: Complete |
Pet Talents: Complete |
# Future |
Rewrite event handlers using the new 3.0 Secure State system. |
Fix macro scanner to work with spells that have parentheses in their |
names, such as "Swipe (Bear)". |
# Possible |
Rewrite entire spell learn / unlearn system to use the system chat |
message event instead of caching and diffing. |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
Debug.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
function LA:TestAdd(kind, ...) |
print("Testing!") |
local t = {...} |
for i = 1, #t do |
local id = t[i] |
if kind == BOOKTYPE_SPELL then |
if not IsPassiveSpell(id, kind) then |
print(addonName..": Test: Adding button with spell id "..id) |
if InCombatLockdown() then |
table.insert(self.queue, { action = "SHOW", id = id, kind = kind }) |
else |
self:AddButton(kind, id) |
end |
else |
print(addonName.." Test: Spell id "..id.." is passive or does not exist") |
end |
elseif kind == "CRITTER" or kind == "MOUNT" then |
if GetCompanionInfo(kind, id) then |
print(addonName..": Test: Adding companion type "..kind.." id "..id) |
if InCombatLockdown() then |
table.insert(self.queue, { action = "SHOW", id = id, kind = kind}) |
else |
self:AddButton(kind, id) |
end |
else |
print(addonName.." Test: Companion type "..kind..", id "..id.." does not exist") |
end |
else |
print(addonName.." Test: Action type "..kind.." is not valid. Valid types are spell, CRITTER or MOUNT.") |
end |
end |
end |
function LA:TestRemove(kind, ...) |
print(addonName.." Testing!") |
local t = {...} |
for i = 1, #t do |
local id = t[i] |
print(addonName.." Test: Removing "..kind.." id "..id) |
if InCombatLockdown() then |
table.insert(self.queue, { action = "CLEAR", id = id, kind = kind }) |
else |
self:ClearButtonID(kind, id) |
end |
end |
end |
function LA:ListJoin(...) |
local str = "" |
local argc = select("#", ...) |
--if argc == 1 and type(...) == "table" then |
-- return self:ListJoin(unpack(...)) |
--else |
if argc >= 1 then |
str = str..tostring(...) |
for i = 2, argc do |
str = str..", "..tostring(select(i, ...)) |
end |
end |
return str |
end |
-- could be called as private:DebugPrint() or LearningAid:DebugPrint() so don't rely on self |
function private:DebugPrint(...) |
local p = private |
p.debugCount = p.debugCount + 1 |
local output = LA:ListJoin(...) |
LearningAid_DebugLog[p.debugCount] = output |
if p.debugWindow then |
p.debugWindow:AddMessage(p.debugCount..': '..output) |
end |
if p.debugCount > p.debugLimit then |
LearningAid_DebugLog[p.debugCount - p.debugLimit] = nil |
end |
-- When there are <p.debugLimit> nils in the list, shift everything back down to start at 1 |
-- The next call to DebugPrint will set DebugLog[debugLimit + 1] and nil out DebugLog[1] |
if (2*p.debugLimit) == p.debugCount then |
for i = 1, p.debugLimit do |
LearningAid_DebugLog[i] = LearningAid_DebugLog[i + p.debugLimit] |
LearningAid_DebugLog[i + p.debugLimit] = nil |
end |
p.debugCount = p.debugLimit |
end |
-- allow inline use |
return ... |
end |
-- don't call the stub DebugPrint, call the real DebugPrint |
private.wrappers.DebugPrint = private.DebugPrint |
private.meta = { |
__index = function(t, key) |
local value = private.shadow[key] |
if type(value) == "function" then |
if private.debugFlags.CALL and not private.noLog[key] then |
return private:Wrap(key, value) |
else |
return value |
end |
elseif private.debugFlags.GET then |
private:DebugPrint("__index["..tostring(key).."] = "..tostring(value)) |
end |
return value |
end, |
__newindex = function(t, key, value) |
if private.debugFlags.SET then |
private:DebugPrint("__newindex["..tostring(key).."] = "..tostring(value)) |
end |
private.shadow[key] = value |
end |
} |
-- when debugging is enabled, calls to LA:DebugPrint will be diverted to private:DebugPrint |
function LA:DebugPrint(...) |
return ... |
end |
--setmetatable(private.empty, private.meta) |
local junk = { } |
local function tset(t, ...) |
wipe(t) |
for i = 1, select("#", ...) do |
t[i] = (select(i, ...)) |
end |
return t |
end |
-- call after original LA is in private.LA and LA is empty |
function private:Wrap(name, f) |
self.tokenCount[name] = self.tokenCount[name] or 0 |
self.wrappers[name] = self.wrappers[name] or function(...) |
self.tokenCount[name] = self.tokenCount[name] + 1 |
local count = self.tokenCount[name] |
self:DebugPrint(name.."["..count.."]("..LA:ListJoin(select(2,...))..")") |
tset(junk, f(...)) |
self:DebugPrint(name.."["..count.."]() return "..LA:ListJoin(unpack(junk))) |
return unpack(junk) |
end |
return self.wrappers[name] |
end |
function LA:Debug(flag, newValue) |
-- Flags are "CALL", "GET", "SET" |
-- TOOD: probably ought to redesign this |
-- private.debug is the sum of the number of flags that are true |
local oldDebug = private.debug |
local newDebug = oldDebug |
local debugFlags = self.saved.debugFlags |
local oldValue = debugFlags[flag] |
if flag == nil then -- initialize |
newDebug = 0 |
private.debugFlags = debugFlags |
for savedFlag, savedValue in pairs(debugFlags) do |
if savedValue then |
newDebug = newDebug + 1 |
end |
end |
elseif newValue == nil then -- getter |
return oldValue |
elseif newValue ~= oldValue then -- setter |
debugFlags[flag] = newValue |
newDebug = newDebug + (newValue and 1 or -1) -- increment if true, decrement if false |
end |
local shadow = private.shadow |
if oldDebug == 0 and newDebug > 0 then -- we're turning debugging on |
for k, v in pairs(LA) do |
shadow[k] = LA[k] |
LA[k] = nil |
end |
setmetatable(LA, private.meta) |
LearningAid_DebugLog = { } |
if LearningAid_Saved then |
LearningAid_Saved.debugLog = LearningAid_DebugLog |
end |
elseif oldDebug > 0 and newDebug == 0 then -- we're turning debugging off |
setmetatable(LA, nil) |
for k, v in pairs(shadow) do |
LA[k] = shadow[k] |
shadow[k] = nil |
end |
if LearningAid_Saved then |
LearningAid_Saved.debugLog = nil |
end |
end |
private.debug = newDebug |
end |
Learning Aid version 1.12 Beta 3 |
Compatible with World of Warcraft version 6.2.0 |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamash.kj@gmail.com |
=== BEGIN LEGAL BOILERPLATE === |
This file is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
=== END LEGAL BOILERPLATE === |
Learning Aid helps you put new spells, abilities, and tradeskills on |
your action bars or in your macros when you learn them, without having |
to waste time paging through your Spellbook. When you learn something |
new, Learning Aid pops up a window with an icon for the newly learned |
action. You may then drag the icon to your action bar, or use it to |
paste a link into chat or text into a macro. You can also use the new |
action directly by clicking on the icon. When you're done, you can |
easily dismiss the window. |
User Interface Reference |
Learning Aid Window |
Left-click and drag the titlebar to move the window. |
Click the close box or middle-click on the titlebar to close the window. |
Click on the lock icon to lock the window (to prevent it from moving) or unlock it. |
Action Buttons |
* Left- or right-click to perform the action. |
* Middle-click to dismiss a button. Dismissing the only button closes the window. |
* Shift-click on a button to create a chat link or paste the ability name into the macro window. |
* Ctrl-click on a button to ignore the spell on it when searching for missing actions. |
Slash Command Reference |
Type |
/learningaid command [arguments] |
or |
/la command [arguments] |
Slash Commands |
/la |
Print help text to the default chat window. |
/la config |
Open the Learning Aid configuration window. |
/la restoreactions [on|off] |
Toggle whether to restore talent-based actions to action bars when they are |
relearned. |
/la filter [0|1|2] |
Set whether to filter "You have learned" and "You have unlearned" chat messages. |
0 - Show All: Do not filter learning and unlearning messages. |
1 - Summarize: Reduce multiple lines of messages to a one or two line summary. |
2 - Show None: Filter out all learning and unlearning messages. |
Default: 1. |
/la search |
Scan through your action bars to find any spells you have learned |
but not placed on an action bar. |
/la close |
Close the window. |
/la reset |
Reset the window's position to default. |
/la lock on |
Lock the window's position so it cannot be dragged. |
/la lock off |
/la unlock |
Unlock the window's position so it can be dragged. |
/la lock |
Toggle whether the window is locked. |
/la tracking [on|off] |
Set whether /la search searches for tracking abilities. |
/la shapeshift [on|off] |
Set whether /la search will find shapeshift forms, stances, auras, |
presences, etc. |
/la macros [on|off] |
Set whether /la search will look inside macros for abilities in use. |
/la totem [on|off] |
Set whether /la search will find Shaman totems. |
/la ignore Name of Ability |
Ignore this ability when using /la search. |
/la ignore |
List all ignored abilities. |
/la unignore Name of Ability |
No longer ignore this ability when using /la search. |
/la unignoreall |
Clear all abilities from the ignore list. |
Advanced Slash Commands |
/la advanced framestrata [TOOLTIP|BACKGROUND|MEDIUM|HIGH|DIALOG|FULLSCREEN_DIALOG|LOW|FULLSCREEN] |
Set the frame strata of the Learning Aid window. Only necessary if you |
are having problems with the Learning Aid window interacting poorly with |
other windows. |
<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"> |
<CheckButton name="LearningAidSpellButtonTemplate" inherits="SpellButtonTemplate, SecureActionButtonTemplate" virtual="true"> |
<Scripts> |
<OnLoad> |
--SpellButton_OnLoad(self); |
self:RegisterForDrag("LeftButton") |
self:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp") |
</OnLoad> |
<OnEvent> |
--SpellButton_OnEvent(self, event, ...); |
</OnEvent> |
<PreClick> |
self:SetChecked(false); |
</PreClick> |
<!-- <OnClick> |
- -if ( IsModifiedClick() ) then |
- - SpellButton_OnModifiedClick(self, button); |
- -else |
- - SpellButton_OnClick(self, button); |
- -end |
- -DEFAULT_CHAT_FRAME:AddMessage(button) |
if button == "RightButton" then |
LearningAid.ClearButtonIndex(LearningAid.Frame, self.Index) |
else |
SecureActionButton_OnClick(self, button) |
end |
</OnClick> --> |
<OnShow> |
--SpellButton_OnShow(self); |
</OnShow> |
<OnHide> |
--SpellButton_OnHide(self); |
LearningAid:SpellButton_OnHide(self) |
</OnHide> |
<OnDragStart> |
--SpellButton_OnDrag(self); |
LearningAid:SpellButton_OnDrag(self) |
</OnDragStart> |
<OnReceiveDrag> |
--SpellButton_OnDrag(self); |
LearningAid:SpellButton_OnDrag(self) |
</OnReceiveDrag> |
<OnEnter> |
--SpellButton_OnEnter(self); |
LearningAid:SpellButton_OnEnter(self) |
</OnEnter> |
<OnLeave> |
GameTooltip:Hide(); |
</OnLeave> |
</Scripts> |
</CheckButton> |
</Ui> |
GNU LESSER GENERAL PUBLIC LICENSE |
Version 3, 29 June 2007 |
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
Everyone is permitted to copy and distribute verbatim copies |
of this license document, but changing it is not allowed. |
This version of the GNU Lesser General Public License incorporates |
the terms and conditions of version 3 of the GNU General Public |
License, supplemented by the additional permissions listed below. |
0. Additional Definitions. |
As used herein, "this License" refers to version 3 of the GNU Lesser |
General Public License, and the "GNU GPL" refers to version 3 of the GNU |
General Public License. |
"The Library" refers to a covered work governed by this License, |
other than an Application or a Combined Work as defined below. |
An "Application" is any work that makes use of an interface provided |
by the Library, but which is not otherwise based on the Library. |
Defining a subclass of a class defined by the Library is deemed a mode |
of using an interface provided by the Library. |
A "Combined Work" is a work produced by combining or linking an |
Application with the Library. The particular version of the Library |
with which the Combined Work was made is also called the "Linked |
Version". |
The "Minimal Corresponding Source" for a Combined Work means the |
Corresponding Source for the Combined Work, excluding any source code |
for portions of the Combined Work that, considered in isolation, are |
based on the Application, and not on the Linked Version. |
The "Corresponding Application Code" for a Combined Work means the |
object code and/or source code for the Application, including any data |
and utility programs needed for reproducing the Combined Work from the |
Application, but excluding the System Libraries of the Combined Work. |
1. Exception to Section 3 of the GNU GPL. |
You may convey a covered work under sections 3 and 4 of this License |
without being bound by section 3 of the GNU GPL. |
2. Conveying Modified Versions. |
If you modify a copy of the Library, and, in your modifications, a |
facility refers to a function or data to be supplied by an Application |
that uses the facility (other than as an argument passed when the |
facility is invoked), then you may convey a copy of the modified |
version: |
a) under this License, provided that you make a good faith effort to |
ensure that, in the event an Application does not supply the |
function or data, the facility still operates, and performs |
whatever part of its purpose remains meaningful, or |
b) under the GNU GPL, with none of the additional permissions of |
this License applicable to that copy. |
3. Object Code Incorporating Material from Library Header Files. |
The object code form of an Application may incorporate material from |
a header file that is part of the Library. You may convey such object |
code under terms of your choice, provided that, if the incorporated |
material is not limited to numerical parameters, data structure |
layouts and accessors, or small macros, inline functions and templates |
(ten or fewer lines in length), you do both of the following: |
a) Give prominent notice with each copy of the object code that the |
Library is used in it and that the Library and its use are |
covered by this License. |
b) Accompany the object code with a copy of the GNU GPL and this license |
document. |
4. Combined Works. |
You may convey a Combined Work under terms of your choice that, |
taken together, effectively do not restrict modification of the |
portions of the Library contained in the Combined Work and reverse |
engineering for debugging such modifications, if you also do each of |
the following: |
a) Give prominent notice with each copy of the Combined Work that |
the Library is used in it and that the Library and its use are |
covered by this License. |
b) Accompany the Combined Work with a copy of the GNU GPL and this license |
document. |
c) For a Combined Work that displays copyright notices during |
execution, include the copyright notice for the Library among |
these notices, as well as a reference directing the user to the |
copies of the GNU GPL and this license document. |
d) Do one of the following: |
0) Convey the Minimal Corresponding Source under the terms of this |
License, and the Corresponding Application Code in a form |
suitable for, and under terms that permit, the user to |
recombine or relink the Application with a modified version of |
the Linked Version to produce a modified Combined Work, in the |
manner specified by section 6 of the GNU GPL for conveying |
Corresponding Source. |
1) Use a suitable shared library mechanism for linking with the |
Library. A suitable mechanism is one that (a) uses at run time |
a copy of the Library already present on the user's computer |
system, and (b) will operate properly with a modified version |
of the Library that is interface-compatible with the Linked |
Version. |
e) Provide Installation Information, but only if you would otherwise |
be required to provide such information under section 6 of the |
GNU GPL, and only to the extent that such information is |
necessary to install and execute a modified version of the |
Combined Work produced by recombining or relinking the |
Application with a modified version of the Linked Version. (If |
you use option 4d0, the Installation Information must accompany |
the Minimal Corresponding Source and Corresponding Application |
Code. If you use option 4d1, you must provide the Installation |
Information in the manner specified by section 6 of the GNU GPL |
for conveying Corresponding Source.) |
5. Combined Libraries. |
You may place library facilities that are a work based on the |
Library side by side in a single library together with other library |
facilities that are not Applications and are not covered by this |
License, and convey such a combined library under terms of your |
choice, if you do both of the following: |
a) Accompany the combined library with a copy of the same work based |
on the Library, uncombined with any other library facilities, |
conveyed under the terms of this License. |
b) Give prominent notice with the combined library that part of it |
is a work based on the Library, and explaining where to find the |
accompanying uncombined form of the same work. |
6. Revised Versions of the GNU Lesser General Public License. |
The Free Software Foundation may publish revised and/or new versions |
of the GNU Lesser General Public License from time to time. Such new |
versions will be similar in spirit to the present version, but may |
differ in detail to address new problems or concerns. |
Each version is given a distinguishing version number. If the |
Library as you received it specifies that a certain numbered version |
of the GNU Lesser General Public License "or any later version" |
applies to it, you have the option of following the terms and |
conditions either of that published version or of any later version |
published by the Free Software Foundation. If the Library as you |
received it does not specify a version number of the GNU Lesser |
General Public License, you may choose any version of the GNU Lesser |
General Public License ever published by the Free Software Foundation. |
If the Library as you received it specifies that a proxy can decide |
whether future versions of the GNU Lesser General Public License shall |
apply, that proxy's public statement of acceptance of any version is |
permanent authorization for you to choose that version for the |
Library. |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
ActionBar.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
local strLower = string.lower |
local castPrefixes = { |
--"USE", |
--"USERANDOM", |
"CAST", |
"CASTRANDOM", |
"CASTSEQUENCE" |
} |
local castSlashCommands = { } |
-- build table of all /cast and similar macro command aliases |
for _, castPrefix in ipairs(castPrefixes) do |
local str |
local command |
local index = 1 |
while true do |
str = "SLASH_"..castPrefix..index |
command = _G[str] |
if not command then |
break |
end |
command = strLower(command) |
LA:DebugPrint("Cast Slash Command "..str.." = "..command) |
castSlashCommands[command] = true |
index = index + 1 |
end |
end |
LA.castSlashCommands = castSlashCommands |
--[[{ |
[SLASH_USE1] = true, |
[SLASH_USE2] = true, |
[SLASH_USERANDOM1] = true, |
[SLASH_USERANDOM2] = true, |
[SLASH_CAST1] = true, |
[SLASH_CAST2] = true, |
[SLASH_CAST3] = true, |
[SLASH_CAST4] = true, |
[SLASH_CASTRANDOM1] = true, |
[SLASH_CASTRANDOM2] = true, |
[SLASH_CASTSEQUENCE1] = true, |
[SLASH_CASTSEQUENCE2] = true |
} ]] |
-- macroText is the text of a macro from the official Macro UI or an addon |
-- returns a table of spells found in the macro of the form |
-- { "spellName1" = true, spellGlobalID1 = true, "spellName2" = true, spellGlobalID2 = true, ...} |
-- spell names are all lower case, global ids are integers |
function LA:MacroSpells(macroText) |
macroText = strLower(macroText) |
local spells = {} |
local first, last, line |
first, last, line = macroText:find("([^\n]+)[\n]?") |
while first ~= nil do |
self:DebugPrint("Line",line) |
-- catch /cast, /castsequence, /castrandom |
local lineFirst, lineLast, slash = line:find("^(/cast[sequncradom]*)%s+") |
if lineFirst ~= nil then |
self:DebugPrint('Slash "'..slash..'"') |
if self.castSlashCommands[slash] then |
--self:DebugPrint("found slash command") |
local token |
local linePos = lineLast |
local found = true |
while found do |
while found do |
found = false |
-- skip reset= |
lineFirst, lineLast = line:find("^reset=%S+%s*", linePos + 1) |
if lineLast ~= nil then linePos = lineLast; found = true end |
-- skip macro options inside square brackets [ ] |
lineFirst, lineLast = line:find("^%[[^%]]*]", linePos + 1) |
if lineLast ~= nil then linePos = lineLast; found = true end |
-- skip whitespace and delimiters |
lineFirst, lineLast = line:find("^[%s,;]+", linePos + 1) |
if lineLast ~= nil then linePos = lineLast; found = true end |
end |
found = false |
lineFirst, lineLast, token = line:find("^([^%[,;]+)", linePos + 1) |
if lineLast ~= nil then |
token = strtrim(token) |
linePos = lineLast |
found = true |
self:DebugPrint('Token: "'..token..'"') |
-- spells[token] = true |
-- not self:GetRealSpellBookItemInfo because the extra info is not needed, |
-- and <token> may contain strings like mount names that are not in the spellbook |
local status, globalID = GetSpellBookItemInfo(token) |
if (not status) and self.nameToGlobal[token] then |
status, globalID = "SPELL", self.nameToGlobal[token] |
end |
if "SPELL" == status then |
local spell = self.Spell.Global[globalID] |
spells[spell.ID] = true |
spells[spell.SpecID] = true |
self:DebugPrint("Adding "..tostring(spell)) |
else |
self:DebugPrint("Status is "..tostring(status)) |
end |
end |
end |
end |
end |
first, last, line = macroText:find("([^\n]+)\n?", last + 1) |
end |
return spells |
end |
function LA:DiffActionBars() |
local spec = GetActiveSpecGroup() |
for slot = 1, 120 do |
local actionType = GetActionInfo(slot) |
-- local actionType, actionID, actionSubType, globalID = GetActionInfo(slot) |
-- If this slot once held a spell |
if self.character.actions and |
self.character.actions[spec] and |
self.character.actions[spec][slot] and |
not actionType |
then |
-- Ensure self.character.unlearned[spec][slot] exists |
if not self.character.unlearned then self.character.unlearned = { } end |
if not self.character.unlearned[spec] then self.character.unlearned[spec] = { } end |
if not self.character.unlearned[spec][slot] then self.character.unlearned[spec][slot] = { } end |
-- Save the old unlearned spell info in the "unlearned" table for future restoration |
self.character.unlearned[spec][slot][self.character.actions[spec][slot]] = true |
end |
end |
end |
function LA:SaveActionBars() |
local spec = GetActiveSpecGroup() |
if self.character.actions == nil then self.character.actions = {} end |
if self.character.actions[spec] then |
wipe(self.character.actions[spec]) |
else |
self.character.actions[spec] = { } |
end |
local savedActions = self.character.actions[spec] |
for actionSlot = 1, 120 do |
local actionType, globalID, actionSubType = GetActionInfo(actionSlot) |
if actionType == "spell" then |
savedActions[actionSlot] = globalID |
end |
end |
end |
function LA:FindMissingActions() |
if InCombatLockdown() then |
DEFAULT_CHAT_FRAME:AddMessage(self:GetText("title")..": "..self:GetText("errorInCombat")) |
return |
end |
local spells = {} |
local types = {} |
local subTypes = {} |
--local tracking = {} |
--local shapeshift = {} |
--local totem = {} |
local results = {} |
local macroSpells = {} |
local flyouts = {} |
local numTrackingTypes = GetNumTrackingTypes() |
--local bookCache = self.spellBookCache |
--local infoCache = self.spellInfoCache |
--[[ |
if not self.saved.tracking then |
for trackingType = 1, numTrackingTypes do |
local name, texture, active, category = GetTrackingInfo(trackingType) |
if category == BOOKTYPE_SPELL then |
tracking[name] = true |
end |
end |
end |
]] |
if (not self.saved.totem) and self.enClass == "SHAMAN" then |
-- Treat all Totem spells as though already on an action bar. |
self:DebugPrint("Searching for totems") |
for totemType = 1, MAX_TOTEMS do |
local totemSpells = {GetMultiCastTotemSpells(totemType)} |
for index, globalID in ipairs(totemSpells) do |
-- name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = GetSpellInfo(spell) |
--local totemName = GetSpellInfo(globalID) |
spells[globalID] = true |
--self:DebugPrint("Found totem "..totemName) |
end |
end |
end |
for slot = 1, 120 do |
local actionType, actionID, actionSubType = GetActionInfo(slot) |
if actionSubType == nil then |
actionSubType = "nil" |
end |
if actionType == nil then |
actionType = "nil" |
end |
-- development info |
if not types[actionType] then |
self:DebugPrint("Type of "..slot.." = "..actionType) |
types[actionType] = true |
end |
if not subTypes[actionSubType] then |
self:DebugPrint("Subtype of"..slot.." = "..actionSubType) |
subTypes[actionSubType] = true |
end |
if actionType == "spell" then |
local spell = self.Spell.Global[actionID] |
--self:DebugPrint("Found globalID for spell "..actionID..'='..self.specSpellCache[actionID]) |
local gID = spell.ID |
local sID = spell.SpecID |
self:DebugPrint(self.name..":FindMissingActions() found spell "..actionID.."/"..gID.."/"..sID.." in action slot "..slot) |
spells[gID] = true |
spells[sID] = true |
elseif actionType == "flyout" then |
-- flyoutID = actionID |
-- local name, description, size, flyoutKnown = GetFlyoutInfo(actionID) |
local flyout = LearningAid.Spell.Flyout[actionID] |
if flyout.Known then |
flyouts[actionID] = true |
for flyoutSlot = 1, flyout.Size do |
--local globalID, known = GetFlyoutSlotInfo(actionID, flyoutSlot) |
local flyoutSpell = flyout[flyoutSlot] |
if flyoutSpell.Known then |
-- local spellBookID = FindSpellBookSlotBySpellID(globalID) |
spells[flyoutSpell.ID] = true |
end |
end |
end |
elseif actionType == "macro" and actionID ~= 0 and self.saved.macros then |
self:DebugPrint("Macro in slot "..slot.." with ID "..actionID) |
local body = GetMacroBody(actionID) |
local foundSpells = self:MacroSpells(body) |
for spell in pairs(foundSpells) do |
macroSpells[spell] = true |
end |
end |
end |
-- Macaroon support code |
if self.saved.macros and Macaroon and Macaroon.Buttons then |
for index, button in ipairs(Macaroon.Buttons) do |
local buttonType = button[1].config.type |
local macroText = button[1].config.macro |
local storage = button[2] |
if (buttonType == "macro") and (storage == 0) then |
self:DebugPrint("Macaroon macro in slot", index) |
local foundSpells = self:MacroSpells(macroText) |
for spell in pairs(foundSpells) do |
macroSpells[spell] = true |
end |
end |
end |
end |
-- End Macaroon code |
if not self.saved.shapeshift then |
-- Treat all Shapeshift/stance/presence/aspect spells as though already on an action bar |
local numForms = GetNumShapeshiftForms() |
for form = 1, numForms do |
local formTexture, formName, formIsCastable, formIsActive, globalID = GetShapeshiftFormInfo(form) |
assert(globalID) |
spells[globalID] = true |
end |
end |
for tab = 1, 2 do |
local _, _, start, count = GetSpellTabInfo(tab) -- the current spec |
for i = start + 1, start + count do |
local spell = self.Spell.Book[i] |
assert(spell, "Spell "..i.." doesn't exist!") |
if "SPELL" == spell.Status then |
local spellName = spell.Name |
local spellNameLower = strLower(spellName) |
local globalID = spell.ID |
local specID = spell.SpecID |
if spell.Known and not ( |
self:IsIgnored(spell) or |
spells[globalID] or |
spells[specID] or -- spell is on any action bar |
spell.Passive or |
-- spell is not a tracking spell, or displaying tracking spells has been enabled |
--(not tracking[spellName]) and |
-- spells[globalID] or |
-- totem[globalID] or |
macroSpells[spellNameLower] or |
macroSpells[globalID] |
) |
then |
self:DebugPrint("Spell "..globalID..' "'..spellName..'" is not on any action bar.') |
--if macroSpells[spellNameLower] then self:DebugPrint("Found spell in macro") end |
table.insert(results, spell) |
end |
elseif "FLYOUT" == spell.Status and not flyouts[spell.ID] then |
-- print("you have not got "..spell.Name.." on your action bar bro") -- DEBUG FIXME DEBUG FIXME |
table.insert(results, spell) |
end |
end |
end |
table.sort(results, function (a, b) return a.Slot < b.Slot end) |
for index = 1, #results do |
self:AddButton(results[index]) |
end |
end |
function LA:RestoreAction(globalID) |
-- self.character.actions[spec][slot] = globalID |
local spec = GetActiveSpecGroup() |
if self.character.actions and self.character.actions[spec] then -- and self.character.actions[spec][globalID] |
for actionSlot, id in pairs(self.character.actions[spec]) do |
if id == globalID then |
self:DebugPrint("RestoreAction("..globalID.."): Found action at action slot "..actionSlot) |
--local actionType, actionID, actionSubType, slotGlobalID = GetActionInfo(actionSlot) |
local actionType = GetActionInfo(actionSlot) |
if actionType == nil then |
local bookID |
if self.spellBookCache[globalID] then |
bookID = self.spellBookCache[globalID].bookID |
self:DebugPrint("RestoreAction("..globalID.."): Found action at Spellbook slot "..bookID) |
PickupSpell(bookID, BOOKTYPE_SPELL) |
PlaceAction(actionSlot) |
end |
end |
end |
end |
end |
end |
local actionBarAliases = { |
default = 1, |
lowerleft = 1, |
alternate = 2, |
farright = RIGHT_ACTIONBAR_PAGE, -- 3 |
nearright = LEFT_ACTIONBAR_PAGE, -- 4 |
bottomright = BOTTOMRIGHT_ACTIONBAR_PAGE, -- 5 |
left = BOTTOMLEFT_ACTIONBAR_PAGE, -- 6 |
bottomleft = BOTTOMLEFT_ACTIONBAR_PAGE, |
cat = 7, |
stealth = 7, |
battle = 7, |
shadowform = 7, |
shadow = 7, |
defensive = 8, |
bear = 9, |
berserker = 9, |
moonkin = 10, |
__index = function (index) |
if nil == index or 'current' == index or '' == index or 0 == index then |
-- bar runs from 1 to NUM_ACTIONBAR_PAGES (6) |
local bar = GetActionBarPage() |
-- offset is from the first NUM_ACTIONBAR_PAGES (6) "normal" action bars, 0 if not offset |
local offset = GetBonusBarOffset() |
if 1 == bar and 0 ~= offset then |
return offset + NUM_ACTIONBAR_PAGES |
else |
return bar |
end |
end |
end |
} |
local function cleanChatInput(msg) |
if type(msg) == "string" then |
msg = strtrim(msg) |
if strmatch(msg, "^%d+$") then |
msg = tonumber(msg) |
end |
end |
return msg |
end |
function LA:CopyActionBar(barID, barClipboard) |
self.barClipboard = self.barClipboard or { } |
if not barClipboard then |
barClipboard = self.barClipboard |
end |
barID = cleanChatInput(barID) |
if type(barID) == "number" then |
barID = math.floor(tonumber(barID)) |
assert(barID >= 1 and barID <= 10) |
elseif type(barID) == "string" and actionBarAliases[barID] then |
barID = actionBarAliases[barID] |
end |
local barOffset = (barID - 1) * NUM_ACTIONBAR_BUTTONS |
for i = 1, NUM_ACTIONBAR_BUTTONS do |
local id = i + barOffset |
if HasAction(id) then |
local slot = {} |
slot.type, slot.ID, slot.subType = GetActionInfo(id) |
barClipboard[i] = slot |
end |
end |
end |
function LA:PasteActionBar(barID, barClipboard) |
barID = cleanChatInput(barID) |
if self.barClipboard then |
if type(barID) == "string" then |
barID = actionBarAliases[barID] |
end |
local barOffset = (barID - 1) * NUM_ACTIONBAR_BUTTONS |
for i = 1, NUM_ACTIONBAR_BUTTONS do |
local slot = self.barClipboard[i] |
if slot then |
if slot.type == "spell" then |
self.Spell.Global[slot.ID]:Pickup() |
elseif slot.type == "companion" then |
PickupCompanion(slot.subType, self.companionCache[slot.subType][slot.ID].index) |
elseif slot.type == "macro" then |
PickupMacro(slot.ID) |
elseif slot.type == "equipmentset" then |
PickupEquipmentSetByName(slot.ID) |
elseif slot.type == "item" then |
PickupItem(slot.ID) |
elseif slot.type == "flyout" then |
self.Spell.Flyout[slot.ID]:Pickup() |
end |
PlaceAction(i + barOffset) |
else |
PickupAction(i + barOffset) |
end |
ClearCursor() |
end |
end |
end |
Bugs: |
# 2015-06-23: Patch 6.2.0 release day |
Spam filter is busted. No messages show regardless of setting. |
INCORRECT: There is no more spam when changing specs! HALLELUJAH |
# 2015-01-05 |
Ignore list savedvariables code mismatch with ignore list update code. Need to reconcile and possibly |
update data format number and update UpgradeIgnoreList to handle the old to new conversion |
Make a copy of saved variables files before doing that! |
Find Missing Actions is borked for Monks, again. |
RFE: Global option to automatically ignore auto attack / auto shot |
# 2014 |
Error when learning warlock talent grimoire of service (extra temp demons) |
which brings up a button with the flyout |
then unlearning the talent |
then mousing over the button |
FIXED, MUST VERIFY "Asphyxiate", a talent override, has the wrong name, "Strangulate" in the LA popup. |
FIXED fix remaining instances of .globalID, .specID and .slot to ._gid, ._sid and ._slot respectively |
Flyouts: Tricky cases |
Don't have flyout, do have all flyout spells |
*do nothing*? show flyout? |
Don't have flyout, have some but not all spells |
show flyout? show missing spells? *show both*? |
Don't have flyout, have no spells |
show flyout? show spells? *show both*? |
Have flyout, have some but not all spells |
show missing spells? / *do nothing*? |
Flyouts: Easy cases |
Have flyout, have no spells |
do nothing |
Have flyout, have all spells |
do nothing |
Should BookID[slot], GlobalID[gid] and FlyoutID[fid] cache their results? |
Issue: Slot may change when the spellbook updates |
Check for and update changed instances? |
Wipe BookID and refresh each update? Won't generate garbage if the contents are still cached in GlobalID |
Consequences of stale slot info: |
spell or flyout that was in slot and now either is not in any slot or is in different slot: bad |
How does this addon actually work? |
Login |
Events |
Learned_Spell_In_Tab |
Spells_Changed |
Swap spec |
User actions |
/la search |
How *should* this addon work? |
Login |
Build table of spells in book and spells on bars |
If autosearch enabled, look for spells not on bars |
Events |
Learned_Spell_In_Tab |
Update spells in book |
If autosearch enabled, see if spell is not on bar |
Spells_Changed |
Unnecessary? |
Whatever the action bar item changed event is |
Update spells on bars |
Swap spec |
Update spells in book |
Update spells on bars |
If autosearch enabled, look for spells not on bars |
User actions |
/la search |
Check for spells not on bars and display them |
/la autosearch |
Always display missing spells as soon as they are detected as missing |
### WoW 5.0.4 / Mists of Pandaria Changes ### |
The pet/mount UI has changed so much, and putting pets/mounts on action bars |
seems so antiquated when people have hundreds of pets and use addons to manage |
them, and pet/mount support in Learning Aid is so buggy that I have removed pet |
and mount support. |
API changes |
New event: PLAYER_SPECIALIZATION_CHANGED fires at the end of the spec swap process |
Event changed: LEARNED_SPELL_IN_TAB, global spell id (integer), tab (integer, index 1) |
### Learning Aid 1.12 / 2.0 Redesign Work ### |
Top down: |
1. Show an icon when the player learns an ability AND the ability is not already on their action bar AND they haven't chosen to ignore that ability |
* Detect when an ability is learned |
* Scanning the spellbook |
* Watching events |
* Chat filter |
* Decide whether or not it is "new enough" to display |
* Has it been learned before? |
* Is it on an action bar already? |
* Determine whether the user wants to see it |
* Is it on the ignore list? |
* What does the ignore list really mean? |
* Class abilities |
* Ignore this ability regardless of class |
* Ignore this ability for this class only |
* Ignore this ability for this character only |
* Tradeskill abilities |
* Ignore this ability for all characters |
* Ignore this ability for this character |
* Ignore all ranks of this ability or just this rank |
* Guild abilities |
* Ignore for all characters |
* Ignore for this guild |
* Ignore for this character |
### IN PROGRESS ### |
got an error while learning Master Riding on the 4.2.0 PTR |
test learning pet talents re: spam filter -- NOT WORKING |
test unlearning pet talents re: spam filter |
clicking a "/usetalents 2" macro while the retalent cast is going on fires a spurious UNIT_SPELLCAST_FAILED_QUIET which fools LA into thinking the talent swap was cancelled |
printing newly learned stuff isn't getting triggered, like I just learned Journeyman Mining and the message didn't appear until way way later, after I spent a talent point. |
Same for learning class skills from a trainer -- passes through unmolested |
Learning talents seems to be working |
filter tradeskills |
"You have learned how to create a new item: <item>." |
AddButton cannot handle flyouts yet |
Spam filter: Spells learned from quests? |
Learn All does not properly detect when the player's character does not have enough money to buy everything |
### DESIGN BLAH ### |
51296 = GM cooking |
3413 = Expert cooking |
spells learned and unlearned |
name, link, id -- player spells/abilities |
name -- pet spells/abilities |
I want to print them out in order |
I want to remove stuff that is unlearned then relearned |
pets r ez, table.insert(petLearned, name) |
--spellsLearned.index[i] = id |
spellsLearned.name[id] = name |
--spellsLearned.id[i] = id |
spellsLearned.link[id] = link |
OR |
spellsLearned[i] = link -- link contains both id and name |
for id, name in pairs(spellsUnlearned.name) do |
if spellsLearned.name[id] then |
spellsLearned.name[id] = nil |
spellsLearned.link[id] = nil |
spellsUnlearned.name[id] = nil |
spellsUnlearned.link[id] = nil |
end |
end |
later... |
I have three data items: id, name, link |
I want to de-dupe on id |
I want to sort on name |
I want to print link |
lua sucks |
### NEW DESIGN FOR SPELL INFO CACHE ### |
part the first: spell info database |
indexed by global id |
invariant information |
spell name |
spell link |
--spell icon -- probably not needed? |
is spell passive? |
possibly whether spell has class/racial/tradeskill/talent/guild origin |
type: tracking (irrelevant, no longer in spellbook, delete option), aura/presence/shapeshift/aspect/stance/form, totem |
is on a flyout? |
part the second: spellbook cache |
indexed by spellbook id |
is spell known? |
global id |
part the third: spam filter cache |
newly learned |
indexed by id |
sort table (by name) |
newly unlearned |
indexed by id |
sort table (by name) |
part the fourth: action bar cache? |
### STUFF WHAT IS DONE ### |
Stack overflow due to infinite recursion in UpdateIgnoreList -- FIXED IN 1.11.2 |
figure out how to handle non-class abilities (like mining, herbing, skinning) with the ignore feature, which currently stores ignore information by class -- FIXED IN 1.11.1 |
rapidly learning multiple pets: first pet (mr chilly) did not show up, subsequent pets (mini thor, core hound pup, warbot) did -- FIXED in 1.11.1 |
Horked up real good from dropping AceEvent -- FIXED in 1.11.1 |
fix spam filter for spells with the same name (cat mangle and bear mangle) -- done |
fix find missing spells for new cache layout -- done |
test unlearning talents re: spam filter -- done |
what's going on with call pet? -- DiffSpellBook wasn't handling flyouts correctly |
spell_update_cooldown probably not going to work as end signal for retalenting (what if it fires while casting the retalent spell?) instead of player_talent_update |
fixed, using unit_spellcast_stop |
# Data Structures # |
## Saved Variables ## |
### Per Account ### |
self.saved = <table> |
#### Options #### |
totem = <boolean> (default = true) |
search for missing Totem spells when scanning action bars |
restoreActions = <boolean> (default = true) |
When re-learning previously unlearned spells, put them back on the |
action bar where they were (if the space is empty) |
filterSpam = <integer> (default = 1) |
0: Do not filter learning spam |
1: Summarize learning spam in one or two lines |
2: Do not display any learning spam |
locked = <boolean> |
If true, the Learning Aid frame cannot be moved by dragging |
If nil or false, the Learning Aid frame can be moved by dragging |
frameStrata = <string> |
What frame strata should the Learning Aid frame be displayed at |
macros = <boolean> (default = true) |
Whether to search inside macros for missing spells and abilities when |
scanning action bars |
#### Misc #### |
dataVersion = <integer> |
Incremented when significant changes occur to saved variables format |
nil in versions prior to 1.11.1 |
1 in 1.11.1 |
x = <float> |
y = <float> |
X and Y coordinates for the upper-left corner of the Learning Aid window |
The origin is in the lower-left corner of the screen, using Blizzard's own weird |
coordinate system |
version = <string> |
The version of Learning Aid that the variables were saved with |
debugFlags = <table> |
log debug messages and table accesses to LearningAid_DebugLog |
SET = <boolean> (default false) |
log updates to addon table values |
CALL = <boolean> (default false) |
log updates to addon methods |
GET = <boolean> (default false) |
log reads of addon table values |
#### Ignore List #### |
##### OLD STYLE PRE-1.11.1, DO NOT USE, FOR REFERENCE ONLY (before dataVersion == 1) ##### |
ignore = <table> |
keys: <string> character class (locale dependent) |
values: <table> |
keys: <string> lowercase spell name |
values: <string> original case spell name |
##### New Style 1.11.1 Ignore List (dataVersion == 1) ##### |
ignore = <table> |
keys: <string> character class (locale independent) or "professions" |
values: <table> |
keys: <integer> Global spell ID of ignored spell |
values: <boolean> true if spell is ignored |
### Per Character ### |
character = <table> |
dataVersion = <integer> Version of the saved variable data format |
version = <string> Version string of Learning Aid that the file was saved with |
guild = <string> Name of the guild the character was in when the file was saved |
actions = <table> TODO: describe this |
guildSpells = <table> |
keys: <integer> Global spell ID of a non-passive guild perk |
values: TODO: describe this |
unlearned = <table> |
[spec <integer> Number of current spec. Can be 1 or 2.] = <table> |
[slot <integer> Action bar slot. Can be from 1 to 120.] = <table> |
[oldID <integer> Global spell id of spell that was unlearned.] = <boolean> Always true if exists |
## Spell Cache ## |
### Invariant Cache ### |
Data that should not change during a game session |
self.spellInfoCache = { |
<integer> = { -- globalID of a spell |
name = <string>, -- Name of spell |
subName = <string>, -- SubName of spell |
-- Examples: "Passive", "Racial Passive", "Apprentice", "Cat Form", "Bear Form" |
passive = <boolean>, -- true if spell is passive, false otherwise |
link = <string>, -- return value of GetSpellLink(globalID) |
globalID -- just in case |
}, |
<integer> = { -- globalID of another spell |
... |
}, |
... |
} |
### Spellbook Cache ### |
Variable spell information, may or may not change during a game session |
spellBookCache = { |
<integer> = { -- globalID of spell |
known = <boolean>, -- does the active character know this spell right now |
status = <string>, -- one of "SPELL", "FUTURESPELL", "FLYOUT" |
bookID = <integer>, -- index in spellbook |
info = <table reference>, -- convenience link to spellInfoCache[globalID] |
origin = <string> -- one of the constants in self.origin (profession, class, guild, riding, race) |
subOrigin = <string> -- if origin is self.origin.profession, index of profession as returned by GetProfessions() |
}, |
... |
} |
Flyout information, may or may not change during a game session |
flyoutCache = { |
<integer> = { -- flyoutID |
name = <string>, -- name of flyout |
description = <string>, -- tooltip text |
count = <integer>, -- number of spells in the flyout |
known = <boolean>, -- is it known by the current character |
bookID = <integer> -- index in spellbook |
}, |
... |
} |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
Events.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
function LA:ADDON_LOADED(addon) |
if addon == addonName then |
self:Init() |
elseif addon == "Blizzard_TrainerUI" then |
self:CreateTrainAllButton() |
--self:UnregisterEvent("ADDON_LOADED") |
end |
end |
function LA:ACTIONBAR_SLOT_CHANGED(slot) |
-- actionbar1 = ["spell" 2354] ["macro" 5] [nil] |
-- then after untalenting actionbar1 = [nil] ["macro" 5] [nil] |
-- self.character.actions[spec][1][2354] = true |
if self.state.untalenting then |
-- something something on (slot) |
local spec = GetActiveSpecGroup() |
local actionType, actionID, actionSubType, globalID = GetActionInfo(slot) |
local oldID = self.character.actions[spec][slot] |
self:DebugPrint("Action Slot "..slot.." changed:", |
(actionType or "")..",", |
(actionID or "")..",", |
(actionSubType or "")..",", |
(globalID or "")..",", |
(oldID or "") |
) |
if oldID and (actionType ~= BOOKTYPE_SPELL or globalID ~= oldID) then |
if not self.character.unlearned then self.character.unlearned = {} end |
if not self.character.unlearned[spec] then self.character.unlearned[spec] = {} end |
if not self.character.unlearned[spec][slot] then self.character.unlearned[spec][slot] = {} end |
self.character.unlearned[spec][slot][oldID] = true |
end |
end |
end |
function LA:ACTIVE_TALENT_GROUP_CHANGED(newSpec, oldSpec) |
-- currently, newSpec and oldSpec can only take on the values 1 or 2 |
-- nothing to see here, just making an entry in the debug log |
-- print("LA:ACTIVE_TALENT_GROUP_CHANGED:", ...) |
end |
--[[ No longer needed as of patch 6.2.0! |
function LA:CHAT_MSG_SYSTEM(message) |
-- note: pet spells, when learned, do not come as links |
-- player spells do come as links |
--local rank |
local spell |
local t |
local str = string.match(message, self.patterns.learnSpell) or string.match(message, self.patterns.learnAbility) or string.match(message, self.patterns.learnPassive) |
if str then |
t = self.spellsLearned |
else |
str = string.match(message, self.patterns.unlearnSpell) |
if str then |
t = self.spellsUnlearned |
end |
end |
if t then |
local name, globalID = self:UnlinkSpell(str) |
self:DebugPrint("Spam filter matched spell", name, globalID) |
spell = self.Spell.Global[globalID] |
tinsert(t, spell) |
else |
str = string.match(message, self.patterns.petLearnAbility) or string.match(message, self.patterns.petLearnSpell) |
if str then |
t = self.petLearned |
else |
str = string.match(message, self.patterns.petUnlearnSpell) |
if str then |
print("Unlearning pet spells is broken, yo: "..str) |
t = self.petUnlearned |
end |
end |
if t then |
self:DebugPrint("Spam filter matched pet spell "..str) |
table.insert(t, str) |
end |
end |
end |
--]] |
function LA:CURRENT_SPELL_CAST_CHANGED() |
-- local frame = self.frame -- unused |
local buttons = self.buttons |
for i = 1, self:GetVisible() do |
local button = buttons[i] |
--if button.kind == BOOKTYPE_SPELL then |
self:SpellButton_UpdateSelection(button) |
--end |
end |
end |
-- Changed in Mists, formerly provided just the tab number I think |
-- Now provides SpellID (yay!) and isGuildSpell (yay!) |
function LA:LEARNED_SPELL_IN_TAB(spellID, tabNum, isGuildSpell) |
-- DEBUG -- |
-- print("LearningAid: Learned spell #"..tostring(spellID).." in tab #"..tostring(tabNum)..(isGuildSpell and " (Guild)" or "")) |
-- /DEBUG -- |
--[[ |
if isGuildSpell then |
local bookID = FindSpellBookSlotBySpellID(spellID) |
self:SpellBookInfo(bookID, self.origin.guild) |
end |
]] |
end |
function LA:PLAYER_ENTERING_WORLD() |
self:RegisterEvent("SPELLS_CHANGED") |
end |
--[[ MOP |
function LA:PLAYER_GUILD_UPDATE() |
self:UpdateGuild() |
end |
]] |
-- when transitioning continents, instances, etc the spellbook may be in flux |
-- between PLAYER_LEAVING_WORLD and PLAYER_ENTERING_WORLD |
function LA:PLAYER_LEAVING_WORLD() |
self:UnregisterEvent("SPELLS_CHANGED") |
self:RegisterEvent("PLAYER_ENTERING_WORLD") |
end |
function LA:PLAYER_LEVEL_UP() |
self:PrintPending() |
end |
function LA:PLAYER_LOGIN() |
self:UpdateSpellBook() |
self:RegisterEvent("SPELLS_CHANGED") |
self:RegisterEvent("LEARNED_SPELL_IN_TAB") |
end |
function LA:PLAYER_LOGOUT() |
self:SaveActionBars() |
end |
function LA:PLAYER_REGEN_DISABLED() |
self.closeButton:Disable() |
end |
function LA:PLAYER_REGEN_ENABLED() |
self.closeButton:Enable() |
self:ProcessQueue() |
end |
function LA:PLAYER_TALENT_UPDATE() |
if self.state.untalenting then |
self.state.untalenting = false |
self:UnregisterEvent("ACTIONBAR_SLOT_CHANGED") |
self:UnregisterEvent("PLAYER_TALENT_UPDATE") |
self:UnregisterEvent("UI_ERROR_MESSAGE") |
self:PrintPending() |
elseif self.pendingTalentCount > 0 then |
self.pendingTalentCount = self.pendingTalentCount - 1 |
if self.pendingTalentCount <= 0 then |
self:PrintPending() |
self:UnregisterEvent("PLAYER_TALENT_UPDATE") |
end |
elseif self.state.learning then |
self.state.learning = false |
self:UnregisterEvent("PLAYER_TALENT_UPDATE") |
self:PrintPending() |
end |
end |
function LA:SPELLS_CHANGED() |
self:UpgradeIgnoreList() |
--PANDARIA |
--[[ |
if not self.companionsReady then |
self:UpdateCompanions() |
end |
]] |
if self.numSpells > 0 then |
if self:DiffSpellBook() > 0 then |
if self.pendingBuyCount > 0 then |
self.pendingBuyCount = self.pendingBuyCount - 1 |
if self.pendingBuyCount <= 0 then |
self:PrintPending() |
end |
end |
end |
end |
end |
function LA:SPELL_UPDATE_COOLDOWN() |
local frame = self.frame |
local buttons = self.buttons |
for i = 1, self:GetVisible() do |
local button = buttons[i] |
--if button.kind == BOOKTYPE_SPELL then |
self:UpdateButton(button) |
--elseif button.kind == "MOUNT" or button.kind == "CRITTER" then |
-- local start, duration, enable = GetCompanionCooldown(button.kind, button:GetID()) |
-- CooldownFrame_SetTimer(button.cooldown, start, duration, enable); |
--end |
end |
end |
function LA:TRADE_SKILL_SHOW() |
local frame = self.frame |
local buttons = self.buttons |
for i = 1, self:GetVisible() do |
local button = buttons[i] |
--if button.kind == BOOKTYPE_SPELL then |
if button.item.Selected then |
button:SetChecked(true) |
else |
button:SetChecked(false) |
end |
--end |
end |
end |
LA.TRADE_SKILL_CLOSE = LA.TRADE_SKILL_SHOW |
function LA:UNIT_SPELLCAST_START(unit, spellName, deprecated, counter, globalID) |
if unit == "player" and |
(globalID == self.activatePrimarySpec or globalID == self.activateSecondarySpec) -- and |
-- not self.state.retalenting |
then |
self:DebugPrint("Talent swap initiated") |
-- print("Retalenting initiated!") -- DEBUG FIXME |
self.state.retalenting = true |
--self:RegisterEvent("PLAYER_TALENT_UPDATE", "OnEvent") |
self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED", "OnEvent") |
self:RegisterEvent("UNIT_SPELLCAST_STOP", "OnEvent") |
self:RegisterEvent("UNIT_SPELLCAST_FAILED_QUIET", "OnEvent") |
end |
end |
function LA:UNIT_SPELLCAST_INTERRUPTED(unit, spellName, deprecated, counter, globalID) |
if unit == "player" and |
(globalID == self.activatePrimarySpec or globalID == self.activateSecondarySpec) -- and |
-- counter == self.state.retalenting |
then |
self:DebugPrint("Talent swap canceled") |
-- print("Retalenting canceled!") -- DEBUG FIXME |
self.state.retalenting = false |
self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED") |
self:UnregisterEvent("UNIT_SPELLCAST_STOP") |
self:UnregisterEvent("UNIT_SPELLCAST_FAILED_QUIET") |
end |
end |
LA.UNIT_SPELLCAST_FAILED_QUIET = LA.UNIT_SPELLCAST_INTERRUPTED |
function LA:UNIT_SPELLCAST_STOP(unit, spellName, deprecated, counter, globalID) |
if unit == "player" and |
(globalID == self.activatePrimarySpec or globalID == self.activateSecondarySpec) -- and |
-- counter == self.state.retalenting |
then |
self:DebugPrint("Talent swap completed") |
-- print("Retalenting completed!") -- DEBUG FIXME |
self:Hide() -- anything currently displayed is likely no longer valid |
self:UpdateSpellBook() -- fixes bug: spec spells all pop up at end of retalenting process |
self.state.retalenting = false |
self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED") |
self:UnregisterEvent("UNIT_SPELLCAST_STOP") |
self:UnregisterEvent("UNIT_SPELLCAST_FAILED_QUIET") |
--[[ |
if self.saved.filterSpam == LA.FILTER_SUMMARIZE then |
local spamCache = self.spellSpamCache |
-- don't print spells that are unlearned then immediately relearned |
for id, name in pairs(unlearned.name) do |
if learned.name[id] then |
learned.name[id] = nil |
learned.link[id] = nil |
unlearned.name[id] = nil |
unlearned.link[id] = nil |
end |
end |
end |
]] |
self:PrintPending() |
end |
end |
function LA:UI_ERROR_MESSAGE() |
if self.state.untalenting then |
self:UnregisterEvent("ACTIONBAR_SLOT_CHANGED") |
self:UnregisterEvent("UI_ERROR_MESSAGE") |
self:UnregisterEvent("PLAYER_TALENT_UPDATE") |
self.state.untalenting = false |
end |
end |
function LA:UI_SCALE_CHANGED(...) |
self:DebugPrint("UI Scale changed: ",...) |
end |
--[[ PANDARIA |
function LA:UPDATE_BINDINGS() |
self:UpdateCompanions() |
self:UnregisterEvent("UPDATE_BINDINGS") |
end ]] |
function LA:VARIABLES_LOADED() |
if self.saved.x and self.saved.y then |
self.frame:ClearAllPoints() |
self.frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", self.saved.x, self.saved.y) |
end |
end |
--[[ |
Learning Aid is copyright © 2008-2015 Jamash (Kil'jaeden US Horde) |
Email: jamashkj@gmail.com |
Trainer.lua is part of Learning Aid. |
Learning Aid is free software: you can redistribute it and/or modify |
it under the terms of the GNU Lesser General Public License as |
published by the Free Software Foundation, either version 3 of the |
License, or (at your option) any later version. |
Learning Aid is distributed in the hope that it will be useful, but |
WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
Lesser General Public License for more details. |
You should have received a copy of the GNU Lesser General Public |
License along with Learning Aid. If not, see |
<http://www.gnu.org/licenses/>. |
To download the latest official version of Learning Aid, please visit |
either Curse or WowInterface at one of the following URLs: |
http://wow.curse.com/downloads/wow-addons/details/learningaid.aspx |
http://www.wowinterface.com/downloads/info10622-LearningAid.html |
Other sites that host Learning Aid are not official and may contain |
outdated or modified versions. If you have obtained Learning Aid from |
any other source, I strongly encourage you to use Curse or WoWInterface |
for updates in the future. |
]] |
local addonName, private = ... |
local LA = private.LA |
function LA:CreateTrainAllButton() |
if not self.trainAllButton then |
local button = CreateFrame("Button", "LearningAid_TrainAllButton", ClassTrainerTrainButton, "MagicButtonTemplate") |
button:SetPoint("RIGHT", ClassTrainerTrainButton, "LEFT") |
button:SetText(self:GetText("trainAllButton")) |
button:SetScript("OnClick", function() StaticPopup_Show("LEARNING_AID_TRAINER_BUY_ALL") end) |
--button:SetScript("OnShow", function(thisButton) self:GetAvailableTrainerServices() end) |
--button:SetScript("OnHide", function() wipe(self.availableServices) end) |
button:Show() |
self.trainAllButton = button |
StaticPopupDialogs.LEARNING_AID_TRAINER_BUY_ALL = { |
text = self:GetText("trainAllPopup"), -- "Train all skills for" |
button1 = ACCEPT, |
button2 = CANCEL, |
OnAccept = function() |
self:BuyAllTrainerServices(LA.CONFIRM_TRAINER_BUY_ALL) |
button:Disable() |
end, |
OnShow = function(dialog) |
MoneyFrame_Update(dialog.moneyFrame, self.availableServices.cost) |
end, |
hasMoneyFrame = 1, |
--showAlert = 1, |
timeout = 0, |
exclusive = 1, |
hideOnEscape = 1, |
whileDead = false |
} |
hooksecurefunc("ClassTrainerFrame_Update", function() LearningAid:GetAvailableTrainerServices() end) |
return button |
end |
end |
function LA:GetAvailableTrainerServices() |
local copper = 0 |
local services = self.availableServices |
wipe(services) |
for i = 1, GetNumTrainerServices() do |
local t = {} -- omg junk table |
--name (String), subType (String), category (String), texture (String), requiredLevel (Number), topServiceLine (Number) |
t.name, t.subType, t.category, t.texture, t.level, t.topServiceLine = GetTrainerServiceInfo(i) |
t.copper, t.isProfession = GetTrainerServiceCost(i) |
--t.skillLine = GetTrainerServiceSkillLine(i) |
t.index = i |
--t.link = GetTrainerServiceItemLink(i) |
if (t.category == "available") and not t.isProfession then |
copper = copper + t.copper |
table.insert(services, t) |
end |
end |
services.cost = copper |
--self:DebugPrint("Total cost of available services: "..GetCoinText(copper)) |
if #services > 0 and copper <= GetMoney() then |
self.trainAllButton:Enable() |
else |
self.trainAllButton:Disable() |
end |
return services |
end |
function LA:BuyAllTrainerServices(really) |
local services = self.availableServices |
if services and really == LA.CONFIRM_TRAINER_BUY_ALL then |
self.pendingBuyCount = #services |
self:DebugPrint("Buying all "..self.pendingBuyCount.." service(s) for "..services.cost.." copper") |
for i, t in ipairs(services) do |
--if t.category == "available" then |
BuyTrainerService(t.index) |
--end |
end |
--[[ |
wipe(services) |
self.learning = false |
local learned = self:FormatSpells(self.spellsLearned) |
if learned then self:SystemPrint(self:GetText("youHaveLearned", learned)) end |
wipe (self.spellsLearned) |
--]] |
end |
end |