vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 ------------------------------------------------------
2 -- ReagentCost.lua
3 ------------------------------------------------------
4 FRC_VERSION = "11200.1";
5 ------------------------------------------------------
6  
7 FRC_Config = { };
8 FRC_Config.Enabled = true;
9 FRC_Config.MinProfitRatio = 0;
10 FRC_Config.MinProfitMoney = nil;
11 FRC_Config.AutoLoadPriceSource = nil;
12  
13 FRC_ReagentLinks = { };
14  
15 local MIN_SCANS = 35; -- times an item must be seen at auction to be considered a good sample (equates to 100% on our confidence scale)
16 local MIN_CONFIDENCE = 5; -- cutoff so we don't report items we have little data on as potentially profitable
17 local MIN_OVERRIDE_CONFIDENCE = 90; -- cutoff for trusting an item's market price versus the price of its components
18  
19 -- Anti-freeze code borrowed from ReagentInfo (in turn, from Quest-I-On):
20 -- keeps WoW from locking up if we try to scan the tradeskill window too fast.
21 FRC_TradeSkillLock = { };
22 FRC_TradeSkillLock.NeedScan = false;
23 FRC_TradeSkillLock.Locked = false;
24 FRC_TradeSkillLock.EventTimer = 0;
25 FRC_TradeSkillLock.EventCooldown = 0;
26 FRC_TradeSkillLock.EventCooldownTime = 1;
27 FRC_CraftLock = { };
28 FRC_CraftLock.NeedScan = false;
29 FRC_CraftLock.Locked = false;
30 FRC_CraftLock.EventTimer = 0;
31 FRC_CraftLock.EventCooldown = 0;
32 FRC_CraftLock.EventCooldownTime = 1;
33  
34 function FRC_CraftFrame_SetSelection(id)
35 FRC_Orig_CraftFrame_SetSelection(id);
36  
37 if ( not id ) then
38 return;
39 end
40 local name, rank, maxRank = GetCraftDisplaySkillLine();
41 if not (name) then
42 return;
43 end
44 local craftName, craftSubSpellName, craftType, numAvailable, isExpanded, trainingPointCost, requiredLevel = GetCraftInfo(id);
45 if ( trainingPointCost and trainingPointCost > 0 ) then
46 return;
47 end
48 if ( craftType == "header" ) then
49 return;
50 end
51  
52 local costText;
53 if (FRC_Config.Enabled) then
54 local itemLink = GetCraftItemLink(id);
55 if not (itemLink) then
56 itemLink = craftName; -- Some enchanting formulae produce items (runed rods), most don't. Enchants don't link, items do.
57 end
58 itemLink = FRC_NormalizeLink(itemLink);
59 local materialsTotal, confidenceScore = FRC_MaterialsCost(name, itemLink);
60 costText = GFWUtils.LtY("(Total cost: ");
61 if (materialsTotal == nil) then
62 if (FRC_PriceSource == "Auctioneer" and not IsAddOnLoaded("Auctioneer")) then
63 costText = costText .. GFWUtils.Gray("[Auctioneer not loaded]");
64 else
65 costText = costText .. GFWUtils.Gray("Unknown [insufficient data]");
66 end
67 else
68 costText = costText .. GFWUtils.TextGSC(materialsTotal) ..GFWUtils.Gray(" Confidence: "..confidenceScore.."%");
69 end
70 costText = costText ..GFWUtils.LtY(")");
71  
72 CraftReagentLabel:SetText(SPELL_REAGENTS.." "..costText);
73 CraftReagentLabel:Show();
74 end
75  
76 end
77  
78 function FRC_TradeSkillFrame_SetSelection(id)
79 FRC_Orig_TradeSkillFrame_SetSelection(id);
80  
81 if ( not id ) then
82 return;
83 end
84 local skillName, skillType, numAvailable, isExpanded = GetTradeSkillInfo(id);
85 if ( skillType == "header" ) then
86 return;
87 end
88 local skillLineName, skillLineRank, skillLineMaxRank = GetTradeSkillLine();
89  
90 local costText;
91 if (FRC_Config.Enabled) then
92 local link = GetTradeSkillItemLink(id);
93 if (link == nil) then return; end
94 link = FRC_NormalizeLink(link);
95  
96 local materialsTotal, confidenceScore = FRC_MaterialsCost(skillLineName, GetTradeSkillItemLink(id));
97 costText = GFWUtils.LtY("(Total cost: ");
98 if (materialsTotal == nil) then
99 if (FRC_PriceSource == "Auctioneer" and not IsAddOnLoaded("Auctioneer")) then
100 costText = costText .. GFWUtils.Gray("[Auctioneer not loaded]");
101 else
102 costText = costText .. GFWUtils.Gray("Unknown [insufficient data]");
103 end
104 else
105 costText = costText .. GFWUtils.TextGSC(materialsTotal) ..GFWUtils.Gray(" Confidence: "..confidenceScore.."%");
106 end
107 costText = costText ..GFWUtils.LtY(")");
108  
109 TradeSkillReagentLabel:SetText(SPELL_REAGENTS.." "..costText);
110 TradeSkillReagentLabel:Show();
111 end
112  
113 end
114  
115 function FRC_TradeSkillFrame_Update()
116 FRC_Orig_TradeSkillFrame_Update();
117  
118 FRC_ScanTradeSkill();
119 end
120  
121 function FRC_CraftFrame_Update()
122 FRC_Orig_CraftFrame_Update();
123  
124 FRC_ScanCraft();
125 end
126  
127 function FRC_OnLoad()
128  
129 -- Register Slash Commands
130 SLASH_FRC1 = "/reagentcost";
131 SLASH_FRC2 = "/rc";
132 SlashCmdList["FRC"] = function(msg)
133 FRC_ChatCommandHandler(msg);
134 end
135  
136 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo("Auctioneer");
137 if (loadable or IsAddOnLoaded("Auctioneer")) then
138 FRC_PriceSource = "Auctioneer";
139 elseif (KC_Auction ~= nil) then
140 FRC_PriceSource = "KC_Items";
141 elseif (AuctionMatrix_Version ~= nil) then
142 FRC_PriceSource = "AuctionMatrix";
143 elseif (WOWEcon_Enabled ~= nil) then
144 FRC_PriceSource = "WOWEcon_PriceMod";
145 end
146  
147 this:RegisterEvent("CRAFT_SHOW");
148 this:RegisterEvent("CRAFT_UPDATE");
149 this:RegisterEvent("TRADE_SKILL_SHOW");
150 this:RegisterEvent("TRADE_SKILL_UPDATE");
151 this:RegisterEvent("ADDON_LOADED");
152 this:RegisterEvent("VARIABLES_LOADED");
153  
154 GFWUtils.Print("Fizzwidget Reagent Cost "..FRC_VERSION.." initialized!");
155  
156 end
157  
158 function FRC_OnUpdate(elapsed)
159 -- If it's been more than a second since our last tradeskill update,
160 -- we can allow the event to process again.
161 FRC_TradeSkillLock.EventTimer = FRC_TradeSkillLock.EventTimer + elapsed;
162 if (FRC_TradeSkillLock.Locked) then
163 FRC_TradeSkillLock.EventCooldown = FRC_TradeSkillLock.EventCooldown + elapsed;
164 if (FRC_TradeSkillLock.EventCooldown > FRC_TradeSkillLock.EventCooldownTime) then
165  
166 FRC_TradeSkillLock.EventCooldown = 0;
167 FRC_TradeSkillLock.Locked = false;
168 end
169 end
170 FRC_CraftLock.EventTimer = FRC_CraftLock.EventTimer + elapsed;
171 if (FRC_CraftLock.Locked) then
172 FRC_CraftLock.EventCooldown = FRC_CraftLock.EventCooldown + elapsed;
173 if (FRC_CraftLock.EventCooldown > FRC_CraftLock.EventCooldownTime) then
174  
175 FRC_CraftLock.EventCooldown = 0;
176 FRC_CraftLock.Locked = false;
177 end
178 end
179  
180 if (FRC_TradeSkillLock.NeedScan) then
181 FRC_TradeSkillLock.NeedScan = false;
182 FRC_ScanTradeSkill();
183 end
184 if (FRC_CraftLock.NeedScan) then
185 FRC_CraftLock.NeedScan = false;
186 FRC_ScanCraft();
187 end
188 end
189  
190 function FRC_OnEvent(event)
191  
192 if (event == "ADDON_LOADED" and (arg1 == "Blizzard_CraftUI" or IsAddOnLoaded("Blizzard_CraftUI"))) then
193 if (FRC_Orig_CraftFrame_SetSelection == nil) then
194 -- Overrides for displaying info in CraftFrame
195 FRC_Orig_CraftFrame_SetSelection = CraftFrame_SetSelection;
196 CraftFrame_SetSelection = FRC_CraftFrame_SetSelection;
197  
198 -- And for scanning, since it looks like doing it in event handlers is crashy/unreliable now.
199 FRC_Orig_CraftFrame_Update = CraftFrame_Update;
200 CraftFrame_Update = FRC_CraftFrame_Update;
201  
202 GFWUtils.Print("ReagentCost CraftFrame hooks installed.");
203 end
204 end
205  
206 if (event == "ADDON_LOADED" and (arg1 == "Blizzard_TradeSkillUI" or IsAddOnLoaded("Blizzard_TradeSkillUI"))) then
207 if (FRC_Orig_TradeSkillFrame_SetSelection == nil) then
208 -- Overrides for displaying info in TradeSkillFrame
209 FRC_Orig_TradeSkillFrame_SetSelection = TradeSkillFrame_SetSelection;
210 TradeSkillFrame_SetSelection = FRC_TradeSkillFrame_SetSelection;
211  
212 -- And for scanning, since it looks like doing it in event handlers is crashy/unreliable now.
213 FRC_Orig_TradeSkillFrame_Update = TradeSkillFrame_Update;
214 TradeSkillFrame_Update = FRC_TradeSkillFrame_Update;
215  
216 GFWUtils.Print("ReagentCost TradeSkillFrame hooks installed.");
217 end
218 end
219  
220 if ( event == "VARIABLES_LOADED" or event == "ADDON_LOADED" ) then
221  
222 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo("Auctioneer");
223 if ((loadable or IsAddOnLoaded("Auctioneer")) and FRC_PriceSource == nil) then
224 FRC_PriceSource = "Auctioneer";
225 end
226 if (AUCTIONEER_VERSION ~= nil) then
227 local _, _, major, minor = string.find(AUCTIONEER_VERSION, "^(%d+)%.(%d+)");
228 major, minor = tonumber(major), tonumber(minor);
229 if (major ~= nil and major >= 3 and minor ~= nil and minor >= 1) then
230 FRC_PriceSource = "Auctioneer";
231 end
232 end
233 return;
234 end
235  
236 if ( event == "TRADE_SKILL_SHOW" or event == "CRAFT_SHOW" and FRC_Config.Enabled) then
237  
238 if (event == "CRAFT_SHOW" and GetCraftDisplaySkillLine() == nil) then
239 -- Beast Training uses the CraftFrame; we can tell when it's up because it doesn't have a skill-level bar.
240 -- We don't have anything to do in that case, so let's not try loading Auctioneer and stuff.
241 return;
242 end
243  
244 local name, title, notes, enabled, loadable, reason, security = GetAddOnInfo("Auctioneer");
245 if ((loadable or IsAddOnLoaded("Auctioneer")) and FRC_PriceSource == nil) then
246 FRC_PriceSource = "Auctioneer";
247 end
248 if ( FRC_PriceSource == "Auctioneer" and FRC_Config.AutoLoadPriceSource) then
249 if (not IsAddOnLoaded("Auctioneer")) then
250 local loaded, reason = LoadAddOn("Auctioneer");
251 if (not loaded) then
252 GFWUtils.Print("Can't load Auctioneer: "..reason);
253 return;
254 end
255 end
256 end
257 if ( FRC_PriceSource == nil) then
258 GFWUtils.Print("ReagentCost: missing required dependency. Can't find Auctioneer, KC_Items, or AuctionMatrix.");
259 return;
260 end
261 end
262  
263 if ( event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_UPDATE" ) then
264  
265 FRC_ScanTradeSkill();
266  
267 elseif ( event == "CRAFT_SHOW" or event == "CRAFT_UPDATE" ) then
268  
269 FRC_ScanCraft();
270  
271 end
272  
273 end
274  
275 function FRC_ChatCommandHandler(msg)
276  
277 if (FRC_PriceSource == nil) then
278 GFWUtils.Print("Fizzwidget Reagent Cost is installed but non-functional; can't find Auctioneer, KC_Items (with auction module), or AuctionMatrix.");
279 return;
280 end
281  
282 -- Print Help
283 if ( msg == "help" ) or ( msg == "" ) then
284 GFWUtils.Print("Fizzwidget Reagent Cost "..FRC_VERSION..":");
285 GFWUtils.Print("/reagentcost (or /rc) <command>");
286 GFWUtils.Print("- "..GFWUtils.Hilite("help").." - Print this helplist.");
287 GFWUtils.Print("- "..GFWUtils.Hilite("status").." - Check current settings.");
288 GFWUtils.Print("- "..GFWUtils.Hilite("reset").." - Reset to default settings.");
289 GFWUtils.Print("- "..GFWUtils.Hilite("on").." | "..GFWUtils.Hilite("off").." - Toggle displaying info in tradeskill windows.");
290 if (FRC_PriceSource == "Auctioneer") then
291 GFWUtils.Print("- "..GFWUtils.Hilite("autoload on").." | "..GFWUtils.Hilite("off").." - Control whether to automatically load Auctioneer when showing tradeskill windows.");
292 end
293 GFWUtils.Print("- "..GFWUtils.Hilite("report [<skillname>]").." - Output a list of the most profitable tradeskill items you can make. (Or only those produced through <skillname>.)");
294 GFWUtils.Print("- "..GFWUtils.Hilite("minprofit <number>").." - When reporting, only show items whose estimated profit is <number> or greater. (In copper, so 1g == 10000.)");
295 GFWUtils.Print("- "..GFWUtils.Hilite("minprofit <number>%").." - When reporting, only show items whose estimated profit exceeds its cost of materials by <number> percent or more.");
296 return;
297 end
298  
299 if (msg == "version") then
300 GFWUtils.Print("Fizzwidget Reagent Cost "..FRC_VERSION);
301 return;
302 end
303  
304 -- Check Status
305 if ( msg == "status" ) then
306 if (FRC_Config.Enabled) then
307 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("is").." displaying materials cost in tradeskill windows.");
308 if (FRC_Config.AutoLoadPriceSource) then
309 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("will").." automatically load Auctioneer to show prices in tradeskill windows.");
310 else
311 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("will not").." automatically load Auctioneer; prices will not be shown in tradeskill windows until Auctioner is loaded some other way.");
312 end
313 else
314 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("is not").." displaying materials cost in tradeskill windows.");
315 end
316 if (FRC_Config.MinProfitMoney == nil) then
317 GFWUtils.Print("Reports will only include items whose estimated profit exceeds materials cost by "..GFWUtils.Hilite(FRC_Config.MinProfitRatio.."%").." or more.");
318 else
319 GFWUtils.Print("Reports will only include items whose estimated profit is "..GFWUtils.TextGSC(FRC_Config.MinProfitMoney).." or greater.");
320 end
321 return;
322 end
323  
324 -- Reset Variables
325 if ( msg == "reset" ) then
326 FRC_Config.Enabled = true;
327 FRC_Config.MinProfitRatio = 0;
328 FRC_Config.MinProfitMoney = nil;
329 GFWUtils.Print("Reagent Cost configuration reset.");
330 FRC_ChatCommandHandler("status");
331 return;
332 end
333  
334 -- Turn trade info gathering on
335 if ( msg == "on" ) then
336 FRC_Config.Enabled = true;
337 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("is").." displaying materials cost in tradeskill windows.");
338 return;
339 end
340  
341 -- Turn trade info gathering Off
342 if ( msg == "off" ) then
343 FRC_Config.Enabled = false;
344 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("is not").." displaying materials cost in tradeskill windows.");
345 return;
346 end
347  
348 if ( msg == "autoload on" ) then
349 FRC_Config.AutoLoadPriceSource = true;
350 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("will").." automatically load Auctioneer to show prices in tradeskill windows.");
351 return;
352 end
353 if ( msg == "autoload off" ) then
354 FRC_Config.AutoLoadPriceSource = nil;
355 GFWUtils.Print("Reagent Cost "..GFWUtils.Hilite("will not").." automatically load Auctioneer; prices will not be shown in tradeskill windows until Auctioner is loaded some other way.");
356 return;
357 end
358  
359 local _, _, cmd, args = string.find(msg, "(%w+) *(.*)");
360 if ( cmd == "minprofit" ) then
361  
362 local _, _, number, isPercent = string.find(msg, "minprofit (-*%d+)(%%*)");
363 if (number == nil) then
364 GFWUtils.Print("Usage: "..GFWUtils.Hilite("/rc minprofit <number>[%]"));
365 return;
366 end
367 if (isPercent == "%") then
368 FRC_Config.MinProfitRatio = tonumber(number);
369 FRC_Config.MinProfitMoney = nil;
370 GFWUtils.Print("Reports will only include items whose estimated profit exceeds materials cost by "..GFWUtils.Hilite(FRC_Config.MinProfitRatio.."%").." or more.");
371 else
372 FRC_Config.MinProfitRatio = nil;
373 FRC_Config.MinProfitMoney = tonumber(number);
374 GFWUtils.Print("Reports will only include items whose estimated profit is "..GFWUtils.TextGSC(FRC_Config.MinProfitMoney).." or greater.");
375 end
376 return;
377 end
378  
379 if ( ( cmd == "reagents" or cmd == "report" ) and FRC_PriceSource == "Auctioneer") then
380 if (not IsAddOnLoaded("Auctioneer")) then
381 local loaded, reason = LoadAddOn("Auctioneer");
382 if (not loaded) then
383 GFWUtils.Print("Can't load Auctioneer: "..reason);
384 return;
385 end
386 end
387 end
388  
389 if ( cmd == "reagents" or cmd == "report" ) then
390  
391 -- check second arg
392 local _, _, arg1, moreArgs = string.find(args, "(%w+) *(.*)");
393 local scope = "toon";
394 if (arg1 == "all") then
395 scope = "realm";
396 args = moreArgs;
397 elseif (arg1 == "allrealms") then
398 scope = "all";
399 args = moreArgs;
400 end
401  
402 -- parse skill names from args
403 local mySkills = { };
404 if (args ~= nil and args ~= "") then
405 for word in string.gfind(args, "[^%s]+") do
406 local niceWord = string.upper(string.sub(word, 1, 1))..string.sub(word, 2);
407 table.insert(mySkills, niceWord);
408 end
409 end
410  
411 -- if no args, use the skills this character knows
412 if (table.getn(mySkills) == 0) then
413 for skillIndex = 1, GetNumSkillLines() do
414 local skillName, _, _, _, _, _, _, isAbandonable = GetSkillLineInfo(skillIndex);
415 if (isAbandonable) then
416 table.insert(mySkills, skillName);
417 end
418 end
419 end
420  
421 local printList;
422 if (cmd == "report") then
423 printList = FRC_ReportForSkill;
424 elseif (cmd == "reagents") then
425 printList = FRC_ListAllReagents;
426 end
427 for _, skillName in mySkills do
428 printList(skillName, scope);
429 end
430  
431 return;
432 end
433  
434 _, _, itemLink = string.find(msg, "(|c%x+|Hitem:%d+:%d+:%d+:%d+|h%[.-%]|h|r)");
435 if (itemLink ~= nil and itemLink ~= "") then
436 itemLink = FRC_NormalizeLink(itemLink);
437 if (not IsAddOnLoaded("Auctioneer")) then
438 local loaded, reason = LoadAddOn("Auctioneer");
439 if (not loaded) then
440 GFWUtils.Print("Can't load Auctioneer: "..reason);
441 return;
442 end
443 end
444 local found = false;
445 for skillName, skillTable in FRC_ReagentLinks do
446 if (skillTable[itemLink] ~= nil) then
447 for recipe, reagentList in skillTable[itemLink] do
448 if (string.find(itemLink, "%["..recipe.."%]")) then
449 GFWUtils.Print(itemLink.." ("..skillName.."):");
450 else
451 GFWUtils.Print(itemLink.." ("..skillName.." - "..recipe.."):");
452 end
453 found = true;
454 for _, reagentInfo in reagentList do
455 if (type(reagentInfo) == "table") then
456 local price, confidence, isAdjusted = FRC_AdjustedCost(skillName, reagentInfo.link);
457 local adjustedText, confidenceText;
458 if (isAdjusted) then
459 adjustedText = "(based on component prices)";
460 else
461 adjustedText = "";
462 end
463 if (confidence < 0) then
464 confidenceText = "from vendor";
465 else
466 confidenceText = confidence.."%"
467 end
468 if (price ~= nil) then
469 GFWUtils.Print(GFWUtils.Hilite(reagentInfo.count.."x ")..reagentInfo.link..": "..GFWUtils.TextGSC(price * reagentInfo.count)..GFWUtils.Gray(" ("..confidenceText..") ")..adjustedText);
470 else
471 GFWUtils.Print(GFWUtils.Hilite(reagentInfo.count.."x ")..reagentInfo.link..": No price data");
472 end
473 end
474 end
475  
476 local itemPrice, itemConfidence = FRC_TypicalItemPrice(itemLink);
477 local materialsCost, matsConfidence = FRC_MaterialsCostForRecipe(skillName, itemLink, recipe);
478 local profit = itemPrice - materialsCost;
479 local profitText;
480 if (profit > 0) then
481 profitText = "profit ".. GFWUtils.TextGSC(profit);
482 elseif (profit == 0) then
483 profitText = GFWUtils.Hilite("(break-even)");
484 else
485 profitText = GFWUtils.Red("loss ").. GFWUtils.TextGSC(math.abs(profit));
486 end
487 if (materialsCost ~= nil) then
488 GFWUtils.Print("Total materials: "..GFWUtils.TextGSC(materialsCost)..GFWUtils.Gray("("..matsConfidence..")"));
489 else
490 GFWUtils.Print("Total materials: data not available for one or more reagents");
491 end
492 if (itemPrice ~= nil) then
493 GFWUtils.Print("Auction price: "..GFWUtils.TextGSC(itemPrice)..GFWUtils.Gray("("..itemConfidence..")").."; "..profitText);
494 else
495 GFWUtils.Print("Auction price: data not available");
496 end
497 end
498 end
499 end
500 if (not found) then
501 GFWUtils.Print(itemLink.." not found in tradeskill data.");
502 end
503 return;
504 end
505  
506  
507 -- If we get down to here, we got bad input.
508 FRC_ChatCommandHandler("help");
509 end
510  
511 function FRC_ListAllReagents(skillName, scope)
512 local itemsTable = FRC_ReagentLinks[skillName];
513 if (itemsTable == nil) then
514 if (ReagentData == nil) then
515 GFWUtils.Print("Nothing for "..GFWUtils.Hilite(skillName)..".");
516 elseif (ReagentData['reversegathering'][skillName] ~= nil) then
517 -- do nothing; don't want to barf errors about gathering skills...
518 elseif (ReagentData['reverseprofessions'][skillName] ~= nil) then
519 GFWUtils.Print("ReagentCost doesn't have information on "..GFWUtils.Hilite(skillName)..". Please open your "..GFWUtils.Hilite(skillName).." window before requesting a report.");
520 else
521 GFWUtils.Print(GFWUtils.Hilite(skillName).." is not a known profession.");
522 end
523 else
524 local realm = GetRealmName();
525 local player = UnitName("player");
526 for anItem, recipesTable in itemsTable do
527 for recipe, reagentList in recipesTable do
528 local known;
529 if (scope == "toon") then
530 if (FRC_KnownRecipes and FRC_KnownRecipes[realm] and FRC_KnownRecipes[realm][player]) then
531 known = GFWTable.KeyOf(FRC_KnownRecipes[realm][player], anItem);
532 end
533 elseif (scope == "realm") then
534 if (FRC_KnownRecipes and FRC_KnownRecipes[realm]) then
535 for player, items in FRC_KnownRecipes[realm] do
536 if (GFWTable.KeyOf(FRC_KnownRecipes[realm][player], anItem)) then
537 known = true;
538 break;
539 end
540 end
541 end
542 else
543 known = true;
544 end
545  
546 if (known) then
547 local itemString;
548 if (string.find(anItem, "%["..recipe.."%]")) then
549 itemString = anItem..": ";
550 else
551 itemString = anItem.." ("..recipe.."): ";
552 end
553 for _, aReagent in reagentsTable do
554 itemString = itemString .. aReagent.count .. "x" .. aReagent.link .. ", ";
555 end
556 itemString = string.gsub(itemString, ", $", "");
557 GFWUtils.Print(itemString);
558 end
559 end
560 end
561 end
562 end
563  
564 function FRC_ReportForSkill(skillName, scope)
565 local knownItems = 0;
566 local reliableItems = 0;
567 local shownItems = 0;
568 local itemsTable = FRC_ReagentLinks[skillName];
569  
570 if (itemsTable == nil) then
571 if (ReagentData == nil) then
572 GFWUtils.Print("Nothing for "..GFWUtils.Hilite(skillName)..".");
573 elseif (ReagentData['reversegathering'][skillName] ~= nil) then
574 -- do nothing; don't want to barf errors about gathering skills...
575 if (skillName == ReagentData['gathering']['mining']) then
576 -- ...except for Mining, which is also a production skill as far as we're concerned.
577 GFWUtils.Print("ReagentCost doesn't have information on "..GFWUtils.Hilite(skillName)..". Please open your "..GFWUtils.Hilite(skillName).." window before requesting a report.");
578 end
579 elseif (ReagentData['reverseprofessions'][skillName] ~= nil) then
580 GFWUtils.Print("ReagentCost doesn't have information on "..GFWUtils.Hilite(skillName)..". Please open your "..GFWUtils.Hilite(skillName).." window before requesting a report.");
581 else
582 GFWUtils.Print(GFWUtils.Hilite(skillName).." is not a known profession.");
583 end
584 return;
585 end
586  
587 local reportTable = { }; -- separate report for each skill
588  
589 -- first, build a table that includes current Auctioneer prices for composite items
590 local realm = GetRealmName();
591 local player = UnitName("player");
592 for anItem in itemsTable do
593 local known;
594 if (scope == "toon") then
595 if (FRC_KnownRecipes and FRC_KnownRecipes[realm] and FRC_KnownRecipes[realm][player]) then
596 known = GFWTable.KeyOf(FRC_KnownRecipes[realm][player], anItem);
597 end
598 elseif (scope == "realm") then
599 if (FRC_KnownRecipes and FRC_KnownRecipes[realm]) then
600 for player, items in FRC_KnownRecipes[realm] do
601 if (GFWTable.KeyOf(FRC_KnownRecipes[realm][player], anItem)) then
602 known = true;
603 break;
604 end
605 end
606 end
607 else
608 known = true;
609 end
610  
611 if (known) then
612 -- parse out a link so that we ignore non-auctionable craft recipes (i.e. enchants)
613 _, _, itemLink = string.find(anItem, "(|c%x+|Hitem:%d+:%d+:%d+:%d+|h%[.-%]|h|r)");
614 if (itemLink ~= nil and itemLink ~= "") then
615 itemLink = FRC_NormalizeLink(itemLink);
616 for recipe in FRC_ReagentLinks[skillName][itemLink] do
617 knownItems = knownItems + 1;
618 local itemPrice, itemConfidence = FRC_TypicalItemPrice(itemLink);
619 local materialsCost, matsConfidence = FRC_MaterialsCostForRecipe(skillName, itemLink, recipe);
620  
621 if (itemConfidence == nil) then itemConfidence = 0; end
622 if (matsConfidence == nil) then matsConfidence = 0; end
623  
624 if (itemConfidence >= MIN_CONFIDENCE and matsConfidence >= MIN_CONFIDENCE) then
625 reliableItems = reliableItems + 1;
626 local profit = itemPrice - materialsCost;
627 table.insert(reportTable, {link=itemLink, recipe=recipe, matsCost=materialsCost, matsConf=matsConfidence, itemPrice=itemPrice, itemConf=itemConfidence, profit=profit});
628 end
629 end
630 end
631 end
632 end
633  
634  
635 if (knownItems == 0) then
636 GFWUtils.Print("ReagentCost doesn't have information on "..GFWUtils.Hilite(skillName)..". Please open your "..GFWUtils.Hilite(skillName).." window before requesting a report.");
637 return;
638 end
639  
640 if (reliableItems == 0) then
641 GFWUtils.Print("None of the "..GFWUtils.Hilite(knownItems).." items you can make with "..GFWUtils.Hilite(skillName).." have reliable auction price data. (They may not be tradeable.)");
642 return;
643 end
644  
645 GFWUtils.Print("Most profitable recipes for "..GFWUtils.Hilite(skillName)..":");
646  
647 if (reliableItems > 1) then
648 table.sort(reportTable, FRC_SortProfit);
649 end
650  
651 -- and report those that meet our minimum requirements
652 for _, reportInfo in reportTable do
653 if (FRC_Config.MinProfitRatio and (reportInfo.profit / reportInfo.matsCost * 100) >= FRC_Config.MinProfitRatio) then
654 shownItems = shownItems + 1;
655 FRC_PrintReportLine(reportInfo);
656 elseif (FRC_Config.MinProfitMoney and reportInfo.profit >= FRC_Config.MinProfitMoney) then
657 shownItems = shownItems + 1;
658 FRC_PrintReportLine(reportInfo);
659 end
660 end
661 GFWUtils.Print(GFWUtils.Hilite(knownItems).." recipes known, "..GFWUtils.Hilite(reliableItems).." with auction data, "..GFWUtils.Hilite(shownItems).." above profit threshold.");
662  
663 end
664  
665 function FRC_ScanTradeSkill()
666 if (not TradeSkillFrame or not TradeSkillFrame:IsVisible() or FRC_TradeSkillLock.Locked) then return; end
667 -- This prevents further update events from being handled if we're already processing one.
668 -- This is done to prevent the game from freezing under certain conditions.
669 FRC_TradeSkillLock.Locked = true;
670  
671 local skillLineName, skillLineRank, skillLineMaxRank = GetTradeSkillLine();
672 if not (skillLineName) then
673 FRC_TradeSkillLock.NeedScan = true;
674 return; -- apparently sometimes we're called too early, this is nil, and all hell breaks loose.
675 end
676 if (FRC_ReagentLinks == nil) then
677 FRC_ReagentLinks = { };
678 end
679 if (FRC_ReagentLinks[skillLineName] == nil) then
680 FRC_ReagentLinks[skillLineName] = { };
681 end
682  
683 local realm = GetRealmName();
684 local player = UnitName("player");
685 if (FRC_KnownRecipes == nil) then
686 FRC_KnownRecipes = {};
687 end
688 if (FRC_KnownRecipes[realm] == nil) then
689 FRC_KnownRecipes[realm] = {};
690 end
691 FRC_KnownRecipes[realm][player] = {};
692 for id = GetNumTradeSkills(), 1, -1 do
693 -- loop from the bottom up, since the reagents we make for compound items are usually below the recipes that need them
694 local skillName, skillType, numAvailable, isExpanded = GetTradeSkillInfo(id);
695 if ( skillType ~= "header" ) then
696 local itemLink = GetTradeSkillItemLink(id);
697 if (itemLink == nil) then
698 FRC_TradeSkillLock.NeedScan = true;
699 else
700 table.insert(FRC_KnownRecipes[realm][player], itemLink);
701  
702 FRC_ReagentLinks[skillLineName][itemLink] = { };
703 FRC_ReagentLinks[skillLineName][itemLink][skillName] = { };
704 for i=1, GetTradeSkillNumReagents(id), 1 do
705 local link = GetTradeSkillReagentItemLink(id, i);
706 if (link == nil) then
707 FRC_ReagentLinks[skillLineName][itemLink][skillName] = nil;
708 FRC_TradeSkillLock.NeedScan = true;
709 break;
710 else
711 local reagentName, reagentTexture, reagentCount, playerReagentCount = GetTradeSkillReagentInfo(id, i);
712 table.insert(FRC_ReagentLinks[skillLineName][itemLink][skillName], {link=link, count=reagentCount});
713 end
714 end
715 end
716 end
717 end
718  
719 end
720  
721 function FRC_ScanCraft()
722 if (not CraftFrame or not CraftFrame:IsVisible() or FRC_CraftLock.Locked) then return; end
723 -- This prevents further update events from being handled if we're already processing one.
724 -- This is done to prevent the game from freezing under certain conditions.
725 FRC_CraftLock.Locked = true;
726  
727 -- This is used only for Enchanting
728 local skillLineName, rank, maxRank = GetCraftDisplaySkillLine();
729 if not (skillLineName) then
730 return; -- Hunters' Beast Training also uses the CraftFrame, but doesn't have a SkillLine.
731 end
732 if (FRC_ReagentLinks == nil) then
733 FRC_ReagentLinks = { };
734 end
735 if (FRC_ReagentLinks[skillLineName] == nil) then
736 FRC_ReagentLinks[skillLineName] = { };
737 end
738  
739 local realm = GetRealmName();
740 local player = UnitName("player");
741 if (FRC_KnownRecipes == nil) then
742 FRC_KnownRecipes = {};
743 end
744 if (FRC_KnownRecipes[realm] == nil) then
745 FRC_KnownRecipes[realm] = {};
746 end
747 FRC_KnownRecipes[realm][player] = {};
748 for id = GetNumCrafts(), 1, -1 do
749 if ( craftType ~= "header" ) then
750 craftName, craftSubSpellName, craftType, numAvailable, isExpanded, trainingPointCost, requiredLevel = GetCraftInfo(id);
751 local itemLink = GetCraftItemLink(id);
752 if (itemLink == nil) then
753 itemLink = craftName; -- may be an item, may be a (currently unlinkable) enchant
754 end
755 if (itemLink == nil) then
756 FRC_TradeSkillLock.NeedScan = true;
757 else
758 table.insert(FRC_KnownRecipes[realm][player], itemLink);
759  
760 FRC_ReagentLinks[skillLineName][itemLink] = { };
761 FRC_ReagentLinks[skillLineName][itemLink][craftName] = { };
762 for i=1, GetCraftNumReagents(id), 1 do
763 local link = GetCraftReagentItemLink(id, i);
764 if (link == nil) then
765 FRC_ReagentLinks[skillLineName][itemLink][craftName] = nil;
766 FRC_CraftLock.NeedScan = true;
767 break;
768 else
769 local reagentName, reagentTexture, reagentCount, playerReagentCount = GetCraftReagentInfo(id, i);
770 table.insert(FRC_ReagentLinks[skillLineName][itemLink][craftName], {link=link, count=reagentCount});
771 end
772 end
773 end
774 end
775 end
776 end
777  
778 function FRC_SortProfit(a, b)
779 -- sort by ratio or actual amount based on which we're using as cutoff
780 if (FRC_Config.MinProfitRatio ~= nil) then
781 return (a.profit / a.matsCost) > (b.profit / b.matsCost);
782 else
783 return a.profit > b.profit;
784 end
785 end
786  
787 function FRC_PrintReportLine(reportInfo)
788 local reportLine;
789 if (string.find(reportInfo.link, "%["..reportInfo.recipe.."%]")) then
790 reportLine = reportInfo.link..": ";
791 else
792 reportLine = reportInfo.link.." ("..reportInfo.recipe.."): ";
793 end
794 reportLine = reportLine .."materials cost ".. GFWUtils.TextGSC(reportInfo.matsCost) ..GFWUtils.Gray(" ("..reportInfo.matsConf.."%)")..", "
795 reportLine = reportLine .."auction price ".. GFWUtils.TextGSC(reportInfo.itemPrice) ..GFWUtils.Gray(" ("..reportInfo.itemConf.."%)")..", "
796 if (reportInfo.profit >= 0) then
797 reportLine = reportLine .."profit ".. GFWUtils.TextGSC(reportInfo.profit);
798 else
799 reportLine = reportLine ..GFWUtils.Red("loss ").. GFWUtils.TextGSC(reportInfo.profit);
800 end
801 GFWUtils.Print(reportLine);
802 end
803  
804 function FRC_AdjustedCost(skillName, itemLink)
805  
806 local itemPrice, itemConfidence = FRC_TypicalItemPrice(itemLink);
807 if (FRC_RecursiveItems == nil) then
808 FRC_RecursiveItems = {};
809 end
810 if (GFWTable.KeyOf(FRC_RecursiveItems, itemLink)) then
811 -- avoid infinite recursion
812 FRC_RecursiveItems = nil;
813 return itemPrice, itemConfidence, false;
814 else
815 table.insert(FRC_RecursiveItems, itemLink);
816 end
817  
818 -- don't calculate sub-reagent prices for the likes of alchemical transumutes
819 -- (recipes that take one reagent also produced by the same skill and produce one other such reagent)
820 if (FRC_ReagentLinks[skillName] and FRC_ReagentLinks[skillName][itemLink]) then
821 for recipe, reagentsList in FRC_ReagentLinks[skillName][itemLink] do
822 if (table.getn(reagentsList) == 1 ) then
823 local reagentInfo = reagentsList[1];
824 if (reagentInfo.count == 1 and FRC_ReagentLinks[skillName][reagentInfo.link]) then
825 return itemPrice, itemConfidence, false;
826 end
827 end
828 end
829 end
830  
831 -- for all other recipes, calculate total cost of reagents which might be produced by the same skill,
832 -- and use that amount if it's more reliable.
833 -- (e.g. engineering parts -> base reagents, bolts of cloth -> pieces of cloth)
834 local subReagentsPrice, subReagentsConfidence = FRC_MaterialsCost(skillName, itemLink);
835 if (subReagentsPrice and subReagentsConfidence) then
836 if (not (itemPrice and itemConfidence)) then
837 return subReagentsPrice, subReagentsConfidence, true;
838 end
839 if (subReagentsConfidence >= itemConfidence and itemConfidence < MIN_OVERRIDE_CONFIDENCE and subReagentsPrice < itemPrice) then
840 return subReagentsPrice, subReagentsConfidence, true;
841 end
842 end
843 return itemPrice, itemConfidence, false;
844 end
845  
846 function FRC_MaterialsCost(skillName, itemLink)
847 if (FRC_ReagentLinks[skillName] == nil) then
848 return nil, nil;
849 end
850 if (FRC_ReagentLinks[skillName][itemLink] == nil) then
851 return nil, nil;
852 end
853  
854 local pricesPerRecipe = {};
855 for recipe in FRC_ReagentLinks[skillName][itemLink] do
856 if (type(recipe) == "string") then
857 local cost, confidence = FRC_MaterialsCostForRecipe(skillName, itemLink, recipe);
858 if (cost) then
859 table.insert(pricesPerRecipe, {cost=cost, confidence=confidence});
860 end
861 end
862 end
863 if (table.getn(pricesPerRecipe) == 0) then
864 return nil, nil;
865 end
866  
867 local sortCost = function(a,b)
868 return a.cost < b.cost;
869 end
870 local sortConfidence = function(a,b)
871 return a.confidence > b.confidence;
872 end
873 table.sort(pricesPerRecipe, sortConfidence);
874 table.sort(pricesPerRecipe, sortCost);
875  
876 return pricesPerRecipe[1].cost, pricesPerRecipe[1].confidence;
877  
878 end
879  
880 function FRC_MaterialsCostForRecipe(skillName, itemLink, recipeName)
881 local materialsTotal = 0;
882 local totalConfidence = 0;
883 local numAuctionReagents = 0;
884  
885 if (FRC_ReagentLinks[skillName] == nil) then
886 return nil, nil;
887 end
888 if (FRC_ReagentLinks[skillName][itemLink] == nil) then
889 return nil, nil;
890 end
891 if (FRC_ReagentLinks[skillName][itemLink][recipeName] == nil) then
892 return nil, nil;
893 end
894  
895 for _, reagentInfo in FRC_ReagentLinks[skillName][itemLink][recipeName] do
896 local price, confidence = FRC_AdjustedCost(skillName, reagentInfo.link)
897 if (price == nil) then
898 return nil, nil; -- if any of the reagents is missing price info, we can't calculate a total.
899 end
900 materialsTotal = materialsTotal + (price * reagentInfo.count);
901 if (confidence >= 0) then
902 totalConfidence = totalConfidence + confidence;
903 numAuctionReagents = numAuctionReagents + 1;
904 end
905 end
906 local confidenceScore = math.floor((totalConfidence / numAuctionReagents) * 100) / 100;
907  
908 return materialsTotal, confidenceScore;
909  
910 end
911  
912 function FRC_TypicalItemPrice(itemLink)
913 if (FRC_PriceSource == "Auctioneer") then
914 if (not IsAddOnLoaded("Auctioneer")) then
915 if (FRC_Config.AutoLoadPriceSource) then
916 local loaded, reason = LoadAddOn("Auctioneer");
917 if (not loaded) then
918 GFWUtils.Print("Can't load Auctioneer: "..reason);
919 return nil;
920 end
921 else
922 return nil;
923 end
924 end
925 return FRC_AuctioneerItemPrice(itemLink);
926 elseif (FRC_PriceSource == "KC_Items") then
927 return FRC_KCItemPrice(itemLink);
928 elseif (FRC_PriceSource == "AuctionMatrix") then
929 return FRC_AuctionMatrixItemPrice(itemLink);
930 elseif (FRC_PriceSource == "WOWEcon_PriceMod") then
931 return FRC_WOWEcon_PriceModItemPrice(itemLink);
932 else
933 return nil;
934 end
935 end
936  
937 function FRC_AuctioneerItemPrice(itemLink)
938 local getUsableMedian = Auctioneer_GetUsableMedian;
939 local getHistoricalMedian = Auctioneer_GetItemHistoricalMedianBuyout;
940 local getVendorSellPrice = Auctioneer_GetVendorSellPrice;
941 if (Auctioneer and Auctioneer.Statistic) then
942 getUsableMedian = Auctioneer.Statistic.GetUsableMedian;
943 getHistoricalMedian = Auctioneer.Statistic.GetItemHistoricalMedianBuyout;
944 end
945 if (Auctioneer and Auctioneer.API) then
946 getVendorSellPrice = Auctioneer.API.GetVendorSellPrice;
947 end
948 if (not (getUsableMedian and getHistoricalMedian)) then
949 GFWUtils.PrintOnce(GFWUtils.Red("ReagentCost error:").." missing expected Auctioneer API; can't calculate item prices.", 5);
950 return nil, nil;
951 end
952  
953 local _, _, itemID, randomProp, enchant = string.find(itemLink, "item:(%d+):(%d+):(%d+):%d+");
954 local itemKey = itemID..":"..(randomProp or 0)..":"..(enchant or 0);
955 local medianPrice, medianCount = getUsableMedian(itemKey);
956 if (medianPrice == nil) then
957 medianPrice, medianCount = getHistoricalMedian(itemKey);
958 end
959 if (medianCount == nil) then medianCount = 0 end
960  
961 itemID = tonumber(itemID) or 0;
962 local buyFromVendorPrice = 0;
963 local sellToVendorPrice = 0;
964 if (FRC_VendorPrices[itemID]) then
965 buyFromVendorPrice = FRC_VendorPrices[itemID].b;
966 sellToVendorPrice = FRC_VendorPrices[itemID].s;
967 end
968 if (sellToVendorPrice == 0) then
969 if (getVendorSellPrice) then
970 sellToVendorPrice = getVendorSellPrice(itemID) or 0;
971 elseif (Auctioneer_BasePrices ~= nil and Auctioneer_BasePrices[itemID] ~= nil and Auctioneer_BasePrices[itemID].s ~= nil) then
972 sellToVendorPrice = Auctioneer_BasePrices[itemID].s or 0;
973 end
974 end
975  
976 if (buyFromVendorPrice > 0) then
977 return buyFromVendorPrice, -1; -- FRC_VendorPrices lists only the primarily-vendor-bought tradeskill items
978 elseif (medianCount == 0 or medianPrice == nil) then
979 return sellToVendorPrice * 3, 0; -- generally a good guess for auction price if we don't have real auction data
980 else
981 return medianPrice, math.floor((math.min(medianCount, MIN_SCANS) / MIN_SCANS) * 1000) / 10;
982 end
983 end
984  
985 function FRC_KCItemPrice(itemLink)
986 local itemCode = KC_Common:GetCode(itemLink);
987 local seen, avgstack, min, bidseen, bid, buyseen, buy = KC_Auction:GetItemData(itemCode);
988 local _, _, itemID = string.find(itemLink, ".Hitem:(%d+):%d+:%d+:%d+.h%[[^]]+%].h");
989 itemID = tonumber(itemID) or 0;
990  
991 local buyFromVendorPrice = 0;
992 local sellToVendorPrice = 0;
993 if (FRC_VendorPrices[itemID]) then
994 buyFromVendorPrice = FRC_VendorPrices[itemID].b;
995 sellToVendorPrice = FRC_VendorPrices[itemID].s;
996 end
997 if (sellToVendorPrice == 0 and KC_SellValue ~= nil) then
998 sellToVendorPrice = (KC_Common:GetItemPrices(itemCode) or 0);
999 end
1000  
1001 --DevTools_Dump({itemLink=itemLink, itemID=itemID, buy=buy, buyseen=buyseen, buyFromVendorPrice=buyFromVendorPrice, sellToVendorPrice=sellToVendorPrice});
1002  
1003 if (buyFromVendorPrice ~= nil and buyFromVendorPrice > 0) then
1004 return buyFromVendorPrice, -1; -- FRC_VendorPrices lists only the primarily-vendor-bought tradeskill items
1005 elseif (buy ~= nil and buy > 0) then
1006 return buy, math.floor((math.min(buyseen, MIN_SCANS) / MIN_SCANS) * 1000) / 10;
1007 elseif (sellToVendorPrice ~= nil and sellToVendorPrice > 0) then
1008 return sellToVendorPrice * 3, 0; -- generally a good guess for auction price if we don't have real auction data
1009 else
1010 GFWUtils.DebugLog(itemLink.." not found in KC_Auction or vendor-reagent prices list");
1011 return nil, 0;
1012 end
1013 end
1014  
1015 function FRC_AuctionMatrixItemPrice(itemLink)
1016 local _, _, itemID, itemName = string.find(itemLink, ".Hitem:(%d+):%d+:%d+:%d+.h%[([^]]+)%].h");
1017 local buyFromVendorPrice = 0;
1018 local sellToVendorPrice = 0;
1019 itemID = tonumber(itemID) or 0;
1020 if (FRC_VendorPrices[itemID]) then
1021 buyFromVendorPrice = FRC_VendorPrices[itemID].b;
1022 sellToVendorPrice = FRC_VendorPrices[itemID].s;
1023 end
1024  
1025 local buyout, times, storeStack;
1026 if (itemName ~= nil and itemName ~= "" and AMDB[itemName]) then
1027 buyout = tonumber(AM_GetMedian(itemName, "abuyout"));
1028 if (buyout == nil) then
1029 buyout = tonumber(AuctionMatrix_GetData(itemName, "abuyout"));
1030 end
1031 times = tonumber(AuctionMatrix_GetData(itemName, "times"));
1032 storeStack = tonumber(AuctionMatrix_GetData(itemName, "stack"));
1033 if (sellToVendorPrice == 0) then
1034 sellToVendorPrice = tonumber(AuctionMatrix_GetData(itemName, "vendor"));
1035 end
1036 end
1037  
1038 --DevTools_Dump({itemLink=itemLink, buyout=buyout, times=times, buyFromVendorPrice=buyFromVendorPrice, sellToVendorPrice=sellToVendorPrice});
1039  
1040 if (buyFromVendorPrice ~= nil and buyFromVendorPrice > 0) then
1041 return buyFromVendorPrice, -1; -- FRC_VendorPrices lists only the primarily-vendor-bought tradeskill items
1042 elseif (buyout ~= nil and times ~= nil and buyout > 0) then
1043 local buyoutForOne = buyout;
1044 if (storeStack ~= nil and storeStack > 0) then
1045 buyoutForOne = math.floor(buyout/storeStack);
1046 end
1047 return buyoutForOne, math.floor((math.min(times, MIN_SCANS) / MIN_SCANS) * 1000) / 10;
1048 elseif (sellToVendorPrice ~= nil and sellToVendorPrice > 0) then
1049 return sellToVendorPrice * 3, 0; -- generally a good guess for auction price if we don't have real auction data
1050 end
1051  
1052 GFWUtils.DebugLog(itemLink.." not found in AuctionMatrix or vendor-reagent prices list");
1053 return nil, 0;
1054 end
1055  
1056 function FRC_WOWEcon_PriceModItemPrice(itemLink)
1057 local medianPrice, medianCount, serverData = WOWEcon_GetAuctionPrice_ByLink(itemLink);
1058 if (medianCount == nil) then
1059 medianCount = 0;
1060 end
1061  
1062 local _, _, itemID = string.find(itemLink, ".Hitem:(%d+):%d+:%d+:%d+.h%[[^]]+%].h");
1063 itemID = tonumber(itemID) or 0;
1064  
1065 local buyFromVendorPrice = 0;
1066 local sellToVendorPrice = 0;
1067 if (FRC_VendorPrices[itemID]) then
1068 buyFromVendorPrice = FRC_VendorPrices[itemID].b;
1069 sellToVendorPrice = FRC_VendorPrices[itemID].s;
1070 end
1071  
1072 if (sellToVendorPrice == 0) then
1073 sellToVendorPrice = WOWEcon_GetVendorPrice_ByLink(itemLink);
1074 end
1075  
1076 if (sellToVendorPrice == nil) then sellToVendorPrice = 0 end
1077  
1078 if (buyFromVendorPrice > 0) then
1079 return buyFromVendorPrice, -1; -- FRC_VendorPrices lists only the primarily-vendor-bought tradeskill items
1080 elseif (medianCount == 0 or medianPrice == nil) then
1081 return sellToVendorPrice * 3, 0; -- generally a good guess for auction price if we don't have real auction data
1082 else
1083 return medianPrice, math.floor((math.min(medianCount, MIN_SCANS) / MIN_SCANS) * 1000) / 10;
1084 end
1085 end
1086  
1087 function FRC_NormalizeLink(link)
1088 -- we don't care about variations in random-property items, enchants, or unique IDs...
1089 -- discarding them lets us use the link as both a printable link and a reliable index key.
1090 return string.gsub(link, "|c(%x+)|Hitem:(%d+):%d+:%d+:%d+|h%[(.-)%]|h|r", "|c%1|Hitem:%2:0:0:0|h[%3]|h|r");
1091 end