diff --git a/.pkgmeta b/.pkgmeta new file mode 100644 index 0000000..d01aff9 --- /dev/null +++ b/.pkgmeta @@ -0,0 +1,6 @@ +package-as: RepairBroker + +externals: + libs/LibTooltip-1.0: svn://svn.wowace.com/wow/libtooltip/mainline/trunk/LibTooltip-1.0 + libs/CallbackHandler-1.0: svn://svn.wowace.com/wow/ace3/mainline/trunk/CallbackHandler-1.0 + libs/LibStub: svn://svn.wowace.com/wow/libstub/mainline/tags/1.0 \ No newline at end of file diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000..ffb4704 --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1 @@ +Project page: http://www.wowace.com/projects/repairbroker/ \ No newline at end of file diff --git a/RepairBroker.lua b/RepairBroker.lua new file mode 100644 index 0000000..f113ffc --- /dev/null +++ b/RepairBroker.lua @@ -0,0 +1,208 @@ +local LibTooltip = LibStub('LibTooltip-1.0') +local LibDataBroker = LibStub('LibDataBroker-1.1') +if not LibDataBroker then return end + +local name = "RepairBroker" +local Repair = LibDataBroker:NewDataObject(name, { + icon = "Interface\\Icons\\Trade_BlackSmithing", + label = "Dur", + text = "100%", + } +) + +local print = function(msg) print("|cFF5555AA"..name..": |cFFAAAAFF"..msg) end + +local slots = { } +do + local slotNames = { "HeadSlot", "ShoulderSlot", "ChestSlot", "WristSlot", "HandsSlot", "WaistSlot", "LegsSlot", "FeetSlot", "MainHandSlot", "SecondaryHandSlot", "RangedSlot" } + for i,name in ipairs(slotNames) do + slots[ i ] = { GetInventorySlotInfo(name), string.sub(name, 1, string.len(name)-4), 1 } + end + slotNames = nil -- dispose +end + +local OnLoad = function() + if not RepairBrokerDB then + RepairBrokerDB = { + autoRepair = 1, -- nil or 1 + useBuildBank = nil, -- nil or 1 + } + end +end + +--------------------------------- +-- Support functions +--------------------------------- +local DurabilityColor = function(perc) + if not perc or perc < 0 then return "|cFF555555" end + if perc == 1 then + return "|cFF005500" -- Dark green + elseif perc >= .9 then + return "|cFF00AA00" -- Green + elseif perc > .5 then + return "|cFFFFFF00" -- Yellow + elseif perc > .2 then + return "|cFFFF9900" -- Orange + else + return "|cFFFF0000" -- Red + end +end + +local CopperToString = function(c) + local str = "" + if not c or c < 0 then return str end + if c > 10000 then + local g = math.floor(c/10000) + c = c - g*10000 + str = str.."|cFFFFD800"..g.." |TInterface\\MoneyFrame\\UI-GoldIcon.blp:0:0:0:0|t " + end + if c > 100 then + local s = math.floor(c/100) + c = c - s*100 + str = str.."|cFFC7C7C7"..s.." |TInterface\\MoneyFrame\\UI-SilverIcon.blp:0:0:0:0|t " + end + if c > 0 then + str = str.."|cFFEEA55F"..c.." |TInterface\\MoneyFrame\\UI-CopperIcon.blp:0:0:0:0|t " + end + return str +end + +--------------------------------- +-- Durability updates and repair +--------------------------------- +local UpdateDurability = function() + local dur, max + local minDur = 1 + for i,info in ipairs(slots) do + if GetInventoryItemLink("player", info[1]) then + dur, max = GetInventoryItemDurability(info[1]) + if not dur or max == 0 then + info[3] = -1 + else + info[3] = dur/max + if info[3] < minDur then minDur = info[3] end + end + else + info[3] = -1 + end + end + Repair.text = DurabilityColor(minDur)..math.floor(minDur*100).."%" +end + +local AutoRepair = function() + if not RepairBrokerDB.autoRepair then return end + local cost, canRepair = GetRepairAllCost() + if not canRepair or cost == 0 then return end + + -- Use guildbank to repair + if CanWithdrawGuildBankMoney() and RepairBrokerDB.useBuildBank and GetGuildBankMoney() >= cost then + RepairAllItems(1) + elseif GetMoney() >= cost then -- Repair the old fashion way + RepairAllItems() + print("Repaired for "..CopperToString(cost)) + else + print("Unable to AutoRepair, you need "..CopperToString(cost - GetMoney()).." more.") + end +end + +local OnEvent = function(_, event, ...) + if event ~= "MERCHANT_SHOW" then + UpdateDurability() + else + AutoRepair() + end +end + +local event = CreateFrame("Frame") +event:RegisterEvent("ADDON_LOADED") +event:SetScript("OnEvent", function(_, _, addon) + if addon ~= name then return end + OnLoad() + event:SetScript("OnEvent", OnEvent) + event:UnregisterEvent("ADDON_LOADED") + event:RegisterEvent("PLAYER_DEAD") + event:RegisterEvent("PLAYER_UNGHOST") + event:RegisterEvent("PLAYER_REGEN_ENABLED") + event:RegisterEvent("UPDATE_INVENTORY_ALERTS") + event:RegisterEvent("MERCHANT_SHOW") + event:RegisterEvent("MERCHANT_CLOSED") +end) + +--------------------------------- +-- TOOLTIP +--------------------------------- +local tooltip = nil + +function Repair:OnEnter() + if tooltip then Repair:OnLeave() end + UpdateDurability() + tooltip = LibTooltip:Acquire("RepairTooltip", 3, "LEFT", "CENTER", "RIGHT") + tooltip:AddHeader("Equipted items") + local dur, totalCost, cost = 0, 0, nil + local gray = "|cFFAAAAAA" + for i,info in ipairs(slots) do + dur = math.floor(info[3]*100) + if dur >= 0 then + dur = DurabilityColor(info[3])..dur + dur = dur.."%" + else + dur = DurabilityColor(-1).."- " + end + + cost = select(3, GameTooltip:SetInventoryItem("player", info[1])) + tooltip:AddLine( + gray..info[2], -- Slot + dur, -- Dur + CopperToString(cost) -- Cost + ) + if cost and cost > 0 then totalCost = totalCost + cost end + end + + local cost, dur, maxDur = 0, 1, 1 + for bag = 0, 4 do + for slot = 1, GetContainerNumSlots(bag) do + -- Cost + local _, repairCost = GameTooltip:SetBagItem(bag, slot) + if repairCost then cost = cost + repairCost end + + -- Dur + d, m = GetContainerItemDurability(bag, slot) + if d and m then dur = dur + d; maxDur = maxDur + m end + end + end + GameTooltip:Hide() + local averageDur = dur/maxDur + tooltip:AddHeader(" ") + tooltip:AddHeader("Inventory") + tooltip:AddLine( + gray.."Items in your bags", -- Slot + DurabilityColor(averageDur)..math.floor(100*averageDur).."%", -- Dur + CopperToString(cost) -- Cost + ) + if cost and cost > 0 then totalCost = totalCost + cost end + + if totalCost > 0 then + tooltip:AddHeader(" ") + tooltip:AddHeader("Total cost") + + local m = 1 + for i=4, 8 do + tooltip:AddLine( + gray.._G["FACTION_STANDING_LABEL"..i], -- Slot + " ", -- Dur + CopperToString(math.floor(totalCost*m+.5)) -- Cost + ) + m = m - .05 + end + end + + tooltip:SmartAnchorTo(self) + tooltip:Show() +end + +function Repair:OnLeave() + if not tooltip then return end + tooltip:Clear() + LibTooltip:Release(tooltip) + tooltip = nil +end \ No newline at end of file diff --git a/RepairBroker.toc b/RepairBroker.toc new file mode 100644 index 0000000..b98a496 --- /dev/null +++ b/RepairBroker.toc @@ -0,0 +1,13 @@ +## Interface: 30000 +## Title: Repair Broker +## Notes: Auto repair + shows durability. +## Author: Merl @ chainwet.net +## Version: @project-version@ +## DefaultState: enabled +## SavedVariables: RepairBrokerDB + +libs\LibStub\LibStub.lua +libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +libs\LibDataBroker-1.1\LibDataBroker-1.1.lua +libs\LibTooltip-1.0\LibTooltip-1.0.lua +RepairBroker.lua \ No newline at end of file diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua new file mode 100644 index 0000000..0bc9214 --- /dev/null +++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -0,0 +1,239 @@ +--[[ $Id: CallbackHandler-1.0.lua 60697 2008-02-09 16:51:20Z nevcairiel $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 3 +local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) + +if not CallbackHandler then return end -- No upgrade needed + +local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} + +local type = type +local pcall = pcall +local pairs = pairs +local assert = assert +local concat = table.concat +local loadstring = loadstring +local next = next +local select = select +local type = type +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local next, xpcall, eh = ... + + local method, ARGS + local function call() method(ARGS) end + + local function dispatch(handlers, ...) + local index + index, method = next(handlers) + if not method then return end + local OLD_ARGS = ARGS + ARGS = ... + repeat + xpcall(call, eh) + index, method = next(handlers, index) + until not method + ARGS = OLD_ARGS + end + + return dispatch + ]] + + local ARGS, OLD_ARGS = {}, {} + for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end + code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) + +-------------------------------------------------------------------------- +-- CallbackHandler:New +-- +-- target - target object to embed public APIs in +-- RegisterName - name of the callback registration API, default "RegisterCallback" +-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" +-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. + +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) + -- TODO: Remove this after beta has gone out + assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") + + RegisterName = RegisterName or "RegisterCallback" + UnregisterName = UnregisterName or "UnregisterCallback" + if UnregisterAllName==nil then -- false is used to indicate "don't want this method" + UnregisterAllName = "UnregisterAllCallbacks" + end + + -- we declare all objects and exported APIs inside this closure to quickly gain access + -- to e.g. function names, the "target" parameter, etc + + + -- Create the registry object + local events = setmetatable({}, meta) + local registry = { recurse=0, events=events } + + -- registry:Fire() - fires the given event/message into the registry + function registry:Fire(eventname, ...) + if not rawget(events, eventname) or not next(events[eventname]) then return end + local oldrecurse = registry.recurse + registry.recurse = oldrecurse + 1 + + Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) + + registry.recurse = oldrecurse + + if registry.insertQueue and oldrecurse==0 then + -- Something in one of our callbacks wanted to register more callbacks; they got queued + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func + -- fire OnUsed callback? + if first and registry.OnUsed then + registry.OnUsed(registry, target, eventname) + first = nil + end + end + end + registry.insertQueue = nil + end + end + + -- Registration of a callback, handles: + -- self["method"], leads to self["method"](self, ...) + -- self with function ref, leads to functionref(...) + -- "addonId" (instead of self) with function ref, leads to functionref(...) + -- all with an optional arg, which, if present, gets passed as first argument (after self if present) + target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) + if type(eventname) ~= "string" then + error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) + end + + method = method or eventname + + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + + if type(method) ~= "string" and type(method) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) + end + + local regfunc + + if type(method) == "string" then + -- self["method"] calling style + if type(self) ~= "table" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) + elseif self==target then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) + elseif type(self[method]) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) self[method](self,arg,...) end + else + regfunc = function(...) self[method](self,...) end + end + else + -- function ref with self=object or self="addonId" + if type(self)~="table" and type(self)~="string" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) method(arg,...) end + else + regfunc = method + end + end + + + if events[eventname][self] or registry.recurse<1 then + -- if registry.recurse<1 then + -- we're overwriting an existing entry, or not currently recursing. just set it. + events[eventname][self] = regfunc + -- fire OnUsed callback? + if registry.OnUsed and first then + registry.OnUsed(registry, target, eventname) + end + else + -- we're currently processing a callback in this registry, so delay the registration of this new entry! + -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency + registry.insertQueue = registry.insertQueue or setmetatable({},meta) + registry.insertQueue[eventname][self] = regfunc + end + end + + -- Unregister a callback + target[UnregisterName] = function(self, eventname) + if not self or self==target then + error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) + end + if type(eventname) ~= "string" then + error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) + end + if rawget(events, eventname) and events[eventname][self] then + events[eventname][self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(events[eventname]) then + registry.OnUnused(registry, target, eventname) + end + end + if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then + registry.insertQueue[eventname][self] = nil + end + end + + -- OPTIONAL: Unregister all callbacks for given selfs/addonIds + if UnregisterAllName then + target[UnregisterAllName] = function(...) + if select("#",...)<1 then + error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) + end + if select("#",...)==1 and ...==target then + error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) + end + + + for i=1,select("#",...) do + local self = select(i,...) + if registry.insertQueue then + for eventname, callbacks in pairs(registry.insertQueue) do + if callbacks[self] then + callbacks[self] = nil + end + end + end + for eventname, callbacks in pairs(events) do + if callbacks[self] then + callbacks[self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(callbacks) then + registry.OnUnused(registry, target, eventname) + end + end + end + end + end + end + + return registry +end + + +-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it +-- try to upgrade old implicit embeds since the system is selfcontained and +-- relies on closures to work. + diff --git a/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml new file mode 100644 index 0000000..876df83 --- /dev/null +++ b/libs/CallbackHandler-1.0/CallbackHandler-1.0.xml @@ -0,0 +1,4 @@ + +