annotate Modules/Crafting.lua @ 128:451d8a19edea

Ticket 33 - Implemented a persistent queue. This allows the crafting queue to be seen even if your tradeskills aren't open and allows you to mix all of your tradeskills into a single queue.
author Asa Ayers <Asa.Ayers@Gmail.com>
date Thu, 02 Sep 2010 23:59:09 -0700
parents e8d3c299542c
children 78c6d905c2e1 7f81764aa03a
rev   line source
Asa@63 1 local ItemAuditor = select(2, ...)
Asa@124 2 local Crafting = ItemAuditor:NewModule("Crafting", 'AceEvent-3.0')
Asa@59 3
Asa@86 4 local Utils = ItemAuditor:GetModule("Utils")
Asa@86 5
Asa@59 6 local AceGUI = LibStub("AceGUI-3.0")
Asa@59 7 local ScrollingTable = LibStub("ScrollingTable")
Asa@59 8
Asa@59 9 local validateMoney = ItemAuditor.validateMoney
Asa@59 10 local parseMoney = ItemAuditor.parseMoney
Asa@59 11
Asa@59 12 local realData = {}
Asa@128 13 local nameMap = nil
Asa@128 14 local shoppingList = nil
Asa@59 15
Asa@124 16 local vellumLevelMap = {
Asa@124 17 [38682] = 37602, -- Armor Vellum => Armor Vellum II
Asa@124 18 [37602] = 43145, -- Armor Vellum II => Armor Vellum III
Asa@124 19 [39349] = 39350, -- Weapon Vellum => Weapon Vellum II
Asa@124 20 [39350] = 43146, -- Weapon Vellum II => Weapon Vellum III
Asa@124 21 }
Asa@67 22
Asa@128 23 function Crafting:OnInitialize()
Asa@128 24 local allQueues = ItemAuditor.db.factionrealm.queue
Asa@128 25 if not allQueues[UnitName("player")] then
Asa@128 26 allQueues[UnitName("player")] = {}
Asa@128 27 end
Asa@128 28 realData = allQueues[UnitName("player")]
Asa@128 29
Asa@128 30 self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
Asa@128 31 end
Asa@128 32
Asa@128 33 local function getQueueLocation(name)
Asa@128 34 if not nameMap then
Asa@128 35 nameMap = {}
Asa@128 36 for key, data in pairs(realData) do
Asa@128 37 nameMap[data.skillName] = key
Asa@128 38 end
Asa@128 39 end
Asa@128 40 return nameMap[name]
Asa@128 41 end
Asa@128 42
Asa@128 43 --@debug@
Asa@128 44 Crafting.getQueueLocation = getQueueLocation
Asa@128 45 function Crafting.getNameMap()
Asa@128 46 return nameMap
Asa@128 47 end
Asa@128 48
Asa@128 49 function Crafting.getRealData()
Asa@128 50 return realData
Asa@128 51 end
Asa@128 52 --@end-debug@
Asa@128 53
Asa@128 54 function Crafting:UNIT_SPELLCAST_SUCCEEDED(event, unit, spell)
Asa@128 55 if unit == "player" and getQueueLocation(spell) then
Asa@128 56 local data = realData[getQueueLocation(spell)]
Asa@128 57 data.queue = data.queue - 1
Asa@128 58 ItemAuditor:RefreshCraftingTable()
Asa@128 59 end
Asa@128 60 end
Asa@128 61
Asa@68 62 local queueDestinations = {}
Asa@70 63 local displayCraftingDestinations = {}
Asa@68 64 function Crafting.RegisterQueueDestination(name, destination)
Asa@68 65 queueDestinations[name] = destination
Asa@70 66 displayCraftingDestinations[name] = name
Asa@70 67 end
Asa@70 68
Asa@70 69 function Crafting.UnRegisterQueueDestination(name)
Asa@70 70 queueDestinations[name] = nil
Asa@70 71 displayCraftingDestinations[name] = nil
Asa@70 72 end
Asa@70 73
Asa@70 74 function Crafting.GetQueueDestination()
Asa@70 75 local dest = ItemAuditor.db.profile.queue_destination
Asa@70 76 if dest and queueDestinations[dest] then
Asa@70 77 return queueDestinations[dest], dest
Asa@70 78 end
Asa@70 79 -- If there is none selected or the selected option has
Asa@70 80 -- dissapeared, choose the first one in the list
Asa@70 81 for name, func in pairs(queueDestinations) do
Asa@70 82 if dest then
Asa@70 83 ItemAuditor:Print("%s is no longer available as a queue destination. %s is the new default", dest, name)
Asa@70 84 end
Asa@70 85 ItemAuditor.db.profile.queue_destination = name
Asa@70 86 return func, name
Asa@70 87 end
Asa@70 88
Asa@70 89 error('Unable to determine queue destination.')
Asa@68 90 end
Asa@68 91
Asa@68 92 function ItemAuditor:GetCraftingThreshold()
Asa@101 93 return self.db.char.profitable_threshold
Asa@68 94 end
Asa@68 95
Asa@67 96 ItemAuditor.Options.args.crafting_options = {
Asa@70 97 name = "Crafting",
Asa@67 98 type = 'group',
Asa@67 99 args = {
Asa@70 100 queue_destination = {
Asa@70 101 type = "select",
Asa@70 102 name = "Queue Destination",
Asa@70 103 desc = "Select the addon who's queue you would like ItemAuditor to post to.",
Asa@70 104 values = displayCraftingDestinations,
Asa@70 105 get = function() return select(2, Crafting.GetQueueDestination()) end,
Asa@70 106 set = function(info, value) ItemAuditor.db.profile.queue_destination = value end,
Asa@72 107 order = 1,
Asa@70 108 },
Asa@72 109 deciders = {
Asa@72 110 type="header",
Asa@72 111 name="Crafting Deciders",
Asa@72 112 order = 10,
Asa@72 113 },
Asa@67 114 },
Asa@67 115 }
Asa@67 116
Asa@59 117 local function displayMoney(rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...)
Asa@59 118 if fShow == true then
Asa@59 119 local money = data[realrow][column]
Asa@59 120 if money then
Asa@59 121 cellFrame.text:SetText(ItemAuditor:FormatMoney(tonumber(money)))
Asa@59 122 else
Asa@59 123 cellFrame.text:SetText("")
Asa@59 124 end
Asa@59 125
Asa@59 126 end
Asa@59 127 end
Asa@59 128
Asa@59 129 local craftingCols = {
Asa@59 130 { name= "Item", width = 200, defaultsort = "desc",
Asa@59 131 ['DoCellUpdate'] = function(rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...)
Asa@59 132 if fShow == true then
Asa@59 133 local data = realData[realrow]
Asa@59 134 cellFrame.text:SetText(data.link)
Asa@59 135 end
Asa@59 136 end,
Asa@59 137 },
Asa@59 138 { name= "Cost Each", width = 100, align = "RIGHT",
Asa@59 139 ['DoCellUpdate'] = displayMoney,
Asa@59 140 },
Asa@59 141 { name= "Est Sale Each", width = 100, align = "RIGHT",
Asa@59 142 ['DoCellUpdate'] = displayMoney,
Asa@59 143 },
Asa@99 144 { name= "Decided By", width = 125, align = "RIGHT",
Asa@59 145
Asa@59 146 },
Asa@59 147 { name= "craft", width = 50, align = "RIGHT",
Asa@59 148
Asa@59 149 },
Asa@99 150 { name= "Have Mats", width = 60, align = "RIGHT",
Asa@99 151
Asa@99 152 },
Asa@123 153 { name= "Profit Each", width = 100, align = "RIGHT",
Asa@59 154 ['DoCellUpdate'] = displayMoney,
Asa@59 155 },
Asa@59 156 }
Asa@59 157
Asa@68 158 function Crafting.ExportToSkillet(data)
Asa@68 159 local skillString = select(3, string.find(data.recipeLink, "^|%x+|H(.+)|h%[.+%]"))
Asa@68 160 local _, skillId = strsplit(":", skillString)
Asa@68 161
Asa@68 162 ItemAuditor:AddToQueue(skillId,tradeSkillIndex, data.queue)
Asa@68 163 end
Asa@68 164
Asa@68 165 Crafting.RegisterQueueDestination('Skillet', Crafting.ExportToSkillet)
Asa@68 166
Asa@68 167
Asa@68 168
Asa@68 169 function Crafting.Export(destination)
Asa@68 170 if type(destination) == 'function' then
Asa@68 171 -- do nothing
Asa@68 172 elseif destination == nil then
Asa@70 173 destination = Crafting.GetQueueDestination()
Asa@68 174 elseif type(destination) == 'string' then
Asa@68 175 destination = queueDestinations[destination]
Asa@68 176 else
Asa@68 177 error('destination must be a function or a string')
Asa@68 178 end
Asa@68 179
Asa@59 180 local index = 1
Asa@59 181 local data = ItemAuditor:GetCraftingRow(index)
Asa@59 182 while data do
Asa@68 183 if data.queue > 0 then
Asa@68 184 destination(data)
Asa@68 185 end
Asa@61 186 index = index + 1
Asa@61 187 data = ItemAuditor:GetCraftingRow(index)
Asa@59 188
Asa@59 189 end
Asa@59 190 end
Asa@59 191
Asa@74 192 -- ItemAuditor:GetModule('Crafting').filter_queued = false
Asa@99 193 Crafting.filter_have_mats = false
Asa@99 194 Crafting.filter_show_all = false
Asa@74 195 local function tableFilter(self, row, ...)
Asa@128 196 if Crafting.nameFilter then
Asa@128 197 return string.find(row[1], Crafting.nameFilter) ~= nil
Asa@128 198 end
Asa@128 199
Asa@99 200 if Crafting.filter_show_all then
Asa@99 201 return true
Asa@99 202 end
Asa@99 203
Asa@74 204 -- column 5 is how many should be crafted
Asa@126 205 if Crafting.filter_have_mats and row[6] == 0 then
Asa@99 206 return false
Asa@99 207 end
Asa@99 208 if strfind(row[4], 'VETO: .*') or row[5] == 0 then
Asa@74 209 return false
Asa@74 210 end
Asa@74 211 return true
Asa@74 212 end
Asa@74 213
Asa@59 214 local craftingContent = false
Asa@59 215 local craftingTable = false
Asa@60 216 local btnProcess = false
Asa@59 217 local function ShowCrafting(container)
Asa@59 218 if craftingContent == false then
Asa@59 219 local window = container.frame
Asa@59 220 craftingContent = CreateFrame("Frame",nil,window)
Asa@59 221 craftingContent:SetBackdropColor(0, 0, 1, 0.5)
Asa@59 222 craftingContent:SetBackdropBorderColor(1, 0, 0, 1)
Asa@59 223
Asa@59 224 craftingContent:SetPoint("TOPLEFT", window, 10, -50)
Asa@59 225 craftingContent:SetPoint("BOTTOMRIGHT",window, -10, 10)
Asa@59 226
Asa@59 227 craftingTable = ScrollingTable:CreateST(craftingCols, 22, nil, nil, craftingContent )
Asa@59 228
Asa@59 229 IAcc = craftingContent
Asa@59 230 IAccWindow = window
Asa@59 231 craftingTable.frame:SetPoint("TOPLEFT",craftingContent, 0,0)
Asa@59 232 craftingTable.frame:SetPoint("BOTTOMRIGHT", craftingContent, 0, 30)
Asa@59 233
Asa@59 234 craftingTable:RegisterEvents({
Asa@59 235 ["OnEnter"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, scrollingTable, ...)
Asa@59 236 if realrow then
Asa@128 237 if column == 1 then
Asa@128 238 local data = realData[realrow]
Asa@128 239
Asa@128 240 GameTooltip:SetOwner(rowFrame, "ANCHOR_CURSOR")
Asa@128 241 GameTooltip:SetHyperlink(data.link)
Asa@128 242 GameTooltip:Show()
Asa@128 243 elseif column == 2 then
Asa@128 244 local data = realData[realrow]
Asa@128 245 GameTooltip:SetOwner(rowFrame, "ANCHOR_CURSOR")
Asa@128 246 GameTooltip:SetText(format('Create %sx%s', data.link, data.queue))
Asa@128 247 for i, reagent in pairs(data.reagents) do
Asa@128 248 GameTooltip:AddDoubleLine("\124cffffffff"..reagent.link, format("\124cffffffff%s/%s", reagent.count-reagent.need, reagent.count))
Asa@128 249 --[[
Asa@128 250 reagents[reagentId] = {
Asa@128 251 link = reagentLink,
Asa@128 252 itemID = vellumID,
Asa@128 253 name = reagentName,
Asa@128 254 count = 1,
Asa@128 255 price = self:GetReagentCost(reagentLink, 1),
Asa@128 256 need = 0, -- This will get populated after the decisions have been made. it can't
Asa@128 257 -- be done before that because highest profit items get priority on materials.
Asa@128 258 }]]
Asa@128 259 end
Asa@128 260 GameTooltip:Show()
Asa@128 261 end
Asa@59 262 end
Asa@59 263 end,
Asa@59 264 ["OnLeave"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, scrollingTable, ...)
Asa@59 265 GameTooltip:Hide()
Asa@59 266 end,
Asa@59 267 });
Asa@59 268
Asa@99 269 local craftingView = CreateFrame("Button", nil, craftingContent, "UIPanelButtonTemplate")
Asa@99 270 craftingView:SetText("View")
Asa@99 271 craftingView:SetSize(50, 25)
Asa@99 272 craftingView:SetPoint("BOTTOMLEFT", craftingContent, 0, 0)
Asa@99 273
Asa@99 274 local menu = {
Asa@99 275 { text = "View", isTitle = true},
Asa@99 276 { text = "To be crafted", func = function()
Asa@99 277 Crafting.filter_have_mats = false
Asa@99 278 Crafting.filter_show_all = false
Asa@99 279 ItemAuditor:RefreshCraftingTable()
Asa@99 280 end },
Asa@99 281 { text = "Have Mats", func = function()
Asa@99 282 Crafting.filter_have_mats = true
Asa@99 283 Crafting.filter_show_all = false
Asa@99 284 ItemAuditor:RefreshCraftingTable()
Asa@99 285 end },
Asa@99 286 { text = "All", func = function()
Asa@99 287 Crafting.filter_have_mats = false
Asa@99 288 Crafting.filter_show_all = true
Asa@99 289 ItemAuditor:RefreshCraftingTable()
Asa@99 290 end },
Asa@99 291 }
Asa@99 292 local menuFrame = CreateFrame("Frame", "ExampleMenuFrame", UIParent, "UIDropDownMenuTemplate")
Asa@99 293 craftingView:SetScript("OnClick", function (self, button, down)
Asa@99 294 EasyMenu(menu, menuFrame, "cursor", 0 , 0, "MENU");
Asa@99 295 end)
Asa@99 296
Asa@99 297
Asa@59 298 btnProcess = CreateFrame("Button", nil, craftingContent, "UIPanelButtonTemplate")
Asa@59 299 btnProcess:SetText("Process")
Asa@59 300 btnProcess:SetSize(100, 25)
Asa@59 301 btnProcess:SetPoint("BOTTOMRIGHT", craftingContent, 0, 0)
Asa@59 302 btnProcess:RegisterForClicks("LeftButtonUp");
Asa@59 303
Asa@60 304 local function UpdateProcessTooltip(btn)
Asa@59 305 local data = ItemAuditor:GetCraftingRow(1)
Asa@59 306 if data then
Asa@59 307 GameTooltip:SetOwner(this, "ANCHOR_CURSOR")
Asa@59 308 GameTooltip:SetText(format('Create %sx%s', data.link, data.queue))
Asa@59 309 GameTooltip:Show()
Asa@59 310 end
Asa@60 311 end
Asa@124 312
Asa@124 313 --[[
Asa@124 314 When enchanting UseContainerItem seems to be protected, so the enchants
Asa@124 315 have to be done one at a time.
Asa@124 316 ]]
Asa@124 317 local function useVellum(vellumID, idealVellum)
Asa@124 318 for bagID = 0, NUM_BAG_SLOTS do
Asa@124 319 for slotID = 0, GetContainerNumSlots(bagID) do
Asa@124 320 local link = GetContainerItemLink(bagID, slotID)
Asa@124 321 local id = Utils.GetItemID(link);
Asa@124 322 if id == vellumID then
Asa@124 323 if idealVellum then
Asa@124 324 ItemAuditor:Print("Using %s instead of %s.",
Asa@124 325 select(2, GetItemInfo(vellumID)),
Asa@124 326 select(2, GetItemInfo(idealVellum))
Asa@124 327 )
Asa@124 328 end
Asa@124 329 UseContainerItem(bagID, slotID)
Asa@124 330 return
Asa@124 331 end
Asa@124 332 end
Asa@124 333 end
Asa@124 334 if vellumLevelMap[vellumID] then
Asa@124 335 return useVellum(vellumLevelMap[vellumID], idealVellum or vellumID)
Asa@124 336 end
Asa@124 337 end
Asa@124 338
Asa@60 339 btnProcess:SetScript("OnClick", function (self, button, down)
Asa@60 340 local data = ItemAuditor:GetCraftingRow(1)
Asa@60 341 if data then
Asa@128 342 -- This will make sure the correct tradeskill window is open.
Asa@128 343 local tradeskillName = GetTradeSkillLine()
Asa@128 344 if data.tradeskillName ~= tradeskillName then
Asa@128 345 CastSpellByName(data.tradeskillName)
Asa@128 346 end
Asa@128 347
Asa@124 348 local queue = data.queue
Asa@124 349 local vellumID = nil
Asa@124 350 _, _, _, _, altVerb = GetTradeSkillInfo(data.tradeSkillIndex)
Asa@124 351 if altVerb == 'Enchant' and LSW.scrollData[data.recipeID] ~= nil then
Asa@124 352 vellumID = LSW.scrollData[data.recipeID]["vellumID"]
Asa@124 353 queue = 1
Asa@124 354 end
Asa@124 355 ItemAuditor:Print('Crafting %sx%s', data.link, queue)
Asa@124 356 DoTradeSkill(data.tradeSkillIndex, queue)
Asa@124 357 if vellumID then
Asa@124 358 useVellum(vellumID)
Asa@124 359 end
Asa@124 360
Asa@60 361 UpdateProcessTooltip()
Asa@60 362 end
Asa@59 363 end)
Asa@59 364
Asa@60 365 btnProcess:SetScript("OnEnter", UpdateProcessTooltip)
Asa@60 366
Asa@59 367 btnProcess:SetScript("OnLeave", function()
Asa@59 368 GameTooltip:Hide()
Asa@59 369 end)
Asa@59 370
Asa@59 371 btnSkillet = CreateFrame("Button", nil, craftingContent, "UIPanelButtonTemplate")
Asa@99 372
Asa@59 373 btnSkillet:SetSize(125, 25)
Asa@59 374 btnSkillet:SetPoint("BOTTOMRIGHT", btnProcess, 'BOTTOMLEFT', 0, 0)
Asa@59 375 btnSkillet:RegisterForClicks("LeftButtonUp");
Asa@59 376 btnSkillet:SetScript("OnClick", function (self, button, down)
Asa@70 377 Crafting.Export()
Asa@59 378 end)
Asa@59 379
Asa@59 380 end
Asa@70 381 local destination = select(2, Crafting.GetQueueDestination())
Asa@70 382 btnSkillet:SetText("Export to "..destination)
Asa@70 383
Asa@59 384 craftingContent:Show()
Asa@59 385
Asa@59 386 if container.parent then
Asa@59 387 local width = 80
Asa@59 388 for i, data in pairs(craftingCols) do
Asa@59 389 width = width + data.width
Asa@59 390 end
Asa@59 391 container.parent:SetWidth(width);
Asa@59 392 end
Asa@59 393
Asa@59 394 ItemAuditor:RegisterEvent("TRADE_SKILL_SHOW", function()
Asa@59 395 if craftingContent and craftingContent:IsVisible() then
Asa@59 396 ItemAuditor:UpdateCraftingTable()
Asa@59 397 end
Asa@59 398 end)
Asa@59 399 ItemAuditor:UpdateCraftingTable()
Asa@59 400
Asa@59 401 return craftingContent
Asa@59 402 end
Asa@59 403
Asa@59 404 ItemAuditor:RegisterTab('Crafting', 'tab_crafting', ShowCrafting)
Asa@59 405 function ItemAuditor:DisplayCrafting()
Asa@59 406 self:CreateFrame('tab_crafting')
Asa@59 407 end
Asa@59 408
Asa@59 409 local craftingDeciders = {}
Asa@59 410
Asa@72 411 function Crafting.RegisterCraftingDecider(name, decider, options)
Asa@59 412 craftingDeciders[name] = decider
Asa@72 413
Asa@72 414 ItemAuditor.Options.args.crafting_options.args['chk'..name] = {
Asa@72 415 type = "toggle",
Asa@72 416 name = "Enable "..name,
Asa@72 417 get = function() return not ItemAuditor.db.profile.disabled_deciders[name] end,
Asa@72 418 set = function(info, value) ItemAuditor.db.profile.disabled_deciders[name] = not value end,
Asa@72 419 order = 11,
Asa@72 420 }
Asa@72 421
Asa@72 422 if options then
Asa@72 423 ItemAuditor.Options.args.crafting_options.args['decider_'..name] = {
Asa@72 424 handler = {},
Asa@72 425 name = name,
Asa@72 426 type = 'group',
Asa@72 427 args = options,
Asa@72 428 }
Asa@72 429 end
Asa@59 430 end
Asa@59 431
Asa@59 432 local lastWinnder = ""
Asa@59 433 local function Decide(data)
Asa@59 434 local newDecision = 0
Asa@74 435 local reason = ""
Asa@59 436 for name, decider in pairs(craftingDeciders) do
Asa@72 437 if not ItemAuditor.db.profile.disabled_deciders[name] and name ~= lastWinner then
Asa@74 438 newDecision, reason = decider(data)
Asa@74 439
Asa@59 440 if newDecision > data.queue then
Asa@59 441 data.queue = newDecision
Asa@74 442 lastWinner = (reason or name)
Asa@59 443 return Decide(data)
Asa@59 444 elseif newDecision < 0 then
Asa@59 445 lastWinner = ""
Asa@74 446 return 'VETO: '..(reason or name), -1
Asa@59 447 end
Asa@59 448 end
Asa@59 449 end
Asa@59 450
Asa@59 451 winner = lastWinner
Asa@59 452 lastWinner = ""
Asa@59 453
Asa@77 454 data.queue = ceil(data.queue / GetTradeSkillNumMade(data.tradeSkillIndex))
Asa@77 455
Asa@59 456 return winner, data.queue
Asa@59 457 end
Asa@59 458
Asa@59 459 local function isProfitable(data)
Asa@59 460 if data.profit > 0 and data.profit > ItemAuditor:GetCraftingThreshold() then
Asa@59 461 return 1
Asa@59 462 end
Asa@99 463 return -1, 'Not Profitable'
Asa@59 464 end
Asa@72 465
Asa@101 466 local isProfitableOptions = {
Asa@101 467 profitable_threshold = {
Asa@101 468 type = "input",
Asa@101 469 name = "Crafting Threshold",
Asa@101 470 desc = "Don't create items that will make less than this amount of profit",
Asa@101 471 get = function() return
Asa@101 472 Utils.FormatMoney(ItemAuditor:GetCraftingThreshold(), '', true)
Asa@101 473 end,
Asa@101 474 validate = function(info, value)
Asa@101 475 if not Utils.validateMoney(value) then
Asa@101 476 return "Invalid money format"
Asa@101 477 end
Asa@101 478 return true
Asa@101 479 end,
Asa@101 480 set = function(info, value)
Asa@101 481 ItemAuditor.db.char.profitable_threshold = Utils.parseMoney(value)
Asa@101 482 end,
Asa@101 483 usage = "###g ##s ##c",
Asa@101 484 order = 0,
Asa@101 485 },
Asa@101 486 }
Asa@101 487
Asa@101 488 Crafting.RegisterCraftingDecider('Is Profitable', isProfitable, isProfitableOptions)
Asa@59 489
Asa@128 490 function Crafting.ClearProfession(tradeskillName)
Asa@128 491 -- This will initialize nameMap if it isn't already.
Asa@128 492 getQueueLocation('')
Asa@74 493
Asa@128 494 for key = #(realData), 1, -1 do
Asa@128 495 if realData[key].tradeskillName == tradeskillName then
Asa@128 496 nameMap[realData[key].tradeskillName] = nil
Asa@128 497 tremove(realData, key)
Asa@128 498 end
Asa@128 499 end
Asa@128 500 end
Asa@59 501
Asa@59 502 local tableData = {}
Asa@59 503 function ItemAuditor:UpdateCraftingTable()
Asa@59 504 if LSW == nil then
Asa@59 505 self:Print("This feature requires LilSparky's Workshop.")
Asa@59 506 return
Asa@59 507 end
Asa@59 508 if GetAuctionBuyout ~= nil then
Asa@59 509 elseif AucAdvanced and AucAdvanced.Version then
Asa@59 510 else
Asa@59 511 self:Print("This feature requires Auctionator, Auctioneer, AuctionLite, or AuctionMaster.")
Asa@59 512 return
Asa@59 513 end
Asa@128 514 local tradeskillName = GetTradeSkillLine()
Asa@128 515 Crafting.ClearProfession(tradeskillName)
Asa@128 516 shoppingList = nil
Asa@128 517
Asa@59 518 wipe(tableData)
Asa@59 519
Asa@59 520 local profitableItems = {}
Asa@59 521 local profitableIndex = 1
Asa@59 522 local numChecked = 0
Asa@128 523 local row = #(realData)+1
Asa@128 524 local numTradeSkills = GetNumTradeSkills()
Asa@128 525 if tradeskillName == 'UNKNOWN' then
Asa@128 526 numTradeSkills = 0
Asa@128 527 end
Asa@59 528
Asa@128 529 for i = 1, numTradeSkills do
Asa@59 530 local itemLink = GetTradeSkillItemLink(i)
Asa@86 531 local itemId = Utils.GetItemID(itemLink)
Asa@124 532 local vellumID = nil
Asa@128 533
Asa@128 534 local spellName = itemName
Asa@59 535
Asa@59 536 --Figure out if its an enchant or not
Asa@59 537 _, _, _, _, altVerb = GetTradeSkillInfo(i)
Asa@59 538 if LSW.scrollData[itemId] ~= nil and altVerb == 'Enchant' then
Asa@59 539 -- Ask LSW for the correct scroll
Asa@124 540 local sd = LSW.scrollData[itemId]
Asa@124 541 itemId = sd.scrollID
Asa@124 542 vellumID = sd.vellumID
Asa@59 543 end
Asa@59 544
Asa@59 545 local recipeLink = GetTradeSkillRecipeLink(i)
Asa@59 546 local stackSize = 1
Asa@59 547 if recipeLink ~= nil and itemId ~= nil then
Asa@59 548 local skillName, skillType, numAvailable, isExpanded, altVerb = GetTradeSkillInfo(i)
Asa@59 549 local itemName, itemLink= GetItemInfo(itemId)
Asa@74 550
Asa@74 551 -- This check has to be here for things like Inscription Research that don't produce an item.
Asa@74 552 if itemLink then
Asa@82 553 local count = ItemAuditor:GetItemCount(itemId)
Asa@74 554 local reagents = {}
Asa@74 555 local totalCost = 0
Asa@74 556 for reagentId = 1, GetTradeSkillNumReagents(i) do
Asa@74 557 local reagentName, _, reagentCount = GetTradeSkillReagentInfo(i, reagentId);
Asa@74 558 local reagentLink = GetTradeSkillReagentItemLink(i, reagentId)
Asa@102 559 local reagentTotalCost = self:GetReagentCost(reagentLink, reagentCount)
Asa@74 560
Asa@74 561 reagents[reagentId] = {
Asa@93 562 link = reagentLink,
Asa@124 563 itemID = Utils.GetItemID(reagentLink),
Asa@74 564 name = reagentName,
Asa@74 565 count = reagentCount,
Asa@102 566 price = reagentTotalCost / reagentCount,
Asa@94 567 need = 0, -- This will get populated after the decisions have been made. it can't
Asa@94 568 -- be done before that because highest profit items get priority on materials.
Asa@74 569 }
Asa@102 570 totalCost = totalCost + reagentTotalCost
Asa@74 571 end
Asa@124 572 if vellumID then
Asa@124 573 reagentId = GetTradeSkillNumReagents(i) + 1
Asa@124 574 local reagentName, reagentLink = GetItemInfo(vellumID)
Asa@124 575 reagents[reagentId] = {
Asa@124 576 link = reagentLink,
Asa@124 577 itemID = vellumID,
Asa@124 578 name = reagentName,
Asa@124 579 count = 1,
Asa@124 580 price = self:GetReagentCost(reagentLink, 1),
Asa@124 581 need = 0, -- This will get populated after the decisions have been made. it can't
Asa@124 582 -- be done before that because highest profit items get priority on materials.
Asa@124 583 }
Asa@124 584 totalCost = totalCost + self:GetReagentCost(reagentLink, 1)
Asa@124 585 end
Asa@116 586
Asa@116 587 local price = (self:GetAuctionPrice(itemLink) or 0)
Asa@116 588 totalCost = totalCost + (price * ItemAuditor:GetAHCut())
Asa@74 589 local data = {
Asa@74 590 recipeLink = recipeLink,
Asa@95 591 recipeID = Utils.GetItemID(recipeLink),
Asa@74 592 link = itemLink,
Asa@74 593 name = itemName,
Asa@128 594 skillName = skillName,
Asa@74 595 count = count,
Asa@110 596 price = price,
Asa@74 597 cost = totalCost,
Asa@110 598 profit = price - totalCost,
Asa@74 599 reagents = reagents,
Asa@74 600 count = count,
Asa@74 601 tradeSkillIndex = i,
Asa@74 602 queue = 0,
Asa@74 603 winner = "",
Asa@128 604 tradeskillName = tradeskillName,
Asa@74 605 }
Asa@59 606
Asa@74 607 data.winner, data.queue = Decide(data)
Asa@99 608 --[[
Asa@99 609 If it wasn't vetoed we need to reduce the number by how many are owned
Asa@99 610 but this should not go below 0
Asa@99 611 ]]
Asa@99 612 if data.queue > 0 then
Asa@99 613 data.queue = max(0, data.queue - count)
Asa@99 614 end
Asa@74 615
Asa@74 616 -- If a tradeskill makes 5 at a time and something asks for 9, we should only
Asa@74 617 -- craft twice to get 10.
Asa@74 618 data.queue = ceil(data.queue / GetTradeSkillNumMade(i))
Asa@128 619
Asa@74 620 realData[row] = data
Asa@128 621 nameMap[skillName] = row
Asa@74 622 row = row + 1
Asa@59 623 end
Asa@59 624 end
Asa@59 625 end
Asa@99 626 table.sort(realData, function(a, b) return a.profit*max(1, a.queue) > b.profit*max(1, b.queue) end)
Asa@94 627
Asa@94 628 local numOwned = {}
Asa@124 629
Asa@94 630 for key, data in pairs(realData) do
Asa@126 631 data.haveMaterials = data.queue
Asa@94 632 for id, reagent in pairs(data.reagents) do
Asa@126 633 local needEach = reagent.count
Asa@122 634 reagent.count = reagent.count * data.queue
Asa@122 635
Asa@94 636 if not numOwned[reagent.link] then
Asa@94 637 numOwned[reagent.link] = ItemAuditor:GetItemCount(ItemAuditor:GetIDFromLink(reagent.link))
Asa@94 638 end
Asa@126 639 data.haveMaterials = min(data.haveMaterials, floor(numOwned[reagent.link] / needEach))
Asa@94 640 numOwned[reagent.link] = numOwned[reagent.link] - reagent.count
Asa@94 641
Asa@124 642 -- Vellums count in cost, but not against whether or not you have the mats.
Asa@124 643 -- I chose to do it this way because you can use a higher level of vellum
Asa@124 644 -- and I'm not sure the best way to determine cost and materials in that situation.
Asa@124 645 if numOwned[reagent.link] < 0 and not vellumLevelMap[reagent.itemID] then
Asa@94 646 reagent.need = min(reagent.count, abs(numOwned[reagent.link]))
Asa@94 647 end
Asa@94 648 end
Asa@126 649 data.haveMaterials = max(0, data.haveMaterials)
Asa@94 650 end
Asa@94 651
Asa@68 652 if craftingTable then
Asa@68 653 craftingTable:SetFilter(tableFilter)
Asa@68 654 self:RefreshCraftingTable()
Asa@68 655 end
Asa@60 656 end
Asa@60 657
Asa@128 658
Asa@128 659 local numOwned = {}
Asa@128 660
Asa@128 661 local function BuildShoppingList(character, queue)
Asa@128 662 for key, data in pairs(queue) do
Asa@128 663 if data.queue > 0 then
Asa@128 664 for id, reagent in pairs(data.reagents) do
Asa@128 665 if not numOwned[reagent.link] then
Asa@128 666 numOwned[reagent.link] = ItemAuditor:GetItemCount(Utils.GetItemID(reagent.link))
Asa@128 667 end
Asa@128 668 numOwned[reagent.link] = numOwned[reagent.link] - reagent.count
Asa@128 669
Asa@128 670 shoppingList[reagent.itemID] = shoppingList[reagent.itemID] or {
Asa@128 671 total = 0,
Asa@128 672 need = 0,
Asa@128 673 characters = {}
Asa@128 674 }
Asa@128 675 local slItem = shoppingList[reagent.itemID]
Asa@128 676
Asa@128 677
Asa@128 678 if numOwned[reagent.link] < 0 and not vellumLevelMap[reagent.itemID] then
Asa@128 679 reagent.need = min(reagent.count, abs(numOwned[reagent.link]))
Asa@128 680 elseif numOwned[reagent.link] >= 0 then
Asa@128 681 reagent.need = 0
Asa@128 682 end
Asa@128 683 shoppingList[reagent.itemID].total = shoppingList[reagent.itemID].total + reagent.count
Asa@128 684 shoppingList[reagent.itemID].need = shoppingList[reagent.itemID].need + reagent.need
Asa@128 685
Asa@128 686 slItem.characters[UnitName("player")] = slItem.characters[UnitName("player")] or {}
Asa@128 687 slItem.characters[UnitName("player")][data.recipeLink] = {
Asa@128 688 count = reagent.count,
Asa@128 689 need = reagent.need,
Asa@128 690 }
Asa@128 691 end
Asa@128 692 end
Asa@128 693 end
Asa@128 694 end
Asa@128 695
Asa@128 696 function Crafting.GetShoppingList(itemID)
Asa@128 697 if not shoppingList then
Asa@128 698 shoppingList = {}
Asa@128 699 wipe(numOwned)
Asa@128 700
Asa@128 701 -- This is done here instead of in the loop to make sure the current
Asa@128 702 -- character gets the first pick of materials.
Asa@128 703 local me = UnitName("player")
Asa@128 704 BuildShoppingList(me, realData)
Asa@128 705
Asa@128 706 for alt, queue in pairs(ItemAuditor.db.factionrealm.queue) do
Asa@128 707 if alt ~= me then
Asa@128 708 BuildShoppingList(alt, queue)
Asa@128 709 end
Asa@128 710 end
Asa@128 711 elseif shoppingList[itemID] then
Asa@128 712 local data = shoppingList[itemID]
Asa@128 713 if data.need ~= max(0, data.total - ItemAuditor:GetItemCount(itemID)) then
Asa@128 714 shoppingList = nil
Asa@128 715 -- I'm rebuilding the list instead of just the item because
Asa@128 716 -- it will be eaiser than tracking down the queued recipes that
Asa@128 717 -- need to change. If this becomes a problem, I may change it.
Asa@128 718 return Crafting.GetShoppingList(itemID)
Asa@128 719 end
Asa@128 720 end
Asa@128 721
Asa@128 722 return shoppingList[itemID]
Asa@128 723 end
Asa@128 724
Asa@60 725 function ItemAuditor:RefreshCraftingTable()
Asa@128 726 tableData = {}
Asa@128 727 nameMap = {}
Asa@59 728 for key, data in pairs(realData) do
Asa@128 729 nameMap[data.name] = key
Asa@128 730
Asa@59 731 tableData[key] = {
Asa@59 732 data.name,
Asa@59 733 data.cost,
Asa@59 734 data.price,
Asa@59 735 data.winner,
Asa@99 736 abs(data.queue),
Asa@126 737 data.haveMaterials,
Asa@123 738 data.profit,
Asa@59 739 }
Asa@59 740 end
Asa@60 741 craftingTable:SetData(tableData, true)
Asa@59 742
Asa@60 743 if self:GetCraftingRow(1) then
Asa@60 744 btnProcess:Enable()
Asa@60 745 else
Asa@60 746 btnProcess:Disable()
Asa@60 747 end
Asa@59 748 end
Asa@59 749
Asa@59 750 function ItemAuditor:GetCraftingRow(row)
Asa@59 751 if craftingTable then
Asa@59 752 for _, index in pairs(craftingTable.sorttable) do
Asa@59 753 local tableRow = tableData[index]
Asa@59 754 if tableFilter(nil, tableRow) then
Asa@59 755 row = row - 1
Asa@59 756 if row == 0 then
Asa@59 757 return realData[index]
Asa@59 758 end
Asa@59 759 end
Asa@59 760 end
Asa@59 761 elseif realData then
Asa@59 762 return realData[row]
Asa@59 763 end
Asa@59 764 return nil
Asa@59 765 end
Asa@67 766 ItemAuditor.Options.args.crafting = {
Asa@67 767 type = "execute",
Asa@67 768 name = "crafting",
Asa@67 769 desc = "This opens a window to configure a crafting queue.",
Asa@67 770 func = "DisplayCrafting",
Asa@67 771 guiHidden = false,
Asa@67 772 }