annotate Modules/Crafting.lua @ 74:71de6e86a1a4 release 2010-07-29

Fixed a bug where tradeskill that don't produce items, like Inscription Research, cause IA to crash. Added some API enhancements. Added ArkInventory as an optional dependency so it will be loaded before IA.
author Asa Ayers <Asa.Ayers@Gmail.com>
date Thu, 29 Jul 2010 22:33:50 -0700
parents 8e9beb8a0330
children a8fc802b42ba
rev   line source
Asa@63 1 local ItemAuditor = select(2, ...)
Asa@63 2 local Crafting = ItemAuditor:NewModule("Crafting")
Asa@59 3
Asa@59 4 local AceGUI = LibStub("AceGUI-3.0")
Asa@59 5 local ScrollingTable = LibStub("ScrollingTable")
Asa@59 6
Asa@59 7 local validateMoney = ItemAuditor.validateMoney
Asa@59 8 local parseMoney = ItemAuditor.parseMoney
Asa@59 9
Asa@59 10 local realData = {}
Asa@59 11
Asa@67 12
Asa@68 13 local queueDestinations = {}
Asa@70 14 local displayCraftingDestinations = {}
Asa@68 15 function Crafting.RegisterQueueDestination(name, destination)
Asa@68 16 queueDestinations[name] = destination
Asa@70 17 displayCraftingDestinations[name] = name
Asa@70 18 end
Asa@70 19
Asa@70 20 function Crafting.UnRegisterQueueDestination(name)
Asa@70 21 queueDestinations[name] = nil
Asa@70 22 displayCraftingDestinations[name] = nil
Asa@70 23 end
Asa@70 24
Asa@70 25 function Crafting.GetQueueDestination()
Asa@70 26 local dest = ItemAuditor.db.profile.queue_destination
Asa@70 27 if dest and queueDestinations[dest] then
Asa@70 28 return queueDestinations[dest], dest
Asa@70 29 end
Asa@70 30 -- If there is none selected or the selected option has
Asa@70 31 -- dissapeared, choose the first one in the list
Asa@70 32 for name, func in pairs(queueDestinations) do
Asa@70 33 if dest then
Asa@70 34 ItemAuditor:Print("%s is no longer available as a queue destination. %s is the new default", dest, name)
Asa@70 35 end
Asa@70 36 ItemAuditor.db.profile.queue_destination = name
Asa@70 37 return func, name
Asa@70 38 end
Asa@70 39
Asa@70 40 error('Unable to determine queue destination.')
Asa@68 41 end
Asa@68 42
Asa@67 43 -- TODO: Convert this to a text field.
Asa@67 44 local craftingThresholds = {5000, 10000, 50000}
Asa@67 45 local craftingThresholdsDisplay = {}
Asa@67 46
Asa@67 47 for key, value in pairs(craftingThresholds) do
Asa@67 48 craftingThresholdsDisplay[key] = ItemAuditor:FormatMoney(value, '', true)
Asa@67 49 -- craftingThresholdsDisplay[key] = value
Asa@67 50 end
Asa@67 51
Asa@68 52 function ItemAuditor:GetCraftingThreshold()
Asa@68 53 local key = ItemAuditor.db.char.crafting_threshold
Asa@68 54 return craftingThresholds[key]
Asa@68 55 end
Asa@68 56
Asa@67 57 ItemAuditor.Options.args.crafting_options = {
Asa@70 58 name = "Crafting",
Asa@67 59 type = 'group',
Asa@67 60 args = {
Asa@67 61 crafting_threshold = {
Asa@67 62 type = "select",
Asa@67 63 name = "Crafting Threshold",
Asa@67 64 desc = "Don't create items that will make less than this amount of profit",
Asa@67 65 values = craftingThresholdsDisplay,
Asa@67 66 get = function() return ItemAuditor.db.char.crafting_threshold end,
Asa@67 67 set = function(info, value) ItemAuditor.db.char.crafting_threshold = value end,
Asa@72 68 order = 0,
Asa@67 69 },
Asa@70 70 queue_destination = {
Asa@70 71 type = "select",
Asa@70 72 name = "Queue Destination",
Asa@70 73 desc = "Select the addon who's queue you would like ItemAuditor to post to.",
Asa@70 74 values = displayCraftingDestinations,
Asa@70 75 get = function() return select(2, Crafting.GetQueueDestination()) end,
Asa@70 76 set = function(info, value) ItemAuditor.db.profile.queue_destination = value end,
Asa@72 77 order = 1,
Asa@70 78 },
Asa@72 79 deciders = {
Asa@72 80 type="header",
Asa@72 81 name="Crafting Deciders",
Asa@72 82 order = 10,
Asa@72 83 },
Asa@72 84
Asa@67 85 },
Asa@67 86 }
Asa@67 87
Asa@59 88 local function displayMoney(rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...)
Asa@59 89 if fShow == true then
Asa@59 90 local money = data[realrow][column]
Asa@59 91 if money then
Asa@59 92 cellFrame.text:SetText(ItemAuditor:FormatMoney(tonumber(money)))
Asa@59 93 else
Asa@59 94 cellFrame.text:SetText("")
Asa@59 95 end
Asa@59 96
Asa@59 97 end
Asa@59 98 end
Asa@59 99
Asa@59 100 local craftingCols = {
Asa@59 101 { name= "Item", width = 200, defaultsort = "desc",
Asa@59 102 ['DoCellUpdate'] = function(rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...)
Asa@59 103 if fShow == true then
Asa@59 104 local data = realData[realrow]
Asa@59 105 cellFrame.text:SetText(data.link)
Asa@59 106 end
Asa@59 107 end,
Asa@59 108 },
Asa@59 109 { name= "Cost Each", width = 100, align = "RIGHT",
Asa@59 110 ['DoCellUpdate'] = displayMoney,
Asa@59 111 },
Asa@59 112 { name= "Est Sale Each", width = 100, align = "RIGHT",
Asa@59 113 ['DoCellUpdate'] = displayMoney,
Asa@59 114 },
Asa@59 115 { name= "Decided By", width = 100, align = "RIGHT",
Asa@59 116
Asa@59 117 },
Asa@59 118 { name= "craft", width = 50, align = "RIGHT",
Asa@59 119
Asa@59 120 },
Asa@59 121 { name= "Total Profit", width = 100, align = "RIGHT",
Asa@59 122 ['DoCellUpdate'] = displayMoney,
Asa@59 123 },
Asa@59 124 }
Asa@59 125
Asa@68 126 function Crafting.ExportToSkillet(data)
Asa@68 127 local skillString = select(3, string.find(data.recipeLink, "^|%x+|H(.+)|h%[.+%]"))
Asa@68 128 local _, skillId = strsplit(":", skillString)
Asa@68 129
Asa@68 130 ItemAuditor:AddToQueue(skillId,tradeSkillIndex, data.queue)
Asa@68 131 end
Asa@68 132
Asa@68 133 Crafting.RegisterQueueDestination('Skillet', Crafting.ExportToSkillet)
Asa@68 134
Asa@68 135
Asa@68 136
Asa@68 137 function Crafting.Export(destination)
Asa@68 138 if type(destination) == 'function' then
Asa@68 139 -- do nothing
Asa@68 140 elseif destination == nil then
Asa@70 141 destination = Crafting.GetQueueDestination()
Asa@68 142 elseif type(destination) == 'string' then
Asa@68 143 destination = queueDestinations[destination]
Asa@68 144 else
Asa@68 145 error('destination must be a function or a string')
Asa@68 146 end
Asa@68 147
Asa@59 148 local index = 1
Asa@59 149 local data = ItemAuditor:GetCraftingRow(index)
Asa@59 150 while data do
Asa@68 151 if data.queue > 0 then
Asa@68 152 destination(data)
Asa@68 153 end
Asa@61 154 index = index + 1
Asa@61 155 data = ItemAuditor:GetCraftingRow(index)
Asa@59 156
Asa@59 157 end
Asa@59 158 end
Asa@59 159
Asa@74 160 -- ItemAuditor:GetModule('Crafting').filter_queued = false
Asa@74 161 Crafting.filter_queued = true
Asa@74 162 local function tableFilter(self, row, ...)
Asa@74 163 -- column 5 is how many should be crafted
Asa@74 164 if Crafting.filter_queued and row[5] <= 0 then
Asa@74 165 return false
Asa@74 166 end
Asa@74 167 return true
Asa@74 168 end
Asa@74 169
Asa@59 170 local craftingContent = false
Asa@59 171 local craftingTable = false
Asa@60 172 local btnProcess = false
Asa@59 173 local function ShowCrafting(container)
Asa@59 174 if craftingContent == false then
Asa@59 175 local window = container.frame
Asa@59 176 craftingContent = CreateFrame("Frame",nil,window)
Asa@59 177 craftingContent:SetBackdropColor(0, 0, 1, 0.5)
Asa@59 178 craftingContent:SetBackdropBorderColor(1, 0, 0, 1)
Asa@59 179
Asa@59 180 craftingContent:SetPoint("TOPLEFT", window, 10, -50)
Asa@59 181 craftingContent:SetPoint("BOTTOMRIGHT",window, -10, 10)
Asa@59 182
Asa@59 183 craftingTable = ScrollingTable:CreateST(craftingCols, 22, nil, nil, craftingContent )
Asa@59 184
Asa@59 185 IAcc = craftingContent
Asa@59 186 IAccWindow = window
Asa@59 187 craftingTable.frame:SetPoint("TOPLEFT",craftingContent, 0,0)
Asa@59 188 craftingTable.frame:SetPoint("BOTTOMRIGHT", craftingContent, 0, 30)
Asa@59 189
Asa@59 190 craftingTable:RegisterEvents({
Asa@59 191 ["OnEnter"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, scrollingTable, ...)
Asa@59 192 if realrow then
Asa@59 193 local data = realData[realrow]
Asa@59 194
Asa@59 195 GameTooltip:SetOwner(rowFrame, "ANCHOR_CURSOR")
Asa@59 196 GameTooltip:SetHyperlink(data.link)
Asa@59 197 GameTooltip:Show()
Asa@59 198 end
Asa@59 199 end,
Asa@59 200 ["OnLeave"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, scrollingTable, ...)
Asa@59 201 GameTooltip:Hide()
Asa@59 202 end,
Asa@59 203 });
Asa@59 204
Asa@59 205
Asa@59 206 btnProcess = CreateFrame("Button", nil, craftingContent, "UIPanelButtonTemplate")
Asa@59 207 btnProcess:SetText("Process")
Asa@59 208 btnProcess:SetSize(100, 25)
Asa@59 209 btnProcess:SetPoint("BOTTOMRIGHT", craftingContent, 0, 0)
Asa@59 210 btnProcess:RegisterForClicks("LeftButtonUp");
Asa@59 211
Asa@60 212 local function UpdateProcessTooltip(btn)
Asa@59 213 local data = ItemAuditor:GetCraftingRow(1)
Asa@59 214 if data then
Asa@59 215 GameTooltip:SetOwner(this, "ANCHOR_CURSOR")
Asa@59 216 GameTooltip:SetText(format('Create %sx%s', data.link, data.queue))
Asa@59 217 GameTooltip:Show()
Asa@59 218 end
Asa@60 219 end
Asa@60 220 btnProcess:SetScript("OnClick", function (self, button, down)
Asa@60 221 local data = ItemAuditor:GetCraftingRow(1)
Asa@60 222 if data then
Asa@60 223 ItemAuditor:Print('Crafting %sx%s', data.link, data.queue)
Asa@60 224 DoTradeSkill(data.tradeSkillIndex, data.queue)
Asa@60 225 data.queue = 0
Asa@60 226 ItemAuditor:RefreshCraftingTable()
Asa@60 227 UpdateProcessTooltip()
Asa@60 228 end
Asa@59 229 end)
Asa@59 230
Asa@60 231 btnProcess:SetScript("OnEnter", UpdateProcessTooltip)
Asa@60 232
Asa@59 233 btnProcess:SetScript("OnLeave", function()
Asa@59 234 GameTooltip:Hide()
Asa@59 235 end)
Asa@59 236
Asa@59 237 btnSkillet = CreateFrame("Button", nil, craftingContent, "UIPanelButtonTemplate")
Asa@70 238
Asa@70 239
Asa@59 240 btnSkillet:SetSize(125, 25)
Asa@59 241 btnSkillet:SetPoint("BOTTOMRIGHT", btnProcess, 'BOTTOMLEFT', 0, 0)
Asa@59 242 btnSkillet:RegisterForClicks("LeftButtonUp");
Asa@59 243 btnSkillet:SetScript("OnClick", function (self, button, down)
Asa@70 244 Crafting.Export()
Asa@59 245 end)
Asa@59 246
Asa@59 247 end
Asa@70 248 local destination = select(2, Crafting.GetQueueDestination())
Asa@70 249 btnSkillet:SetText("Export to "..destination)
Asa@70 250
Asa@59 251 craftingContent:Show()
Asa@59 252
Asa@59 253 if container.parent then
Asa@59 254 local width = 80
Asa@59 255 for i, data in pairs(craftingCols) do
Asa@59 256 width = width + data.width
Asa@59 257 end
Asa@59 258 container.parent:SetWidth(width);
Asa@59 259 end
Asa@59 260
Asa@59 261 ItemAuditor:RegisterEvent("TRADE_SKILL_SHOW", function()
Asa@59 262 if craftingContent and craftingContent:IsVisible() then
Asa@59 263 ItemAuditor:UpdateCraftingTable()
Asa@59 264 end
Asa@59 265 end)
Asa@59 266 ItemAuditor:UpdateCraftingTable()
Asa@59 267
Asa@59 268 return craftingContent
Asa@59 269 end
Asa@59 270
Asa@59 271
Asa@59 272
Asa@59 273 ItemAuditor:RegisterTab('Crafting', 'tab_crafting', ShowCrafting)
Asa@59 274 function ItemAuditor:DisplayCrafting()
Asa@59 275 self:CreateFrame('tab_crafting')
Asa@59 276 end
Asa@59 277
Asa@59 278 local craftingDeciders = {}
Asa@59 279
Asa@72 280 function Crafting.RegisterCraftingDecider(name, decider, options)
Asa@59 281 craftingDeciders[name] = decider
Asa@72 282
Asa@72 283 ItemAuditor.Options.args.crafting_options.args['chk'..name] = {
Asa@72 284 type = "toggle",
Asa@72 285 name = "Enable "..name,
Asa@72 286 get = function() return not ItemAuditor.db.profile.disabled_deciders[name] end,
Asa@72 287 set = function(info, value) ItemAuditor.db.profile.disabled_deciders[name] = not value end,
Asa@72 288 order = 11,
Asa@72 289 }
Asa@72 290
Asa@72 291 if options then
Asa@72 292 ItemAuditor.Options.args.crafting_options.args['decider_'..name] = {
Asa@72 293 handler = {},
Asa@72 294 name = name,
Asa@72 295 type = 'group',
Asa@72 296 args = options,
Asa@72 297 }
Asa@72 298 end
Asa@59 299 end
Asa@59 300
Asa@59 301 local lastWinnder = ""
Asa@59 302 local function Decide(data)
Asa@59 303 local newDecision = 0
Asa@74 304 local reason = ""
Asa@59 305 for name, decider in pairs(craftingDeciders) do
Asa@72 306 if not ItemAuditor.db.profile.disabled_deciders[name] and name ~= lastWinner then
Asa@74 307 newDecision, reason = decider(data)
Asa@74 308
Asa@59 309 if newDecision > data.queue then
Asa@59 310 data.queue = newDecision
Asa@74 311 lastWinner = (reason or name)
Asa@59 312 return Decide(data)
Asa@59 313 elseif newDecision < 0 then
Asa@59 314 lastWinner = ""
Asa@74 315 return 'VETO: '..(reason or name), -1
Asa@59 316 end
Asa@59 317 end
Asa@59 318 end
Asa@59 319
Asa@59 320 winner = lastWinner
Asa@59 321 lastWinner = ""
Asa@59 322
Asa@59 323 return winner, data.queue
Asa@59 324 end
Asa@59 325
Asa@59 326 local function isProfitable(data)
Asa@59 327 if data.profit > 0 and data.profit > ItemAuditor:GetCraftingThreshold() then
Asa@59 328 return 1
Asa@59 329 end
Asa@59 330 return -1
Asa@59 331 end
Asa@72 332
Asa@64 333 Crafting.RegisterCraftingDecider('Is Profitable', isProfitable)
Asa@59 334
Asa@74 335
Asa@59 336
Asa@59 337 local tableData = {}
Asa@59 338 function ItemAuditor:UpdateCraftingTable()
Asa@59 339 if LSW == nil then
Asa@59 340 self:Print("This feature requires LilSparky's Workshop.")
Asa@59 341 return
Asa@59 342 end
Asa@59 343 if Skillet == nil then
Asa@59 344 self:Print("This feature requires Skillet.")
Asa@59 345 return
Asa@59 346 end
Asa@59 347 if GetAuctionBuyout ~= nil then
Asa@59 348 elseif AucAdvanced and AucAdvanced.Version then
Asa@59 349 else
Asa@59 350 self:Print("This feature requires Auctionator, Auctioneer, AuctionLite, or AuctionMaster.")
Asa@59 351 return
Asa@59 352 end
Asa@59 353 wipe(realData)
Asa@59 354 wipe(tableData)
Asa@59 355
Asa@59 356 local profitableItems = {}
Asa@59 357 local profitableIndex = 1
Asa@59 358 local numChecked = 0
Asa@59 359 local row = 1
Asa@59 360
Asa@59 361 for i = 1, GetNumTradeSkills() do
Asa@59 362 local itemLink = GetTradeSkillItemLink(i)
Asa@59 363 local itemId = Skillet:GetItemIDFromLink(itemLink)
Asa@59 364
Asa@59 365 --Figure out if its an enchant or not
Asa@59 366 _, _, _, _, altVerb = GetTradeSkillInfo(i)
Asa@59 367 if LSW.scrollData[itemId] ~= nil and altVerb == 'Enchant' then
Asa@59 368 -- Ask LSW for the correct scroll
Asa@59 369 itemId = LSW.scrollData[itemId]["scrollID"]
Asa@59 370 end
Asa@59 371
Asa@59 372 local recipeLink = GetTradeSkillRecipeLink(i)
Asa@59 373 local stackSize = 1
Asa@59 374 if recipeLink ~= nil and itemId ~= nil then
Asa@59 375 local skillName, skillType, numAvailable, isExpanded, altVerb = GetTradeSkillInfo(i)
Asa@59 376 local itemName, itemLink= GetItemInfo(itemId)
Asa@74 377
Asa@74 378 -- This check has to be here for things like Inscription Research that don't produce an item.
Asa@74 379 if itemLink then
Asa@74 380 local count = Altoholic:GetItemCount(itemId)
Asa@74 381 local reagents = {}
Asa@74 382 local totalCost = 0
Asa@74 383 for reagentId = 1, GetTradeSkillNumReagents(i) do
Asa@74 384 local reagentName, _, reagentCount = GetTradeSkillReagentInfo(i, reagentId);
Asa@74 385 local reagentLink = GetTradeSkillReagentItemLink(i, reagentId)
Asa@74 386
Asa@74 387 reagents[reagentId] = {
Asa@74 388 name = reagentName,
Asa@74 389 count = reagentCount,
Asa@74 390 price = self:GetReagentCost(reagentLink, reagentCount),
Asa@74 391 }
Asa@74 392 totalCost = totalCost + self:GetReagentCost(reagentLink, reagentCount)
Asa@74 393 end
Asa@74 394 local data = {
Asa@74 395 recipeLink = recipeLink,
Asa@74 396 link = itemLink,
Asa@74 397 name = itemName,
Asa@74 398 count = count,
Asa@74 399 price = (self:GetAuctionPrice(itemLink) or 0),
Asa@74 400 cost = totalCost,
Asa@74 401 profit = (self:GetAuctionPrice(itemLink) or 0) - totalCost,
Asa@74 402 reagents = reagents,
Asa@74 403 count = count,
Asa@74 404 tradeSkillIndex = i,
Asa@74 405 queue = 0,
Asa@74 406 winner = "",
Asa@74 407 }
Asa@59 408
Asa@74 409 data.winner, data.queue = Decide(data)
Asa@74 410 data.queue = data.queue - count
Asa@74 411
Asa@74 412 -- If a tradeskill makes 5 at a time and something asks for 9, we should only
Asa@74 413 -- craft twice to get 10.
Asa@74 414 data.queue = ceil(data.queue / GetTradeSkillNumMade(i))
Asa@74 415
Asa@74 416 realData[row] = data
Asa@74 417 row = row + 1
Asa@59 418 end
Asa@59 419 end
Asa@59 420 end
Asa@59 421 table.sort(realData, function(a, b) return a.profit*a.queue > b.profit*b.queue end)
Asa@68 422 if craftingTable then
Asa@68 423 craftingTable:SetFilter(tableFilter)
Asa@68 424 self:RefreshCraftingTable()
Asa@68 425 end
Asa@60 426 end
Asa@60 427
Asa@60 428 function ItemAuditor:RefreshCraftingTable()
Asa@59 429 for key, data in pairs(realData) do
Asa@59 430 tableData[key] = {
Asa@59 431 data.name,
Asa@59 432 data.cost,
Asa@59 433 data.price,
Asa@59 434 data.winner,
Asa@59 435 data.queue,
Asa@59 436 data.profit*data.queue,
Asa@59 437 }
Asa@59 438 end
Asa@60 439 craftingTable:SetData(tableData, true)
Asa@59 440
Asa@60 441 if self:GetCraftingRow(1) then
Asa@60 442 btnProcess:Enable()
Asa@60 443 else
Asa@60 444 btnProcess:Disable()
Asa@60 445 end
Asa@59 446 end
Asa@59 447
Asa@59 448 function ItemAuditor:GetCraftingRow(row)
Asa@59 449 if craftingTable then
Asa@59 450 for _, index in pairs(craftingTable.sorttable) do
Asa@59 451 local tableRow = tableData[index]
Asa@59 452 if tableFilter(nil, tableRow) then
Asa@59 453 row = row - 1
Asa@59 454 if row == 0 then
Asa@59 455 return realData[index]
Asa@59 456 end
Asa@59 457 end
Asa@59 458 end
Asa@59 459 elseif realData then
Asa@59 460 return realData[row]
Asa@59 461 end
Asa@59 462 return nil
Asa@59 463 end
Asa@67 464 ItemAuditor.Options.args.crafting = {
Asa@67 465 type = "execute",
Asa@67 466 name = "crafting",
Asa@67 467 desc = "This opens a window to configure a crafting queue.",
Asa@67 468 func = "DisplayCrafting",
Asa@67 469 guiHidden = false,
Asa@67 470 }