Andrew6810 3 years ago
parent
commit
d6aaac97af
  1. 1
      TradeSkillMaster/Auction/AuctionItem.lua
  2. 213
      TradeSkillMaster/ChangeLog.txt
  3. 71
      TradeSkillMaster/Core/Options.lua
  4. 41
      TradeSkillMaster/GUI/Design.lua
  5. 4
      TradeSkillMaster/GUI/MainFrame.lua
  6. 245
      TradeSkillMaster/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua
  7. 2
      TradeSkillMaster/Locale/deDE.lua
  8. 7
      TradeSkillMaster/Locale/enUS.lua
  9. 2
      TradeSkillMaster/Locale/esES.lua
  10. 2
      TradeSkillMaster/Locale/esMX.lua
  11. 2
      TradeSkillMaster/Locale/frFR.lua
  12. 2
      TradeSkillMaster/Locale/koKR.lua
  13. 2
      TradeSkillMaster/Locale/ptBR.lua
  14. 2
      TradeSkillMaster/Locale/ruRU.lua
  15. 2
      TradeSkillMaster/Locale/zhCN.lua
  16. 2
      TradeSkillMaster/Locale/zhTW.lua
  17. 39
      TradeSkillMaster/TradeSkillMaster.lua
  18. 13
      TradeSkillMaster/TradeSkillMaster.toc
  19. 40
      TradeSkillMaster/Util/Util.lua
  20. 2
      TradeSkillMaster_AuctionDB/Locale/deDE.lua
  21. 7
      TradeSkillMaster_AuctionDB/Locale/enUS.lua
  22. 2
      TradeSkillMaster_AuctionDB/Locale/esES.lua
  23. 2
      TradeSkillMaster_AuctionDB/Locale/esMX.lua
  24. 2
      TradeSkillMaster_AuctionDB/Locale/frFR.lua
  25. 2
      TradeSkillMaster_AuctionDB/Locale/koKR.lua
  26. 2
      TradeSkillMaster_AuctionDB/Locale/ptBR.lua
  27. 2
      TradeSkillMaster_AuctionDB/Locale/ruRU.lua
  28. 2
      TradeSkillMaster_AuctionDB/Locale/zhCN.lua
  29. 2
      TradeSkillMaster_AuctionDB/Locale/zhTW.lua
  30. 59
      TradeSkillMaster_AuctionDB/Modules/GUI.lua
  31. 490
      TradeSkillMaster_AuctionDB/Modules/Scanning.lua
  32. 7
      TradeSkillMaster_AuctionDB/Modules/config.lua
  33. 474
      TradeSkillMaster_AuctionDB/Modules/data.lua
  34. 26
      TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.lua
  35. 1
      TradeSkillMaster_Auctioning/locale/enUS.lua
  36. 1
      TradeSkillMaster_Auctioning/locale/frFR.lua
  37. 1
      TradeSkillMaster_Auctioning/locale/koKR.lua
  38. 1
      TradeSkillMaster_Auctioning/locale/ruRU.lua
  39. 1
      TradeSkillMaster_Auctioning/locale/zhCN.lua
  40. 27
      TradeSkillMaster_Auctioning/modules/ScanUtil.lua
  41. 33
      TradeSkillMaster_Auctioning/modules/manage.lua
  42. 12
      TradeSkillMaster_Crafting/Modules/Cost.lua
  43. 42
      TradeSkillMaster_Crafting/Modules/CraftingGUI.lua
  44. 192
      TradeSkillMaster_Crafting/Modules/EnchantingInfo.lua
  45. 79
      TradeSkillMaster_Crafting/Modules/SpellNames2IDs.lua
  46. 49
      TradeSkillMaster_Crafting/Modules/Util.lua
  47. 567
      TradeSkillMaster_Crafting/Modules/VellumInfo.lua
  48. 107
      TradeSkillMaster_Crafting/TradeSkillMaster_Crafting.lua
  49. 1
      TradeSkillMaster_Shopping/Locale/enUS.lua
  50. 1
      TradeSkillMaster_Shopping/Locale/koKR.lua
  51. 3
      TradeSkillMaster_Shopping/Locale/ruRU.lua
  52. 1
      TradeSkillMaster_Shopping/Locale/zhCN.lua
  53. 71
      TradeSkillMaster_Shopping/modules/Util.lua

1
TradeSkillMaster/Auction/AuctionItem.lua

@ -61,6 +61,7 @@ local AuctionRecord = {
GetItemBuyout = function(self)
if not self.buyout or self.buyout == 0 then return end
-- Calculate the price per item, always rounded downwards.
return floor(self.buyout / self.count)
end,

213
TradeSkillMaster/ChangeLog.txt

@ -1,3 +1,216 @@
# v2.8.3.668
TradeSkillMaster: Revived
- Release Date: March 23rd, 2023
- Author: Gnomezilla on Warmane-Icecrown [https://github.com/Bananaman].
## Total Time Elapsed Shown After "Full Scan" and "Group Scan" Completion
The "Full Scan" and "Group Scan" features now display the "total time elapsed" in the status bar after completion, such as "Done Scanning (1:35:27)".
This helps players get a better feel for how long each scan takes, and gives people something nice to look at after each scan.
## Improved Formatting of the "Full Scan" Time Estimate
We now display the time as `elapsed time / ~estimated total time`, such as `0:15:37 / ~1:41:27`.
The total time estimate is continuously updated during the scan, based on our intelligent, weighted "seconds per page" metric and the total number of pages (which can dynamically change during a scan, when other people post or buy auctions). This "total time estimate" therefore follows the server performance beautifully and is very accurate yet responsive to server performance changes.
It's easier to understand than our previous display of "estimated time remaining", since the server might rapidly send you 10 pages in just a few seconds (which then counted down the "estimated time remaining" very quickly), before suddenly stalling for 30 seconds without any progress. It was still exactly as accurate as the new display method, but the new method is nicer to look at and easier to understand.
---
# v2.8.3.667
TradeSkillMaster: Revived
- Release Date: March 20th, 2023
- Author: Gnomezilla on Warmane-Icecrown [https://github.com/Bananaman].
## AuctionDB's "GetAll" Scans Can Now Be Disabled
You can now completely disable "GetAll" scans via the AuctionDB settings. This is useful if your server doesn't return all auctions in its "GetAll" results, which means that you'll get incorrect market value calculations for all items. If you're playing on such servers, it's best to disable the "GetAll" feature to avoid accidentally polluting your price database with incorrect data.
Go into TSM's main options, and then the "AuctionDB" section at the top. Click on the "Options" tab, and use the "Disable GetAll Auction Scans" checkbox. Disabling the "GetAll" feature will also hide its button from the auction scanning GUI.
This protection feature is disabled by default, since most casual players probably won't care that "GetAll" generates incorrect market values. However, it's an incredibly good feature for professional gold makers, who want to avoid all risks of calculating incorrect item prices.
Regardless of whether you use this feature or not, the AuctionDB scan GUI has also been cleaned up now, with much cleaner and more uniform spacing between the various scan buttons.
---
# v2.8.3.666
TradeSkillMaster: Revived
- Release Date: March 19th, 2023
- Author: Gnomezilla on Warmane-Icecrown [https://github.com/Bananaman].
## The Market Value Algorithm Actually Works Now
The new market value algorithm now actually calculates the market value.
Yes, seriously. For some reason, the previous algorithm author didn't think about sorting the auction scan data by "buyout price" before parsing it, which meant that all previous TSM calculations were completely incorrect. All previous versions of TSM generate absolutely batshit insane market prices, such as "1 Wool = 500 Gold", simply because the old algorithm was completely broken.
Therefore, TSM's core feature, the "dbmarket" value which is used in its automatic pricing algorithms, was completely broken and useless.
It has now been fixed. All market value calculations are now correct.
In fact, all of your old AuctionDB databases should be wiped and restarted from zero data with this new patch, so that you get rid of all old, corrupt market price data.
## Completely Rewritten Auction Scanning Algorithm
Rewritten, cleaned up and much faster auction scanning algorithm, which uses almost zero memory and NEVER causes any memory overflow crashes, unlike the old algorithm.
This new algorithm is 1.3x-27.3x faster depending on input data size, and on average 5x faster for most data. It uses less than 5% of the memory of the old algorithm. :)
If you want to see for yourself that the new algorithm is working correctly, you simply have to set `verifyNewAlgorithm = true` at the top of `TradeSkillMaster_AuctionDB/Modules/Scanning.lua`, and then run a "GetAll" scan to quickly fetch a huge amount of auctions. It will then process all of the data via both the old and new algorithms, and will display the speed improvements of the new algorithm. If there are any differences at all between the calculations of the two algorithms, it would tell you about that via an "error" message. But the algorithms behave completely identically, so you don't have to worry about that and won't see any errors. This verification feature was simply added because the algorithm rewrite is a massive change, and I am sure that some people will want to verify the complex new algorithm with their own eyes.
## Fixed Database Decoding Crash
Fixed a database decoding bug which caused TSM to crash anytime you attempted to decode data for a "corrupted" item in the database.
This bug usually manifested itself as people failing to perform auction house scans, since every "Full Scan" and "GetAll Scan" (but not "Group Scan") ends by decoding EVERY existing item in your current database (to reset their last-seen, cheapest "minBuyout" value to nil before processing the new data). This meant that the decode failures were usually triggered when people reached the end of their auction scans (but would also happen any other time when something attempts to decode data for that item, such as attempting to view the price-info tooltip of an item that has corrupt data in your database).
The cause of the random database corruption is pretty much impossible to find, but it's extremely rare (and it might even have been caused by people re-using databases from older versions of TSM). The fix simply ignores the corrupt "market day / scan" data for items that cannot be correctly decoded, thus purging those corrupt "days / scans" from the database automatically, while still rescuing their remaining non-corrupt data.
## Added Support for SharedMedia and Custom GUI Fonts
For some insane reason, TradeSkillMaster uses "Arial" as its default font, which is one of the ugliest fonts in the Universe.
Therefore, I've implemented LibSharedMedia support in TSM, which means that you now have access to a huge variety of fonts. By default, TSM will only list the game's own fonts, but you can install the [SharedMedia](https://github.com/bkader/SharedMedia#install) addon for WOTLK to add over 100 extra fonts that are now usable in TSM! Other addons you've installed may also be adding extra fonts to the LibSharedMedia collection!
To change TSM's font, simply type "/tsm", then go into the main tab, and then "Options". Scroll down to the bottom, until you see the new "Normal Font" and "Header Font" options. Have fun!
Note: TSM is unable to use SharedMedia fonts that are registered by addons that load AFTER TSM, since TSM accesses the fonts and creates its own GUI immediately when it loads, even before the user opens TSM's GUI. To use fonts from such an addon, you have to add that addon to "OptionalDeps" in "TradeSkillMaster.toc", which tells the game that you want TSM to load AFTER the addon that provides your extra fonts. This step isn't necessary for SharedMedia fonts, since they're already marked as an optional TSM dependency now.
## Made the "Group Scan" (AuctionDB) Progress Bar More Logical
The old progress bar for "Group Scans" used absolutely insane numbering and progress messages. It previously went: "Preparing Filter 1 / 2", "Preparing Filter 2 / 2", "Scanning 0 / 2 (Page 1 / 1)", "Scanning 0 / 2 (Page 2 / 16)", ... "Scanning 0 / 2 (Page 16 / 16)", "Scanning 1 / 2 (Page 1 / 1)", "Scanning 1 / 2 (Page 2 / 6)", ... "Scanning 1 / 2 (Page 6 / 6)", "Done Scanning".
It made absolutely zero sense, and was inconsistent with the behavior of other "counting" progress labels in TSM. There were two issues with it: The item scanner counted from 0, so item 0 was actually item 1, and item 1 was actually item 2, etc. The other issue is that it always said "Page 1 / 1" while requesting the first page, which was a very confusing and misleading display.
Both issues have now been fixed, and the exact same scan would now look as follows: "Preparing Filter 1 / 2", "Preparing Filter 2 / 2", "Scanning 1 / 2 (Page 1 / ?)", "Scanning 1 / 2 (Page 2 / 16)", ... "Scanning 1 / 2 (Page 16 / 16)", "Scanning 2 / 2 (Page 1 / ?)", "Scanning 2 / 2 (Page 2 / 6)", ... "Scanning 2 / 2 (Page 6 / 6)", "Done Scanning".
In other words, we now properly count the items we're processing, and we display a temporary question mark while we're fetching page 1 (while we don't know how many other pages exist), which then changes into the actual page count as the scan proceeds.
## Made the "Auctioning" Module's Progress Bar More Logical
This is similar to the "Group Scan" issues described above.
The "Auctioning" module's features (Post Scan / Cancel Scan / Reset Scan) had several issues.
They were counting items from 0, so the progres bar said "Scanning 0 / 2" for the first item, and "Scanning 1 / 2" for the final item. That has now been fixed and now counts properly (as "1/2" and "2/2").
They also had a completely broken page counter, which didn't properly reset between items, which meant that you'd see complete nonsense while scanning, such as "Scanning 3 / 5 (Page 5 / 5)" followed by "Scanning 4 / 5 (Page 5 / 5)" (keeping the previous page counter), and then the proper counter would finally show up as "Scanning 4 / 5 (Page 1 / 12)" after a few seconds (whenever the server finally replied to the query). It was broken and confusing as hell. That's now been fixed too, so that it properly resets the page counter before every new item it's scanning.
Furthermore, the confusing "Page 1 / 1 ... Page 2 / 18" issue has been fixed here too, and now says "Page 1 / ?" while fetching the first page of each item.
Lastly, their page counter was inconsistent with the behavior of regular "AuctionDB: Group Scan" and the "Shopping" progress bars. The page counter in "Auctioning" was zero-indexed, meaning that "Scanning Page 1/8" meant that you were actually scanning page 2/8. That math has now been fixed here, for consistency with the AuctionDB and Shopping scanner's math, because that's a much more logical way to display the pages.
## Made the "Shopping" Module's Progress Bar More Logical
The "Shopping" module's scanner had multiple issues.
The bar background was supposed to gradually "fill in" to indicate progress for both the "current/total item progress" and the "page progress of the current item". But the math was completely broken. It rendered the progress bar incorrectly, since it ended up counting the progress as "2 items below the item it's currently working on" (due to a mistaken "-1" in the old math). As a result, if you were currently scanning item 3 of 3, you SHOULD have seen a 66% full bar (since 2 of 3 items are done, and the 3rd is being worked on). But you instead only saw a 33% full bar, which was completely wrong. This was especially noticeable if you performed a multi-item search, such as "copper ore; copper bar; gold ore; gold bar".
They were also counting items from 0, so the progress bar said "Scanning 0 / 2" for the first item, and "Scanning 1 / 2" for the final item. That has now been fixed and now counts properly (as "1/2" and "2/2"), just like with the other modules above.
Lastly, the confusing "Page 1 / 1 ... Page 2 / 18" issue has been fixed here too, and now says "Page 1 / ?" while fetching the first page of each item.
## Accurate Time Estimate for the "Full Scan" Progress Bar
Implemented an accurate "time remaining" estimate for the "Full Scan" algorithm. The progress bar now shows how much time remains until the scan is complete, such as `Scanning page 293/2399 (~1:04:34)`, meaning that roughly one hour remains. This is very useful for people who want to use the comprehensive "Full Scan" scanning method, but who hate the feeling of blindly scanning without being able to see the remaining time.
The first time you run a scan on a server, you'll have a very unpredictable estimate, since we use a learning algorithm. The estimate is based on how fast the server is feeding us the "auction pages" on average, which is impossible to know on the first run. Most servers will gradually throttle the player's requests and use random load throttling, so you'll see a growing estimate until it finally settles on an accurate estimate after around 80% of the pages have been received. The slowdown curve depends on the server's throttling implementation, and cannot be predicted ahead of time.
Think of the first run as the "learning run". After you've completed a "Full Scan", our algorithm learns from it and will produce excellent estimates for all future scans, using a blending algorithm which takes into account both the active run's speed and previous speeds, to predict the server's behavior. The statistics are stored per-server and per-faction, so if you're playing both Alliance and Horde, you'll have to do one full scan per faction to teach it about both factions.
Note regarding the other scan types: The "GetAll Scan" algorithm only takes a few seconds, so it doesn't need any time estimate. And the "Group Scan" algorithm can't implement any estimate, since there's no way to know how many "auction pages" the remaining items in the group will have.
## Improved "GetAll" Algorithm Reliability
Most private servers don't implement WoW's "can we perform a GetAll Query?" API, and incorrectly answer that the user is allowed to run a query, even though you may still be throttled after a recent "GetAll" scan. Therefore, TSM's own "don't start a GetAll if we're on cooldown" check doesn't work on private servers. The user may therefore click on "Run GetAll Scan" and will then just blindly sit there like an idiot while the request has actually been secretly ignored by the server.
This situation has now been fixed, so that the "GetAll" scan UI tells the user when the server has silently throttled / ignored their "GetAll" request, so they don't sit there and wait for hours without any progress. Basically, if it's taken longer than 30 seconds, we're assuming that the request has been silently ignored by the server, and will now tell the user via the TSM GUI's progress bar message. It first says `Running query...` as usual, which then changes to `Running query... Server not responding due to throttling? Try again later...` if there hasn't been a server response in more than 30 seconds.
The "GetAll" algorithm has also been improved to automatically detect when your server is only sending limited / truncated data. Extremely large private servers such as Warmane simply have too many auctions, and their "GetAll" scan won't retrieve all of them. We now warn the user via a chat-message whenever we detect truncated auction data, and we then tell them to use "Full Scan" instead.
It's unfortunately impossible to calculate accurate market values if your server is sending truncated "GetAll" responses, but people may still prefer the speed of the "GetAll" scans, so we're therefore still allowing their "truncated" auction data to be processed. The users will have to read the warning message and decide for themselves which scans they want to use.
The inaccuracy of an incomplete "GetAll" scan completely depends on how the server implements "GetAll". If the server sorts the cheapest auctions first, then it's somewhat acceptable for popular items which have many stacks (and are therefore likely to contain enough data points in the "GetAll" result), and those items will "just" tend to be undervalued by 2-10% less than their real market value (since their percentile-based scans will look at less auctions than the real amount). But for very rare or unpopular items, the partial "GetAll" scan is very dangerous, since you might only receive the massively overpriced auctions of a certain item, and thereby calculate an insanely high market value for it. These dangers are increased if the server sends the auctions in a totally random order, which means an even greater risk of market price pollution by only seeing overpriced auctions for many of the items.
Furthermore, receiving incomplete "GetAll" results means that TSM will wipe all of its "cheapest, current buyout price" data for all items, and then fills them with incorrect data (or nothing at all if an item wasn't seen in the latest fetch), thus hindering your ability to look up the correct "best current buyout prices" too.
In summary, you'll have to use partial "GetAll" results at your own risk! A "Full Scan" is ALWAYS much better when your server's "GetAll" doesn't provide all auction data!
## Improved All Auction Data and Market Value Processing
There were several bugs everywhere in the old code. For example, only the "GetAll" scan had correctly implemented filtering of non-buyout items (auctions that ONLY have a bid price, without any buyout price), which meant that TSM's other scan methods ("Full Scan", "Group Scan", etc) were injecting invalid data into the player's database, such as items with empty "buyout prices". They were being treated as items with a buyout price of ZERO (as if they were totally free items, at no cost!). This serious bug happened every time when there were ANY bid-based auctions for an item in the latest scan.
All of the auction scanning and processing algorithms have been rewritten for correctness, and will now filter out those useless "bid-only" data entries. We will only analyze auctions that have buyout prices.
Several bugs have also been fixed related to items being incorrectly marked as "seen and scanned now" even when there was no new data for that item in the latest data batch (this was yet again related to the "incoming data only has bid-based auctions" bug). We now only update the scan-statistics for items when we encounter new data for them with valid buyout prices.
There were also bugs in the market value calculation algorithm, which would crash with "division by zero" if given empty input data (yet again related to only having bid-based auctions in the input data, but this crash only happened if there were only bid-based auctions for an item, without any buyout-based auctions whatsoever for that item, so it was rare to encounter this crash). That issue has also been fixed now, by adding perfect protection against empty inputs in the market value calculation algorithm.
We also no longer update the market value for an item if we were unable to calculate a new market value (the old code simply wrote invalid market values to the database without any sanity checks).
All of these improvements mean that TSM's market price estimates are now infinitely more reliable than old TSM versions.
## Fixed Missing "Interruption" Events in Auction Scanners
This is a great example of what a total mess the TSM codebase is...
All auction scanners (AuctionDB, Auctioning and Shopping) were missing their "scan interrupted" event, which is triggered when the Auction House frame closes while a scan is ongoing.
The problem was caused by the fact that TSM previously used the "LibAuctionScan-1.0" library, which emits the "SCAN_INTERRUPTED" event. All TSM scanners were still written to react to that old event name. But TSM nowadays uses their own scanning library instead, which sends the "INTERRUPTED" event instead. They literally forgot to update the event handlers when they switched the library, and they didn't even bother deleting the old library either.
All three scanning methods have now been fixed, to react to the proper "INTERRUPTED" event, to be able to detect when your scans are interrupted by the auction window being closed.
Furthermore, the "Shopping" module hadn't implemented interruption handling whatsoever. There literally wasn't any event handler for it. It has now been properly implemented, so that the "Shopping" scanner works properly and correctly resets itself after interruptions.
## IMPORTANT!
When upgrading to this improved version, it's an extremely good idea to delete your old-school, CORRUPT auction database, since your old data has been generated by the extremely buggy algorithms of the old TSM. For example, some of those old "auction data corrupting" bugs include the fact that it was calculating completely incorrect market values when doing "GetAll" scans, since it was previously unable to handle per-item prices inside item stacks, and another bug is the fact that all other scan methods generated wrong market values too, thanks to TSM's previously completely broken market value algorithm (those problems are described in the other changelog entries above).
There is NO GOOD REASON to keep your old auction database. It's all corrupt thanks to all the old TSM bugs, 100% guaranteed. You need to start fresh.
If you're ready to start fresh, simply delete these files (where `YOUR_ACCOUNT_NAME` is your own account name):
```
./WoW/WTF/Account/YOUR_ACCOUNT_NAME/SavedVariables/TradeSkillMaster_AuctionDB.lua.bak
./WoW/WTF/Account/YOUR_ACCOUNT_NAME/SavedVariables/TradeSkillMaster_AuctionDB.lua
```
Then do a "Full Scan" in the game, and you'll be sure that all data from now on is processed using more intelligent algorithms.
All serious bugs are fixed now. You can finally relax and enjoy TSM.
---
v2.8.3
*Made TSMAPI:IsSoulbound() MUCH more efficient which will GREATLY reduce the interface log of all modules..

71
TradeSkillMaster/Core/Options.lua

@ -11,6 +11,7 @@
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local LSM = LibStub("LibSharedMedia-3.0") -- load the SharedMedia library
local lib = TSMAPI
local private = {}
@ -132,6 +133,11 @@ function private:LoadHelpPage(parent)
text = TSMAPI.Design:ColorText(L["Co-Founder:"], "link") .. " Cente [US-Illidan(H)]",
relativeWidth = 1,
},
{
type = "Label",
text = TSMAPI.Design:ColorText(L["Wrath of the Lich King Revival:"], "link") .. " Gnomezilla [Warmane-Icecrown(A)], BlueAo [Warmane], andrew6180, Yoshiyuka, DimaSheiko, and other contributors...",
relativeWidth = 1,
},
},
},
},
@ -224,12 +230,23 @@ end
local function DecodeAppearanceData(encodedData)
if not encodedData then return end
encodedData = gsub(encodedData, " ", "")
-- Decode the appearance-data string into a table.
encodedData = gsub(encodedData, " ", "")
local result = StringToTable(encodedData, 1)
if not result then return TSM:Print(L["Invalid appearance data."]) end
-- Preserve the user's current font choices and switch the theme.
-- NOTE: The "design" table won't exist on first TSM launch, while we're
-- loading the default design, so we also check the "design" table's existence.
local preservedFonts = TSM.db.profile.design and TSM.db.profile.design.fonts or nil
TSM.db.profile.design = result
TSM.db.profile.design.fonts = preservedFonts -- Is allowed to be nil.
-- Use default values for any missing fields (such as fonts, if missing).
TSM:SetDesignDefaults(TSM.designDefaults, TSM.db.profile.design)
-- Apply the new design.
TSMAPI:UpdateDesign()
end
@ -762,7 +779,59 @@ function private:LoadOptionsPage(parent)
tinsert(page[1].children[4].children, { type = "HeadingLine" })
-- Figure out which fonts to automatically select in the dropdown.
-- NOTE: We visually fall back to our defaults if the stored names are
-- invalid, to avoid confusion if user-fonts are temporarily unavailable.
local availableFontTitlesList = LSM:List("font")
local selectedContentFontIndex = -1
local selectedBoldFontIndex = -1
local arialNarrowFontIndex = 1
local tsmDroidBoldFontIndex = 1
for i,v in ipairs(availableFontTitlesList) do
if v == "Arial Narrow" then
arialNarrowFontIndex = i
elseif v == "TSM Droid Sans Bold" then
tsmDroidBoldFontIndex = i
end
if v == TSM.db.profile.design.fonts.content then
selectedContentFontIndex = i
elseif v == TSM.db.profile.design.fonts.bold then
selectedBoldFontIndex = i
end
end
if selectedContentFontIndex == -1 then
selectedContentFontIndex = arialNarrowFontIndex
end
if selectedBoldFontIndex == -1 then
selectedBoldFontIndex = tsmDroidBoldFontIndex
end
local miscWidgets = {
{
type = "Dropdown",
relativeWidth = 0.5,
label = L["Normal Font (Requires Reload)"],
list = availableFontTitlesList,
value = selectedContentFontIndex,
callback = function(_, _, index)
local fontTitle = availableFontTitlesList[index]
TSM.db.profile.design.fonts.content = fontTitle
end,
tooltip = format(L["Default: \"%s\"."], "Arial Narrow"),
},
{
type = "Dropdown",
relativeWidth = 0.5,
label = L["Header Font (Requires Reload)"],
list = availableFontTitlesList,
value = selectedBoldFontIndex,
callback = function(_, _, index)
local fontTitle = availableFontTitlesList[index]
TSM.db.profile.design.fonts.bold = fontTitle
end,
tooltip = format(L["Default: \"%s\"."], "TSM Droid Sans Bold"),
},
{
type = "Slider",
relativeWidth = 0.5,

41
TradeSkillMaster/GUI/Design.lua

@ -10,11 +10,15 @@
local TSM = select(2, ...)
local lib = TSMAPI
local LSM = LibStub("LibSharedMedia-3.0") -- load the SharedMedia library
TSMAPI.Design = {}
local Design = TSMAPI.Design
local coloredFrames = {}
local coloredTexts = {}
local LSM_Fonts = LSM:HashTable("font") -- Persistent hashtable of all registered LSM fonts.
--[[-----------------------------------------------------------------------------
Support functions
@ -88,15 +92,44 @@ function Design:SetTitleTextColor(obj)
return SetTextColor(obj, "title")
end
--- Primary content font.
function Design:GetContentFont(size)
-- Retrieve the user's desired font size, if the "size" specifier is valid.
size = size or "normal"
TSM.db.profile.design.fontSizes[size] = TSM.db.profile.design.fontSizes[size] or TSM.designDefaults.fontSizes[size]
assert(TSM.db.profile.design.fontSizes[size], format("Invalid font size '%s", tostring(size)))
return TSM.db.profile.design.fonts.content, TSM.db.profile.design.fontSizes[size]
local size_number = TSM.db.profile.design.fontSizes[size]
if not size_number then
if TSM.designDefaults.fontSizes[size] then
-- Save the default size to user's settings, for faster lookups.
TSM.db.profile.design.fontSizes[size] = TSM.designDefaults.fontSizes[size]
size_number = TSM.db.profile.design.fontSizes[size]
end
assert(size_number, format("Invalid font size '%s", tostring(size)))
end
-- Verify existence of user's chosen font, and do a "soft revert" to our defaults
-- if missing, which can happen if previous SharedMedia fonts have been uninstalled.
-- NOTE: We don't overwrite the database value, in case the user re-enables that font.
local content_font = LSM_Fonts[TSM.db.profile.design.fonts.content]
if not content_font then
content_font = LSM_Fonts["Arial Narrow"]
end
-- Return the path to the font, and the desired (user-configurable) size.
return content_font, size_number
end
--- Header/section title font.
function Design:GetBoldFont()
return TSM.db.profile.design.fonts.bold
-- Verify existence of user's chosen font, and do a "soft revert" to our defaults
-- if missing, which can happen if previous SharedMedia fonts have been uninstalled.
-- NOTE: We don't overwrite the database value, in case the user re-enables that font.
local bold_font = LSM_Fonts[TSM.db.profile.design.fonts.bold]
if not bold_font then
bold_font = LSM_Fonts["TSM Droid Sans Bold"]
end
-- Return the path to the font.
return bold_font
end
function Design:GetInlineColor(key)

4
TradeSkillMaster/GUI/MainFrame.lua

@ -29,10 +29,10 @@ function TSMAPI:OpenFrame()
if TSM.db.global.infoMessage < 1001 then
TSM.db.global.infoMessage = 1001
StaticPopupDialogs["TSM_INFO_MESSAGE"] = StaticPopupDialogs["TSM_INFO_MESSAGE"] or {
text = format(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced")),
text = format(L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced")),
button1 = OKAY,
OnAccept = function()
TSM:Printf(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced"))
TSM:Printf(L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced"))
end,
timeout = 0,
preferredIndex = 3,

245
TradeSkillMaster/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua

@ -0,0 +1,245 @@
--[[
Name: LibSharedMedia-3.0
Revision: $Revision: 62 $
Author: Elkano (elkano@gmx.de)
Inspired By: SurfaceLib by Haste/Otravi (troeks@gmail.com)
Website: http://www.wowace.com/projects/libsharedmedia-3-0/
Description: Shared handling of media data (fonts, sounds, textures, ...) between addons.
Dependencies: LibStub, CallbackHandler-1.0
License: LGPL v2.1
]]
local MAJOR, MINOR = "LibSharedMedia-3.0", 3030001 -- 3.3.5 / increase manually on changes
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end
local _G = getfenv(0)
local pairs = _G.pairs
local type = _G.type
local band = _G.bit.band
local table_sort = _G.table.sort
local locale = GetLocale()
local locale_is_western
local LOCALE_MASK = 0
lib.LOCALE_BIT_koKR = 1
lib.LOCALE_BIT_ruRU = 2
lib.LOCALE_BIT_zhCN = 4
lib.LOCALE_BIT_zhTW = 8
lib.LOCALE_BIT_western = 128
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
lib.callbacks = lib.callbacks or CallbackHandler:New(lib)
lib.DefaultMedia = lib.DefaultMedia or {}
lib.MediaList = lib.MediaList or {}
lib.MediaTable = lib.MediaTable or {}
lib.MediaType = lib.MediaType or {}
lib.OverrideMedia = lib.OverrideMedia or {}
local defaultMedia = lib.DefaultMedia
local mediaList = lib.MediaList
local mediaTable = lib.MediaTable
local overrideMedia = lib.OverrideMedia
-- create mediatype constants
lib.MediaType.BACKGROUND = "background" -- background textures
lib.MediaType.BORDER = "border" -- border textures
lib.MediaType.FONT = "font" -- fonts
lib.MediaType.STATUSBAR = "statusbar" -- statusbar textures
lib.MediaType.SOUND = "sound" -- sound files
-- populate lib with default Blizzard data
-- BACKGROUND
if not lib.MediaTable.background then lib.MediaTable.background = {} end
lib.MediaTable.background["None"] = [[]]
lib.MediaTable.background["Blizzard Dialog Background"] = [[Interface\DialogFrame\UI-DialogBox-Background]]
lib.MediaTable.background["Blizzard Dialog Background Dark"] = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]]
lib.MediaTable.background["Blizzard Dialog Background Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Background]]
lib.MediaTable.background["Blizzard Low Health"] = [[Interface\FullScreenTextures\LowHealth]]
lib.MediaTable.background["Blizzard Marble"] = [[Interface\FrameGeneral\UI-Background-Marble]]
lib.MediaTable.background["Blizzard Out of Control"] = [[Interface\FullScreenTextures\OutOfControl]]
lib.MediaTable.background["Blizzard Parchment"] = [[Interface\AchievementFrame\UI-Achievement-Parchment-Horizontal]]
lib.MediaTable.background["Blizzard Parchment 2"] = [[Interface\AchievementFrame\UI-GuildAchievement-Parchment-Horizontal]]
lib.MediaTable.background["Blizzard Rock"] = [[Interface\FrameGeneral\UI-Background-Rock]]
lib.MediaTable.background["Blizzard Tabard Background"] = [[Interface\TabardFrame\TabardFrameBackground]]
lib.MediaTable.background["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Background]]
lib.MediaTable.background["Solid"] = [[Interface\Buttons\WHITE8X8]]
lib.DefaultMedia.background = "None"
-- BORDER
if not lib.MediaTable.border then lib.MediaTable.border = {} end
lib.MediaTable.border["None"] = [[]]
lib.MediaTable.border["Blizzard Achievement Wood"] = [[Interface\AchievementFrame\UI-Achievement-WoodBorder]]
lib.MediaTable.border["Blizzard Chat Bubble"] = [[Interface\Tooltips\ChatBubble-Backdrop]]
lib.MediaTable.border["Blizzard Dialog"] = [[Interface\DialogFrame\UI-DialogBox-Border]]
lib.MediaTable.border["Blizzard Dialog Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Border]]
lib.MediaTable.border["Blizzard Party"] = [[Interface\CHARACTERFRAME\UI-Party-Border]]
lib.MediaTable.border["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Border]]
lib.DefaultMedia.border = "None"
-- FONT
if not lib.MediaTable.font then lib.MediaTable.font = {} end
local SML_MT_font = lib.MediaTable.font
if locale == "koKR" then
LOCALE_MASK = lib.LOCALE_BIT_koKR
--
SML_MT_font["굵은 글꼴"] = [[Fonts\2002B.TTF]]
SML_MT_font["기본 글꼴"] = [[Fonts\2002.TTF]]
SML_MT_font["데미지 글꼴"] = [[Fonts\K_Damage.TTF]]
SML_MT_font["퀘스트 글꼴"] = [[Fonts\K_Pagetext.TTF]]
--
lib.DefaultMedia["font"] = "기본 글꼴" -- someone from koKR please adjust if needed
--
elseif locale == "zhCN" then
LOCALE_MASK = lib.LOCALE_BIT_zhCN
--
SML_MT_font["伤害数字"] = [[Fonts\ZYKai_C.ttf]]
SML_MT_font["默认"] = [[Fonts\ZYKai_T.ttf]]
SML_MT_font["聊天"] = [[Fonts\ZYHei.ttf]]
--
lib.DefaultMedia["font"] = "默认" -- someone from zhCN please adjust if needed
--
elseif locale == "zhTW" then
LOCALE_MASK = lib.LOCALE_BIT_zhTW
--
SML_MT_font["提示訊息"] = [[Fonts\bHEI00M.ttf]]
SML_MT_font["聊天"] = [[Fonts\bHEI01B.ttf]]
SML_MT_font["傷害數字"] = [[Fonts\bKAI00M.ttf]]
SML_MT_font["預設"] = [[Fonts\bLEI00D.ttf]]
--
lib.DefaultMedia["font"] = "預設" -- someone from zhTW please adjust if needed
elseif locale == "ruRU" then
LOCALE_MASK = lib.LOCALE_BIT_ruRU
--
SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]]
SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT__.TTF]]
SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS.TTF]]
SML_MT_font["Nimrod MT"] = [[Fonts\NIM_____.ttf]]
SML_MT_font["Skurri"] = [[Fonts\SKURRI.TTF]]
--
lib.DefaultMedia.font = "Arial Narrow"
--
else
LOCALE_MASK = lib.LOCALE_BIT_western
locale_is_western = true
--
SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]]
SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT__.TTF]]
SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS.TTF]]
SML_MT_font["Skurri"] = [[Fonts\SKURRI.TTF]]
--
lib.DefaultMedia.font = "Friz Quadrata TT"
--
end
-- STATUSBAR
if not lib.MediaTable.statusbar then lib.MediaTable.statusbar = {} end
lib.MediaTable.statusbar["Blizzard"] = [[Interface\TargetingFrame\UI-StatusBar]]
lib.MediaTable.statusbar["Blizzard Character Skills Bar"] = [[Interface\PaperDollInfoFrame\UI-Character-Skills-Bar]]
lib.DefaultMedia.statusbar = "Blizzard"
-- SOUND
if not lib.MediaTable.sound then lib.MediaTable.sound = {} end
lib.MediaTable.sound["None"] = [[Interface\Quiet.ogg]] -- Relies on the fact that PlaySound[File] doesn't error on these values.
lib.DefaultMedia.sound = "None"
local function rebuildMediaList(mediatype)
local mtable = mediaTable[mediatype]
if not mtable then return end
if not mediaList[mediatype] then mediaList[mediatype] = {} end
local mlist = mediaList[mediatype]
-- list can only get larger, so simply overwrite it
local i = 0
for k in pairs(mtable) do
i = i + 1
mlist[i] = k
end
table_sort(mlist)
end
function lib:Register(mediatype, key, data, langmask)
if type(mediatype) ~= "string" then
error(MAJOR..":Register(mediatype, key, data, langmask) - mediatype must be string, got "..type(mediatype))
end
if type(key) ~= "string" then
error(MAJOR..":Register(mediatype, key, data, langmask) - key must be string, got "..type(key))
end
mediatype = mediatype:lower()
if mediatype == lib.MediaType.FONT and ((langmask and band(langmask, LOCALE_MASK) == 0) or not (langmask or locale_is_western)) then
-- ignore fonts that aren't flagged as supporting local glyphs on non-western clients
return false
end
if mediatype == lib.MediaType.SOUND and type(data) == "string" then
local path = data:lower()
if not path:find(".ogg", nil, true) and not path:find(".mp3", nil, true) and not path:find(".wav", nil, true) then
-- Only wav, ogg and mp3 are valid sounds.
return false
end
end
if not mediaTable[mediatype] then mediaTable[mediatype] = {} end
local mtable = mediaTable[mediatype]
if mtable[key] then return false end
mtable[key] = data
rebuildMediaList(mediatype)
self.callbacks:Fire("LibSharedMedia_Registered", mediatype, key)
return true
end
function lib:Fetch(mediatype, key, noDefault)
local mtt = mediaTable[mediatype]
local overridekey = overrideMedia[mediatype]
local result = mtt and ((overridekey and mtt[overridekey] or mtt[key]) or (not noDefault and defaultMedia[mediatype] and mtt[defaultMedia[mediatype]])) or nil
return result ~= "" and result or nil
end
function lib:IsValid(mediatype, key)
return mediaTable[mediatype] and (not key or mediaTable[mediatype][key]) and true or false
end
function lib:HashTable(mediatype)
return mediaTable[mediatype]
end
function lib:List(mediatype)
if not mediaTable[mediatype] then
return nil
end
if not mediaList[mediatype] then
rebuildMediaList(mediatype)
end
return mediaList[mediatype]
end
function lib:GetGlobal(mediatype)
return overrideMedia[mediatype]
end
function lib:SetGlobal(mediatype, key)
if not mediaTable[mediatype] then
return false
end
overrideMedia[mediatype] = (key and mediaTable[mediatype][key]) and key or nil
self.callbacks:Fire("LibSharedMedia_SetGlobal", mediatype, overrideMedia[mediatype])
return true
end
function lib:GetDefault(mediatype)
return defaultMedia[mediatype]
end
function lib:SetDefault(mediatype, key)
if mediaTable[mediatype] and mediaTable[mediatype][key] and not defaultMedia[mediatype] then
defaultMedia[mediatype] = key
return true
else
return false
end
end

2
TradeSkillMaster/Locale/deDE.lua

@ -317,7 +317,7 @@ L["Module Information:"] = "Modulinformation:"
L["Module Operations / Options"] = "Modul Operationen / Optionen"
-- L["Modules"] = ""
L["More Advanced Methods"] = "Fortgeschrittene Optionen"
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "Verschiebe bereits gruppierte Gegenstände"
L["Moved %s to %s."] = "Verschiebe %s zu %s"
L["Move Group"] = "Verschiebe Gruppe"

7
TradeSkillMaster/Locale/enUS.lua

@ -155,6 +155,7 @@ L["Default BankUI Tab"] = true
L["Default Group Tab"] = true
L["Default Tab (Open Auction House to Enable)"] = true
L["Default Tab"] = true
L["Default: \"%s\"."] = true
L["Default"] = true
L["Delete a Profile"] = true
L["Delete Custom Price Source"] = true
@ -235,6 +236,7 @@ L["Group names cannot contain %s characters."] = true
L["Group:"] = true
L["Group(Base Item):"] = true
L["Groups"] = true
L["Header Font (Requires Reload)"] = true
L["Help"] = true
L["Here you can setup relationships between the settings of this operation and other operations for this module. For example, if you have a relationship set to OperationA for the stack size setting below, this operation's stack size setting will always be equal to OperationA's stack size setting."] = true
L["Hide Minimap Icon"] = true
@ -335,7 +337,7 @@ L["Module Information:"] = true
L["Module Operations / Options"] = true
L["Modules"] = true
L["More Advanced Methods"] = true
L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = true
L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = true
L["Move Already Grouped Items"] = true
L["Move Group"] = true
L["Move to Top Level"] = true
@ -351,6 +353,7 @@ L["New Subgroup Name"] = true
L["New"] = true
L["No Assistant guides available for the modules which you have installed."] = true
L["None of your groups have %s operations assigned. Type '/tsm' and click on the 'TradeSkillMaster Groups' button to assign operations to your TSM groups."] = true
L["Normal Font (Requires Reload)"] = true
L["Normal Text Size (Requires Reload)"] = true
L["Now that the scan is finished, you can look through the results shown in the log, and for each item, decide what action you want to take.\n\nOnce you're done, click on the button below."] = true
L["Number of Auction Result Rows (Requires Reload)"] = true
@ -580,10 +583,10 @@ L["We will add items to this group through its 'Items' tab. Click on that tab no
L["We will import items into this group using the import list you have."] = true
L["Web Master:"] = true
L["What do you want to do?"] = true
L["What do you want to do?"] = true
L["When checked, random enchants will be ignored for ungrouped items.\n\nNB: This will not affect parent group items that were already added with random enchants\n\nIf you have this checked when adding an ungrouped randomly enchanted item, it will act as all possible random enchants of that item."] = true
L["When clicked, makes this group a top-level group with no parent."] = true
L["Would you like to add this new operation to %s?"] = true
L["Wrath of the Lich King Revival:"] = true
L["Wrong number of item links."] = true
L["You appear to be attempting to import an operation from a different module."] = true
L["You can change the active database profile, so you can have different settings for every character."] = true

2
TradeSkillMaster/Locale/esES.lua

@ -321,7 +321,7 @@ L["Module Information:"] = "Información de Módulo:"
L["Module Operations / Options"] = "Módulo Operaciones / Opciones"
L["Modules"] = "Módulos" -- Needs review
L["More Advanced Methods"] = "Mas Metodos Avanzados"
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "Mover los objetos ya en grupo"
L["Moved %s to %s."] = "Movido %s a %s"
L["Move Group"] = "Mover Grupo"

2
TradeSkillMaster/Locale/esMX.lua

@ -315,7 +315,7 @@ Once you're done, click the button below.]=] ] = "" ]==]
-- L["Module Operations / Options"] = ""
-- L["Modules"] = ""
-- L["More Advanced Methods"] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
-- L["Move Already Grouped Items"] = ""
-- L["Moved %s to %s."] = ""
-- L["Move Group"] = ""

2
TradeSkillMaster/Locale/frFR.lua

@ -315,7 +315,7 @@ L["Module Information:"] = "Informations sur le module:" -- Needs review
L["Module Operations / Options"] = "Opérations / Options du module" -- Needs review
-- L["Modules"] = ""
L["More Advanced Methods"] = "Méthodes plus avancées" -- Needs review
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
-- L["Move Already Grouped Items"] = ""
-- L["Moved %s to %s."] = ""
L["Move Group"] = "Déplacer le groupe" -- Needs review

2
TradeSkillMaster/Locale/koKR.lua

@ -315,7 +315,7 @@ L["Module Information:"] = "모듈 정보:" -- Needs review
L["Module Operations / Options"] = "모듈 작업 / 옵션" -- Needs review
-- L["Modules"] = ""
L["More Advanced Methods"] = "좀더 고급 방식" -- Needs review
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "이미 그룹화된 아이템 이동" -- Needs review
L["Moved %s to %s."] = "%s을(를) %s으로 이동했습니다." -- Needs review
L["Move Group"] = "그룹 이동" -- Needs review

2
TradeSkillMaster/Locale/ptBR.lua

@ -318,7 +318,7 @@ L["Module Information:"] = "Informações do Módulo:" -- Needs review
L["Module Operations / Options"] = "Módulo de operações/opções" -- Needs review
-- L["Modules"] = ""
L["More Advanced Methods"] = "Métodos mais avançados"
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "Mover itens já agrupados"
L["Moved %s to %s."] = "Movido %s para %s."
L["Move Group"] = "Mover grupo" -- Needs review

2
TradeSkillMaster/Locale/ruRU.lua

@ -317,7 +317,7 @@ L["Module Information:"] = "Информация о модуле:"
L["Module Operations / Options"] = "Операции/Настройки модуля"
-- L["Modules"] = ""
L["More Advanced Methods"] = "Более совершенных методов"
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "Перемещать уже Группированные предметы"
L["Moved %s to %s."] = "Переместить %s к %s."
L["Move Group"] = "Переместить Группу"

2
TradeSkillMaster/Locale/zhCN.lua

@ -317,7 +317,7 @@ L["Module Information:"] = "模块信息:"
L["Module Operations / Options"] = "模块操作/选项"
L["Modules"] = "模块"
L["More Advanced Methods"] = "更多高级模式"
L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = "更多高级选项指定%sred文本|r。不建议初学者使用。"
L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = "更多高级选项指定%sred文本|r。不建议初学者使用。"
L["Move Already Grouped Items"] = "移动已存在分组内物品"
L["Moved %s to %s."] = "移动 %s 到 %s 。"
L["Move Group"] = "移动分组"

2
TradeSkillMaster/Locale/zhTW.lua

@ -315,7 +315,7 @@ L["Module Information:"] = "模組資訊:"
L["Module Operations / Options"] = "模組操作/選項"
-- L["Modules"] = ""
L["More Advanced Methods"] = "更多高級模式"
-- L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."] = ""
-- L["More advanced options are now designated by %sred text|r. Beginners are encouraged to come back to these once they have a solid understanding of the basics."] = ""
L["Move Already Grouped Items"] = "移動已存在分組內物品"
L["Moved %s to %s."] = "移動 %s 到 %s 。"
L["Move Group"] = "移動分組"

39
TradeSkillMaster/TradeSkillMaster.lua

@ -16,9 +16,19 @@ TSM.moduleNames = {}
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
TSM._version = GetAddOnMetadata("TradeSkillMaster", "Version") -- current version of the addon
local LSM = LibStub("LibSharedMedia-3.0") -- load the SharedMedia library
TSMAPI = {}
-- We must register all custom fonts we want here, since we're loaded before the other TSM modules.
local LSM_Fonts = LSM:HashTable("font") -- Persistent hashtable of all registered LSM fonts.
if not LSM_Fonts["Arial Narrow"] then
-- LSM only registers it for western locales, so enforce its registration for all locales.
LSM:Register("font", "Arial Narrow", [[Fonts\ARIALN.TTF]])
end
LSM:Register("font", "TSM Droid Sans Bold", [[Interface\Addons\TradeSkillMaster\Media\DroidSans-Bold.ttf]]) -- Our own custom font.
-- Default TSM design settings.
TSM.designDefaults = {
frameColors = {
frameBG = { backdrop = { 24, 24, 24, .93 }, border = { 30, 30, 30, 1 } },
@ -42,8 +52,8 @@ TSM.designDefaults = {
},
edgeSize = 1.5,
fonts = {
content = "Fonts\\ARIALN.TTF",
bold = "Interface\\Addons\\TradeSkillMaster\\Media\\DroidSans-Bold.ttf",
content = "Arial Narrow",
bold = "TSM Droid Sans Bold",
},
fontSizes = {
normal = 15,
@ -155,11 +165,36 @@ function TSM:OnInitialize()
-- add this character to the list of characters on this realm
TSM.db.factionrealm.characters[UnitName("player")] = true
-- Initialize default design, and apply defaults for any missing DB values.
-- NOTE: We allow missing fonts (such as uninstalled SharedMedia fonts), and
-- simply do a soft fallback to our defaults in our font lookup functions
-- later (in "GUI/Design.lua"), to support the scenario where the user has
-- selected fonts which are registered by a SharedMedia-based addon which
-- may be temporarily disabled.
-- NOTE: TSM is unable to use SharedMedia fonts that are registered by addons
-- that load AFTER TSM, since TSM accesses the fonts and creates its own GUI
-- immediately when it loads, even before the user opens TSM's GUI. To use
-- fonts from such an addon, you have to add that addon to "OptionalDeps"
-- in "TradeSkillMaster.toc", which tells the game that you want TSM to load
-- AFTER the addon that provides your extra fonts. This step isn't necessary
-- for SharedMedia fonts, since they're already marked as an optional TSM
-- dependency now.
if not TSM.db.profile.design then
TSM:LoadDefaultDesign()
end
TSM:SetDesignDefaults(TSM.designDefaults, TSM.db.profile.design)
-- If the user is migrating from an old TSM version, convert their static
-- font paths into our modern font name representations instead.
-- NOTE: Our font system can handle fallbacks for these "missing fonts", but
-- this method cleans up their old database and modernizes it, which is nicer.
if TSM.db.profile.design.fonts.content == "Fonts\\ARIALN.TTF" then
TSM.db.profile.design.fonts.content = "Arial Narrow"
end
if TSM.db.profile.design.fonts.bold == "Interface\\Addons\\TradeSkillMaster\\Media\\DroidSans-Bold.ttf" then
TSM.db.profile.design.fonts.bold = "TSM Droid Sans Bold"
end
-- create / register the minimap button
TSM.LDBIcon = LibStub("LibDataBroker-1.1", true) and LibStub("LibDBIcon-1.0", true)
local TradeSkillMasterLauncher = LibStub("LibDataBroker-1.1", true):NewDataObject("TradeSkillMasterMinimapIcon", {

13
TradeSkillMaster/TradeSkillMaster.toc

@ -1,12 +1,14 @@
## Interface: 30300
## Title: |cff00fe00TradeSkillMaster|r
## Notes: Core addon for the TradeSkillMaster suite. Does nothing without modules installed.
## Author: Sapu94, Bart39
## Version: v2.8.3
## Title: |cff00fe00TradeSkillMaster: Revived|r
## Notes: Core addon for the TradeSkillMaster suite, revived for Wrath of the Lich King. Does nothing without modules installed.
## Author: Sapu94, Bart39, Gnomezilla [Warmane-Icecrown(A)], BlueAo [Warmane], andrew6180, Yoshiyuka, DimaSheiko, and other contributors...
## Version: Rev668
## SavedVariables: TradeSkillMasterAppDB, TradeSkillMasterDB
## OptionalDeps: AccurateTime, Ace3, LibDataBroker-1.1, LibDBIcon-1.0, LibExtraTip, TipHelper, LibParse, LibCompress, LibGraph-2.0, TheUndermineJournal, TheUndermineJournalGE
## OptionalDeps: AccurateTime, Ace3, LibDataBroker-1.1, LibDBIcon-1.0, LibExtraTip, TipHelper, LibParse, LibCompress, LibGraph-2.0, SharedMedia, TheUndermineJournal, TheUndermineJournalGE
## X-Embeds: AccurateTime, Ace3, LibDataBroker-1.1, LibDBIcon-1.0, LibExtraTip, TipHelper, LibParse, LibCompress, LibGraph-2.0
# TSM Revived is based on TSM v2.8.3.
#@no-lib-strip@
Libs\LibStub\LibStub.lua
Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
@ -32,6 +34,7 @@ Libs\LibChatAnims\LibChatAnims.xml
## Embeded libraries not on CurseForge
Libs\LibExtraTip\Load.xml
Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua
Locale\enUS.lua
Locale\deDE.lua

40
TradeSkillMaster/Util/Util.lua

@ -13,6 +13,25 @@ local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Util_private")
-- Locals to speed up function access.
local CopyTable = CopyTable
local error = error
local floor = floor
local format = format
local GetRealmName = GetRealmName
local gsub = gsub
local ipairs = ipairs
local select = select
local StaticPopup_Show = StaticPopup_Show
local strfind = strfind
local strlower = strlower
local strsub = strsub
local tinsert = tinsert
local tremove = tremove
local type = type
local UnitName = UnitName
--- Shows a popup dialog with the given name and ensures it's visible over the TSM frame by setting the frame strata to TOOLTIP.
-- @param name The name of the static popup dialog to be shown.
function TSMAPI:ShowStaticPopupDialog(name)
@ -98,3 +117,24 @@ end
function TSMAPI:IsPlayer(target)
return strlower(target) == strlower(UnitName("player")) or (strfind(target, "-") and strlower(target) == strlower(UnitName("player").."-"..GetRealmName()))
end
--- Converts seconds into hours, minutes and seconds.
-- @param seconds Number of seconds. Can be a float (will automatically be rounded down to whole seconds).
-- NOTE: The return values of this function can be wrapped directly by a call to "TSMAPI:FormatHMS()".
function TSMAPI:SecondsToHMS(seconds)
-- NOTE: This code can be shorter, but was written this way for efficiency.
local hours = floor(seconds / 3600)
seconds = seconds - (hours * 3600)
local minutes = floor(seconds / 60)
seconds = floor(seconds - (minutes * 60))
return hours, minutes, seconds
end
--- Converts hours, minutes and seconds into a human-readable string.
-- @param hours Hours. Must be a whole number without decimals.
-- @param minutes Minutes. Must be a whole number without decimals.
-- @param seconds Seconds. Must be a whole number without decimals.
function TSMAPI:FormatHMS(hours, minutes, seconds)
return format("%d:%02d:%02d", hours, minutes, seconds)
end

2
TradeSkillMaster_AuctionDB/Locale/deDE.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "deDE
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "Ein voller Auktionshausscan wird jedes einzelne Item im Auktionshaus scannen, ist aber sehr viel langsamer als der GetAll-Scan. Erwarte, dass es mehrere Minuten dauert oder länger."
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = "Ein GetAll-Scan ist die schnellste Methode, um in-game alle Gegenstände im Auktionshaus zu scannen. Allerdings gibt es viele mögliche Bugs seitens Blizzard die auftreten können, inklusive der Möglichkeit, dass deine Verbindung zum Spiel getrennt wird. Außerdem gibt es einen 15-Minuten-Cooldown."
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = "Ein GetAll-Scan ist die schnellste Methode, um in-game alle Gegenstände im Auktionshaus zu scannen. Allerdings gibt es viele mögliche Bugs seitens Blizzard die auftreten können, inklusive der Möglichkeit, dass deine Verbindung zum Spiel getrennt wird. Außerdem gibt es einen 15-Minuten-Cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server." -- Needs review
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "Es werden alle Gegenstände in der \"AuctionDB\" Datenbank angezeigt, deren Namen mit der Sucheingabe übereinstimmen."
L["Are you sure you want to clear your AuctionDB data?"] = "Sind Sie sicher, dass Sie die \"AuctionDB\" Daten löschen wollen?"
L["Ascending"] = "Aufsteigend"

7
TradeSkillMaster_AuctionDB/Locale/enUS.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "enUS
if not L then return end
L["%s ago"] = true
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = true
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = true
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = true
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = true
L["Are you sure you want to clear your AuctionDB data?"] = true
@ -23,6 +23,7 @@ L["AuctionDB - Market Value"] = true
L["AuctionDB - Minimum Buyout"] = true
L["Can't run a GetAll scan right now."] = true
L["Descending"] = true
L["Disable \"GetAll\" Auction Scans"] = true
L["Display lowest buyout value seen in the last scan in tooltip."] = true
L["Display market value in tooltip."] = true
L["Done Scanning"] = true
@ -31,6 +32,7 @@ L["Enable display of AuctionDB data in tooltip."] = true
L["GetAll scan did not run successfully due to issues on Blizzard's end. Using the TSM application for your scans is recommended."] = true
L["Hide poor quality items"] = true
L["If checked, AuctionDB will add a tab to the AH to allow for in-game scans. If you are using the TSM app exclusively for your scans, you may want to hide it by unchecking this option. This option requires a reload to take effect."] = true
L["If checked, AuctionDB will not perform \"GetAll\" scans. This is useful if your server doesn't return all auctions in its \"GetAll\" results, which means that you'll get incorrect market value calculations for all items. If you're playing on such servers, it's best to disable the \"GetAll\" feature to avoid accidentally polluting your price database with incorrect data. This option takes effect immediately, but requires a reload to completely hide the \"Run GetAll Scan\" button."] = true
L["If checked, poor quality items won't be shown in the search results."] = true
L["If checked, the lowest buyout value seen in the last scan of the item will be displayed."] = true
L["If checked, the market value of the item will be displayed"] = true
@ -72,7 +74,9 @@ L["Result Order:"] = true
L["Run Full Scan"] = true
L["Run GetAll Scan"] = true
L["Running query..."] = true
L["Running query... Server not responding due to throttling? Try again later..."] = true
L["Scan Selected Groups"] = true
L["Scanning %d / %d (Page 1 / ?)"] = true
L["Scanning %d / %d (Page %d / %d)"] = true
L["Scanning page %s/%s"] = true
L["Scanning the auction house in game is no longer necessary!"] = true
@ -89,3 +93,4 @@ L["Use the search box and category filters above to search the AuctionDB data."]
L["You can filter the results by item subtype by using this dropdown. For example, if you want to search for all herbs, you would select \"Trade Goods\" in the item type dropdown and \"Herbs\" in this dropdown."] = true
L["You can filter the results by item type by using this dropdown. For example, if you want to search for all herbs, you would select \"Trade Goods\" in this dropdown and \"Herbs\" as the subtype filter."] = true
L["You can use this page to lookup an item or group of items in the AuctionDB database. Note that this does not perform a live search of the AH."] = true
L["You have disabled GetAll scans via AuctionDB's options."] = true

2
TradeSkillMaster_AuctionDB/Locale/esES.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "esES
if not L then return end
-- L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = ""
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "Todos los artículos en la base de datos de AuctionDB que contienen la frase de búsqueda en su nombre en la pantalla."
L["Are you sure you want to clear your AuctionDB data?"] = "¿Está seguro que desea borrar los datos AuctionDB?"
L["Ascending"] = "Ascendente"

2
TradeSkillMaster_AuctionDB/Locale/esMX.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "esMX
if not L then return end
-- L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = ""
-- L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = ""
-- L["Are you sure you want to clear your AuctionDB data?"] = ""
-- L["Ascending"] = ""

2
TradeSkillMaster_AuctionDB/Locale/frFR.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "frFR
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "Une analyse complète de l'Hôtel des ventes examinera tous les objets de l'hôtel des ventes mais est beaucoup plus lente que la méthode du GetAll. Attendez-vous à ce que cette analyse dure de nombreuses minutes."
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = ""
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "Tout objets présent dans la base de donnée d'AuctionDB contenant la phrase recherchée dans son nom sera affiché."
L["Are you sure you want to clear your AuctionDB data?"] = "Êtes-vous sûr de vouloir vider les données d'AuctionDB?"
L["Ascending"] = "Croissant"

2
TradeSkillMaster_AuctionDB/Locale/koKR.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "koKR
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "전체 검색은 경매장 내의 모든 아이템을 검색하지만 GetAll 검색보다는 훨씬 느립니다. 이 검색은 몇 분 정도 또는 그 이상의 시간이 소요됩니다."
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = "GetAll 검색은 게임 내에서 경매장의 모든 아이템을 검색하기 위한 가장 빠른 검색 방법입니다. 하지만 블리자드 쪽에 많은 버그가 존재하며 게임의 접속이 끊길 가능성도 있습니다. 또한, 15분의 쿨다운이 존재합니다." -- Needs review
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = "GetAll 검색은 게임 내에서 경매장의 모든 아이템을 검색하기 위한 가장 빠른 검색 방법입니다. 하지만 블리자드 쪽에 많은 버그가 존재하며 게임의 접속이 끊길 가능성도 있습니다. 또한, 15분의 쿨다운이 존재합니다. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server." -- Needs review
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "이름에 검색 구문을 포함하는 AuctionDB 데이터베이스 내의 모든 아이템이 표시됩니다."
L["Are you sure you want to clear your AuctionDB data?"] = "모든 AuctionDB 데이터를 삭제 하시겠습니까?"
L["Ascending"] = "오름차순"

2
TradeSkillMaster_AuctionDB/Locale/ptBR.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "ptBR
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "Um escaneamento completo da casa de leilões irá escanear todos os itens da casa de leilões, porém é bem mais lento que um escaneamento PegaTudo. Espere que este escaneamento demore vários minutos ou mais."
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = ""
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "Qualquer item no bando de dados do AuctionDB que contém a frase procurada em seus nomes serão exibidos."
L["Are you sure you want to clear your AuctionDB data?"] = "Você tem certeza de que quer limpar os dados do seu AuctionDB?"
L["Ascending"] = "Crescente"

2
TradeSkillMaster_AuctionDB/Locale/ruRU.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "ruRU
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "Полный скан Аукциона просканирует каждый товар, но намного дольше, чем GetAll скан. Ждите, это займёт несколько минут или более."
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = "GetAll скан - самый быстрый внутриигровой способ сканирования. Однако, из-за из-за ошибок со стороны Blizzard's, существует вероятность отключения от сервера. Кроме того, он имеет 15-минутный перерыв."
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = "GetAll скан - самый быстрый внутриигровой способ сканирования. Однако, из-за из-за ошибок со стороны Blizzard's, существует вероятность отключения от сервера. Кроме того, он имеет 15-минутный перерыв. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server." -- Needs review
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "Будут отображены все товары из базы данных AuctionDB, содержащие искомую фразу в названии."
L["Are you sure you want to clear your AuctionDB data?"] = "Вы действительно хотите очистить базу AuctionDB?"
L["Ascending"] = "Возрастание"

2
TradeSkillMaster_AuctionDB/Locale/zhCN.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "zhCN
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "完整扫描拍卖行内的所有物品,此方式远慢于快速扫描,预计费时几分钟甚至更久。"
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = "快速扫描时扫描拍卖行中每件物品最快的方式。然而,在服务器端有着可能的BUG会使您掉线,所以每15分钟才能执行一次。"
L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = "快速扫描时扫描拍卖行中每件物品最快的方式。然而,在服务器端有着可能的BUG会使您掉线,所以每15分钟才能执行一次。 You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server." -- Needs review
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "任何包含搜索短语的AuctionDB数据库中的物品都将显示。"
L["Are you sure you want to clear your AuctionDB data?"] = "您确定要清除AuctionDB数据吗?"
L["Ascending"] = "升序"

2
TradeSkillMaster_AuctionDB/Locale/zhTW.lua

@ -14,7 +14,7 @@ local L = LibStub("AceLocale-3.0"):NewLocale("TradeSkillMaster_AuctionDB", "zhTW
if not L then return end
L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."] = "完整的拍賣行掃描會掃描每件在拍賣行的物品但是比GetAll掃描還緩慢。預期掃描會花費幾分鐘或是更久。"
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."] = ""
-- L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."] = ""
L["Any items in the AuctionDB database that contain the search phrase in their names will be displayed."] = "任何在AuctionDB資料庫中符合搜尋條件的物品,都會顯示出來。"
L["Are you sure you want to clear your AuctionDB data?"] = "你確定要清除AuctionDB資料?"
L["Ascending"] = "遞增"

59
TradeSkillMaster_AuctionDB/Modules/GUI.lua

@ -51,12 +51,22 @@ function private:CreateStartScanContent(parent)
frame:SetAllPoints(parent)
frame:Hide()
-- Don't create or handle the GetAll button if player has disabled GetAll.
-- NOTE: This GUI creation is only done once per game reload, so this choice
-- won't change until the user does a UI "/reload" or logs out of the game.
local includeGetAll = not TSM.db.profile.disableGetAll
local function UpdateGetAllButton()
if not frame.startGetAllButton then
return -- Do nothing if the GetAll feature is disabled.
end
if TSM.Scan.isScanning then
frame:Disable()
elseif not select(2, CanSendAuctionQuery()) then
-- Server says that GetAll isn't ready. Check our stored cooldown value.
local previous = TSM.db.profile.lastGetAll or time()
if previous > (time() - 15*60) then
if previous > (time() - 15*60) then -- 15 minute enforced cooldown between GetAll scans...
local diff = previous + 15*60 - time()
local diffMin = math.floor(diff/60)
local diffSec = diff - diffMin*60
@ -67,12 +77,14 @@ function private:CreateStartScanContent(parent)
frame:Enable()
frame.startGetAllButton:Disable()
else
-- Server says that GetAll is ready.
frame:Enable()
frame.getAllStatusText:SetText("|cff009900"..L["Ready"])
frame.startGetAllButton:Enable()
end
end
if includeGetAll then
frame:SetScript("OnShow", function(self)
TSMAPI:CreateTimeDelay("auctionDBGetAllStatus", 0, UpdateGetAllButton, 0.2)
end)
@ -80,20 +92,21 @@ function private:CreateStartScanContent(parent)
frame:SetScript("OnHide", function(self)
TSMAPI:CancelFrame("auctionDBGetAllStatus")
end)
end
frame.Enable = function(self)
self.startGetAllButton:Enable()
if self.startGetAllButton then self.startGetAllButton:Enable() end
self.startFullScanButton:Enable()
self.startGroupScanButton:Enable()
end
frame.Disable = function(self)
self.startGetAllButton:Disable()
if self.startGetAllButton then self.startGetAllButton:Disable() end
self.startFullScanButton:Disable()
self.startGroupScanButton:Disable()
end
-- top row (auto updater)
-- Top row: Auto updater.
local text = TSMAPI.GUI:CreateLabel(frame)
text:SetFont(TSMAPI.Design:GetContentFont(), 24)
text:SetPoint("TOP", 0, -24)
@ -112,7 +125,7 @@ function private:CreateStartScanContent(parent)
content:SetAllPoints(parent.content)
TSMAPI.Design:SetFrameBackdropColor(content)
-- group tree
-- Group tree.
local container = CreateFrame("Frame", nil, content)
container:SetPoint("TOPLEFT", 5, -35)
container:SetPoint("BOTTOMRIGHT", -205, 5)
@ -128,14 +141,17 @@ function private:CreateStartScanContent(parent)
buttonFrame:SetPoint("TOPLEFT", content, "TOPRIGHT", -200, 0)
buttonFrame:SetPoint("BOTTOMRIGHT")
-- first row (getall scan)
-- Row: GetAll Scan.
-- NOTE: We hide this button if the player has disabled GetAll scans.
local yOffset = -50
if includeGetAll then
local btn = TSMAPI.GUI:CreateButton(buttonFrame, 18)
btn:SetPoint("TOPLEFT", 6, -50)
btn:SetPoint("TOPRIGHT", -6, -50)
btn:SetPoint("TOPLEFT", 6, yOffset)
btn:SetPoint("TOPRIGHT", -6, yOffset)
btn:SetHeight(22)
btn:SetScript("OnClick", TSM.Scan.StartGetAllScan)
btn:SetText(L["Run GetAll Scan"])
btn.tooltip = L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown."]
btn.tooltip = L["A GetAll scan is the fastest in-game method for scanning every item on the auction house. However, there are many possible bugs on Blizzard's end with it including the chance for it to disconnect you from the game. Also, it has a 15 minute cooldown. You can disable the GetAll button via TSM's AuctionDB options if this feature doesn't work well on your server."]
frame.startGetAllButton = btn
local text = TSMAPI.GUI:CreateLabel(buttonFrame)
@ -146,24 +162,33 @@ function private:CreateStartScanContent(parent)
text:SetJustifyV("CENTER")
frame.getAllStatusText = text
TSMAPI.GUI:CreateHorizontalLine(buttonFrame, -110)
yOffset = yOffset - 50
TSMAPI.GUI:CreateHorizontalLine(buttonFrame, yOffset)
-- second row (full scan)
yOffset = yOffset - 20
end
-- Row: Full Scan.
local btn = TSMAPI.GUI:CreateButton(buttonFrame, 18)
btn:SetPoint("TOPLEFT", 6, -150)
btn:SetPoint("TOPRIGHT", -6, -150)
btn:SetPoint("TOPLEFT", 6, yOffset)
btn:SetPoint("TOPRIGHT", -6, yOffset)
btn:SetHeight(22)
btn:SetScript("OnClick", TSM.Scan.StartFullScan)
btn:SetText(L["Run Full Scan"])
btn.tooltip = L["A full auction house scan will scan every item on the auction house but is far slower than a GetAll scan. Expect this scan to take several minutes or longer."]
frame.startFullScanButton = btn
TSMAPI.GUI:CreateHorizontalLine(buttonFrame, -200)
yOffset = yOffset - 40
TSMAPI.GUI:CreateHorizontalLine(buttonFrame, yOffset)
yOffset = yOffset - 20
-- third row (group scan)
-- Row: Group Scan.
local btn = TSMAPI.GUI:CreateButton(buttonFrame, 18)
btn:SetPoint("TOPLEFT", 6, -225)
btn:SetPoint("TOPRIGHT", -6, -225)
btn:SetPoint("TOPLEFT", 6, yOffset)
btn:SetPoint("TOPRIGHT", -6, yOffset)
btn:SetHeight(22)
btn:SetScript("OnClick", GUI.StartGroupScan)
btn:SetText(L["Scan Selected Groups"])

490
TradeSkillMaster_AuctionDB/Modules/Scanning.lua

@ -11,94 +11,389 @@ local TSM = select(2, ...)
local Scan = TSM:NewModule("Scan", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_AuctionDB") -- loads the localization table
Scan.groupScanStartTime = 0
Scan.groupScanData = {}
Scan.filterList = {}
Scan.numFilters = 0
Scan.fullScanStartTime = 0
Scan.fullScanSecondsPerPage = -1
Scan.fullScanCompleteElapsed = nil
local verifyNewAlgorithm = false -- DEVELOPERS: Set to "true" to validate and benchmark the new market data algorithm!
local function ScanCallback(event, ...)
local function FullScanCallback(event, ...)
if event == "SCAN_PAGE_UPDATE" then
-- We're running a "Full Scan" and have received an auction page.
-- NOTE: These normal per-page scans receive 50 items per page, and will
-- successfully download ALL auctions on private servers, thanks to pagination.
-- For example, while Warmane's "GetAll" only returns 55000 of 126559 auctions,
-- the regular "Full Scan" mode retrieves all 2532 pages of 50 items each,
-- meaning that it covers 126600 auctions (and therefore grabs them all)
-- in this example. Users should always prefer "Full Scan" when "GetAll" fails.
local page, total = ...
TSM.GUI:UpdateStatus(format(L["Scanning page %s/%s"], page, total), page*100/total)
-- Calculate the current page progress and the remainder as floating-point values.
local progress_float = page / total
local remaining_float = 1.0 - progress_float
-- Estimate the total scan time, based on a MIX of the average per-page so far,
-- and the previous scan's averages stored in the database (if available).
-- NOTE: This callback triggers after we RECEIVED "page", so we count "page" too.
-- NOTE: We don't do any "live" updates of the progress bar text. We only
-- update the text labels when we receive a page, which is very CPU-efficient.
local time_estimate_str = ""
if (page >= 1) and (total > page) then
-- Calculate how many seconds have elapsed per page-request so far.
-- NOTE: We time it via the less-precise "time()" function, which
-- bluntly returns whole seconds. The alternative would be to use
-- "debugprofilestop()", which has millisecond-precision, but breaks
-- if another addon calls "debugprofilestart()" (which resets that
-- timer back to zero). It doesn't really matter, since our "seconds
-- per page" is constantly re-calculated based on the latest "total
-- amount of whole seconds elapsed", so it doesn't accumulate any
-- rounding errors and gets more precise the more pages have been
-- downloaded (after ~10 pages, it's practically as accurate as the
-- debug-timer). We have to use this technique for safety!
-- NOTE: Most servers will gradually slow down the page requests
-- across the first 300 requests or so, which will become slower
-- and slower, which means that the initial time estimate will
-- grow until it settles on the correct time remainder. There's
-- nothing we can do to predict those gradual slowdowns / throttling,
-- which is why we're also storing the last scan's "final average"
-- in the database and using that for our subsequent scan estimates.
local seconds_elapsed = abs(time() - Scan.fullScanStartTime)
local seconds_per_page = seconds_elapsed / page
-- local pages_remaining = total - page -- Not used for anything.
-- Remember our "real", unweighted value, for later DB storage.
Scan.fullScanSecondsPerPage = seconds_per_page
-- Calculate a smoothly weighted "seconds per page" value based on
-- a linear mix between the current "seconds per page" and the
-- stored "final seconds per page value" from our previous scan.
-- As we reach 100%, we'll use 100% of the current "real seconds
-- per page". But at 0%, we'll use the stored value instead.
-- Between that, we linearly fade the values so that we react
-- smoothly to changes in speed. This solves the issue that all
-- servers face, which is their gradual slowdown of page fetches,
-- where they start out very fast (such as 1.1 seconds per page),
-- but will have slowed down when you're at the end (such as 2.5 per
-- page). Typical server slowdown in speed is roughly linear, which
-- is why our linear blend between "current estimate" and "finished
-- estimate from previous scan" creates the most accurate results
-- we're able to get, given the server behavior. It should also
-- work perfectly on servers which don't follow this pattern, such
-- as if they have a perfectly linear time between all pages without
-- any throttling at all, in which case both estimates will basically
-- agree anyway (both the current and the saved value). This is the
-- best we can do with the facts of the game. A totally accurate
-- estimate is impossible, but we're as accurate as we can be.
-- NOTE: This estimate cannot be improved, since practically all
-- servers apply random throttling, have various loads and slowdowns
-- throughout the day, etc. This is the best we can do since the
-- actual speed depends on the server and is pretty unpredictable.
-- It would be like trying to predict "the total download-time of a
-- file that keeps fluctuating between fast and slow speeds". The
-- best we can do is estimate based on current and previous speeds.
local last_scan_seconds_per_page = TSM.db.factionrealm.lastScanSecondsPerPage
if last_scan_seconds_per_page and last_scan_seconds_per_page > 0 then
-- TSM:Print(format("Read from DB: %f (Our unweighted estimate: %f)", last_scan_seconds_per_page, seconds_per_page)) -- DEBUG
seconds_per_page = (seconds_per_page * progress_float) + (last_scan_seconds_per_page * remaining_float)
-- TSM:Print(format("New, weighted estimate: %f (Progress: %f / Remaining: %f)", seconds_per_page, progress_float, remaining_float)) -- DEBUG
else
-- TSM:Print(format("Nothing in DB yet (Our unweighted estimate: %f)", seconds_per_page)) -- DEBUG
end
-- Estimate the "total time" requirement for ALL pages, rounded to
-- the nearest whole second, at least 1 second.
-- NOTE: We calculate the total estimate instead of the "remaining
-- time", because servers tend to fluctuate constantly between slowly
-- and then quickly sending the pages, which means a "pages_remaining"
-- timer is hard to understand in terms of real time remaining, since
-- we might get 10 pages within a few seconds and count down their
-- "seconds per page" amounts much faster than natural time, and
-- then suddenly stall for 30 seconds without getting any pages.
-- So a "remaining time" estimate would not move naturally. Instead,
-- we use a constantly updating "total time estimate" which follows
-- the server performance beautifully and is easy to understand.
-- NOTE: Thanks to the linear blend between historical and current
-- server performance, our estimate is very accurate yet responsive.
-- NOTE: The total page count is able to change during AH scan, when
-- more auctions are added or removed, which further contributes to
-- the confusion if we would use a "time remaining" display instead,
-- but since we use a "total time" estimate the user instead smoothly
-- sees the total estimate change when the page count changes.
local seconds_total_estimate = max(1, floor((total * seconds_per_page) + 0.5))
-- Convert the "elapsed / estimated" seconds into hours, minutes and seconds.
time_estimate_str = format(" (%s / ~%s)", TSMAPI:FormatHMS(TSMAPI:SecondsToHMS(seconds_elapsed)), TSMAPI:FormatHMS(TSMAPI:SecondsToHMS(seconds_total_estimate)))
end
-- Calculate progress bar from 0-100%.
local progress_bar = min(100 * progress_float, 100)
-- Display the progress bar with the time estimate.
TSM.GUI:UpdateStatus(format(L["Scanning page %s/%s"], page, total) .. time_estimate_str, progress_bar)
elseif event == "SCAN_COMPLETE" then
-- The whole scan is complete, and wasn't interrupted by the player.
-- Store the final "seconds elapsed per page request" into the database.
-- NOTE: We only update it here after complete scans, to avoid poisoning
-- with incorrect, partial-scan estimates, since most servers heavily
-- slow down their page requests over time. The completed scan is the truth.
if Scan.fullScanSecondsPerPage > 0 then
TSM.db.factionrealm.lastScanSecondsPerPage = Scan.fullScanSecondsPerPage
end
-- Calculate how many seconds the completed "Full Scan" took.
-- NOTE: We must cache it in this external variable, because "Full Scans"
-- use a threading callback which calls "DoneScanning()" one more time,
-- so we preserve the value to still display it via that callback too.
Scan.fullScanCompleteElapsed = abs(time() - Scan.fullScanStartTime)
-- Now process all of the fetched auctions, and display the total time elapsed.
local data = ...
Scan:ProcessScanData(data)
Scan:DoneScanning()
elseif event == "INTERRUPTED" then
Scan:DoneScanning(Scan.fullScanCompleteElapsed)
elseif event == "SCAN_INTERRUPTED" or event == "INTERRUPTED" then
-- We've been interrupted by the Auction House closing.
-- NOTE: "SCAN_INTERRUPTED" is from LibAuctionScan-1.0, which isn't used
-- by TSM anymore, and "INTERRUPTED" is from "TSM/Auction/AuctionScanning.lua",
-- which is what this scanner uses nowadays.
Scan:DoneScanning()
end
end
function Scan.ProcessGetAllScan(self)
local temp = 0
-- Await the "AUCTION_ITEM_LIST_UPDATE" event with the results of the "get all" scan.
-- NOTE: This event won't be received if the server ignored/throttled the "get all" scan.
local time_start = time()
local progress_bar = 0
while true do
temp = min(temp + 1, 100)
self:Sleep(0.2)
if not Scan.isScanning then return end
if Scan.getAllLoaded then
-- NOTE: The fake progress bar takes 20 seconds to fill to 100% with this interval.
progress_bar = min(progress_bar + 1, 100) -- Fake progress bar from 0-100%.
self:Sleep(0.2) -- Update progress bar with this interval while waiting for server data.
if not Scan.isScanning then return end -- Failed or aborted scan.
if Scan.getAllLoaded then -- We have received the list of auctions via "AUCTION_ITEM_LIST_UPDATE" event!
break
end
TSM.GUI:UpdateStatus(L["Running query..."], nil, temp)
-- NOTE: If it has taken more than 30 seconds, we can assume that the server
-- didn't send any data and won't be replying, most likely due to hidden
-- throttling (happens on many private servers, if scanning too soon after another).
local time_elapsed = time() - time_start
if time_elapsed >= 30 then
-- If this happens, the user has most likely been throttled and should try again later.
TSM.GUI:UpdateStatus(L["Running query... Server not responding due to throttling? Try again later..."], nil, 100)
else
TSM.GUI:UpdateStatus(L["Running query..."], nil, progress_bar)
end
end
-- IMPORTANT: As explained in the "Scan:AUCTION_ITEM_LIST_UPDATE()" code
-- comments, we're allowing GetAll scans even when the server DOESN'T return
-- all auctions. For example, Warmane has chosen to limit GetAll results to
-- only 55k auctions (even though they usually have 120k+ auctions). This
-- means that the market data calculations will be missing data. We should
-- therefore warn the user and tell them to use "Full Scan" instead, when
-- they're playing on such "limited GetAll" servers. But we'll still allow
-- use of "GetAll", with a warning message for people who INSIST on using it,
-- since even when it only looks at half the auctions, it can still give a
-- "pretty decent" idea of market values.
-- NOTE: It WOULD be "best" to always throw away (ignore) incomplete "GetAll"
-- data, but most casual players will prefer to have this feature even if those
-- incomplete scans are much less accurate than a proper "Full Scan".
-- NOTE: The inaccuracy of an incomplete "GetAll" scan completely depends on
-- how the server implements "GetAll". If the server sorts the cheapest
-- auctions first, then it's somewhat acceptable for popular items which
-- have many stacks (and are therefore likely to contain enough data points
-- in the "GetAll" result), and those items will "just" tend to be undervalued
-- by 2-10% less than their real market value (since their percentile-based
-- scans will look at less auctions than the real amount). But for very rare
-- or unpopular items, the partial "GetAll" scan is very dangerous, since
-- you might only receive the massively overpriced auctions of a certain
-- item, and thereby calculate an insanely high market value for it. These
-- dangers are increased if the server sends the auctions in a totally
-- random order, which means an even greater risk of market price pollution
-- by only seeing overpriced auctions for many of the items.
-- NOTE: Furthermore, receiving incomplete "GetAll" results means that TSM
-- will wipe all of its "cheapest, current buyout price" data for all items,
-- and then fills them with incorrect data (or nothing at all if an item
-- wasn't seen in the latest fetch), thus hindering your ability to look up
-- the correct "best current buyout prices" too.
-- NOTE: In summary, you'll have to use partial "GetAll" results at your
-- own risk! A "Full Scan" is ALWAYS much better when your server's "GetAll"
-- doesn't provide all auction data!
local shown, total = GetNumAuctionItems("list")
if total ~= Scan.getAllLoaded then -- getAllLoaded = Same as "shown", but cached via our GetAll event.
-- NOTE: Message is not localized, because who the hell is gonna provide
-- translations to this project? It'd be a waste of time to translate it,
-- especially since we dynamically insert a word based on the result.
TSM:Print(format(
"WARNING: Your server has %d auctions, but it%s sent %d auctions to us. Please use the normal \"Full Scan\" instead, if you want to accurately calculate the real market values of items. Your server's \"GetAll\" scan doesn't fetch all auctions!",
total, ((total > shown) and " only" or ""), shown
))
else
TSM:Print(format("All auctions received from server (%d auctions)...", Scan.getAllLoaded))
end
-- Collect relevant data about the auctions that we've received.
-- NOTE: "Scan.getAllLoaded" is the count of auctions we've received from
-- the server. One per listing. We keep this cached value to constantly verify
-- that we're still looking at the same auction-list while we're processing.
local data = {}
for i=1, Scan.getAllLoaded do
TSM.GUI:UpdateStatus(format(L["Scanning page %s/%s"], 1, 1), i*100/Scan.getAllLoaded)
if i % 100 == 0 then
progress_bar = 0
for auction_idx=1, Scan.getAllLoaded do
if (auction_idx == 1) or (auction_idx == Scan.getAllLoaded) or ((auction_idx % 100) == 0) then
-- Update progress bar for the 1st, last, and every 100th auction.
progress_bar = min(100*(auction_idx/Scan.getAllLoaded), 100) -- Calculate progress bar from 0-100%.
TSM.GUI:UpdateStatus(format(L["Scanning page %s/%s"], 1, 1), progress_bar)
-- Yield the CPU every 100th "auction listing", to prevent freezing the game.
self:Yield()
-- Verify the currently visible auction count, to ensure that we're
-- still analyzing the same list of "GetAll" auction items.
if GetNumAuctionItems("list") ~= Scan.getAllLoaded then
-- This can happen due to server/game issues, or if the user
-- closes the auction GUI while we're processing. If that
-- happens, we'll abort the scan without processing the data.
--TSM:Print(L["GetAll scan did not run successfully due to issues on Blizzard's end. Using the TSM application for your scans is recommended."])
TSM:Print("GetAll scan did not run successfully.")
Scan:DoneScanning()
TSM:Print("GetAll: Scan failed or aborted by user.")
Scan:DoneScanning() -- Sets isScanning and getAllLoaded to nil.
return
end
end
local itemID = TSMAPI:GetItemID(GetAuctionItemLink("list", i))
--local _, _, count, _, _, _, _, _, _, buyout = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, _, _, buyout = GetAuctionItemInfo("list", i)
if itemID and buyout and buyout > 0 then
data[itemID] = data[itemID] or {records={}, minBuyout=math.huge, quantity=0}
data[itemID].minBuyout = min(data[itemID].minBuyout, floor(buyout/count))
data[itemID].quantity = data[itemID].quantity + count
for j=1, count do
tinsert(data[itemID].records, floor(buyout/count))
-- Retrieve information about this auction.
-- * itemID = The numeric ID of the item (such as 52021, which would represent "Iceblade Arrow" for example).
-- * stack_size = How many are in this auction's exact "stack" (such as 940, if they're selling a stack of 940).
-- * buyout = The buyout price of the auction (to get the per-item price, we need to divide "buyout / stack_size").
local itemID = TSMAPI:GetItemID(GetAuctionItemLink("list", auction_idx))
--local _, _, stack_size, _, _, _, _, _, _, buyout = GetAuctionItemInfo("list", auction_idx)
local _, _, stack_size, _, _, _, _, _, buyout = GetAuctionItemInfo("list", auction_idx)
-- Only process this auction if we saw a valid buyout price (ignore bid-only auctions, etc).
if itemID and buyout and (buyout > 0) then
-- Calculate the price per item, always rounded downwards.
-- NOTE: TSM's "buyout per item" calculations are actually a freaking
-- mess. They basically use "floor(buyout/count)" everywhere in the
-- code, EXCEPT in SOME places where they use "floor(buyout/count + 0.5)"
-- which means that those round to the nearest number instead, which
-- is definitely more correct. In fact, it would probably be even more
-- correct to enforce a minimum value of "1", otherwise a stack of
-- 100 items for a total buyout price of 10 copper would end up as
-- "0 copper per item" by the basic flooring algorithm (floor(0.1) = 0).
-- But whatever, it's extremely inconsistently used everywhere in TSM's
-- codebase, and most places use the plain "floor downwards", so we'll
-- do that too. It would be way too much effort to rewrite the HUNDREDS of
-- other code locations that deal with money (all via differently named
-- variables), just to handle smarter rounding, and it would require
-- a lot of effort to ensure that TSM's algorithms still work afterwards.
-- But yeah, just realize this: TSM's per-item calculations aren't great,
-- however it really DOESN'T MATTER MUCH since it only affects items
-- whose prices are less than 1 copper per item, which is probably
-- why TSM's author never noticed any issues with the basic "floor()".
local buyout_per_item = floor(buyout / stack_size)
-- Append to the existing "data to process" for this item ID if exists, else create new item.
data[itemID] = data[itemID]
if not data[itemID] then
data[itemID] = {records={}, minBuyout=math.huge, quantity=0}
end
-- Calculate the lowest "per-item buyout price" we're seeing for this item.
data[itemID].minBuyout = min(data[itemID].minBuyout, buyout_per_item)
-- Count the total amount of this item that exists on the auction house (adds together all stacks).
-- NOTE: This is super useful when we're calculating the market value in data.lua,
-- since it tells us immediately what the total count (quantity) of all new records is.
data[itemID].quantity = data[itemID].quantity + stack_size
-- BRAINDEAD OLD TSM CODE WHICH ADDS 1 RECORD PER ITEM IN THE STACK,
-- MEANING 500 STACKS OF 1000 ARROWS WOULD BE HALF A MILLION TABLE
-- ROWS AND WOULD LEAD TO "OUT OF MEMORY" ERRORS. DON'T DO THIS!
-- for j=1, stack_size do
-- tinsert(data[itemID].records, buyout_per_item)
-- end
-- Rewritten, intelligent code which adds 1 record per "stack" (auction) instead.
-- NOTE: We avoid using hash-keys, saving memory by using a numeric array instead.
-- NOTE: We'll store 1 table per unique itemID, and 1 record per "auction"
-- which passed our filters (has a buyout price), meaning that it's usually
-- a bit less than the total auction count on the server, and will never
-- be too many rows for WoW's Lua memory system to handle.
-- NOTE: If there is ever a server with so many auctions that even this
-- would reach WoW's Lua memory limits, then we could possibly store
-- the records as a concatenated, semicolon-separated string instead,
-- which would be slower but would handle millions of auctions with ease.
tinsert(data[itemID].records, {stack_size, buyout_per_item})
end
end
-- Process the collected "GetAll" auction data as a new "complete scan" with today's date.
TSM.db.factionrealm.lastCompleteScan = time()
TSM.Data:ProcessData(data)
TSM.Data:ProcessData(data, nil, verifyNewAlgorithm)
-- Show GUI progress while we're waiting for the processing.
-- NOTE: The status text will be set to "complete" elsewhere, automatically.
TSM.GUI:UpdateStatus(L["Processing data..."])
while TSM.processingData do
self:Sleep(0.2)
end
-- Processing is complete, so warn the user that they should reload the UI now.
TSM:Print(L["It is strongly recommended that you reload your ui (type '/reload') after running a GetAll scan. Otherwise, any other scans (Post/Cancel/Search/etc) will be much slower than normal."])
end
function Scan:AUCTION_ITEM_LIST_UPDATE()
Scan:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
local num, total = GetNumAuctionItems("list")
--if num ~= total or num == 0 then
if num == 0 then
-- shown = How many auctions we received in the current data batch.
-- total = The total number of auction items.
local shown, total = GetNumAuctionItems("list")
-- OLD TSM CODE: It assumes that "GetAll" returns ALL auctions on official
-- Blizzard servers, so it also checks if "shown equals the total".
--if shown ~= total or shown == 0 then
-- WORKAROUND: Because places like Warmane with huge auction houses DON'T
-- return all items even via "GetAll" scans, we must ignore shown-vs-total
-- mismatches. The server won't let us query the subsequent pages since
-- there isn't any pagination in the "GetAll" API, and wouldn't work anyway
-- due to the 15-30 minute cooldown for "GetAll" calls, so we can't fetch
-- all auctions if the server is too popular. For example, typical
-- Warmane-Icecrown "GetAll" results will be: shown=55000, total=122523,
-- meaning that "GetAll" only receives about half of the total auctions.
if shown <= 0 then
--TSM:Print(L["GetAll scan did not run successfully due to issues on Blizzard's end. Using the TSM application for your scans is recommended."])
TSM:Print("GetAll scan did not run successfully.")
Scan:DoneScanning()
TSM:Print("GetAll: Scan failed due to server issues.")
Scan:DoneScanning() -- Sets isScanning and getAllLoaded to nil.
return
end
Scan.getAllLoaded = num
-- Cache the amount of auctions we received on the current "page/batch", so that
-- we can validate that we're looking at this "page" while processing later.
Scan.getAllLoaded = shown
end
function Scan:GetAllScanQuery()
-- NOTE: This API doesn't work properly on some servers. For example, on Warmane,
-- it always claims that you can do a "GetAll" scan after you log in, even
-- if you're on cooldown and the server will actually be ignoring your request,
-- which is why we had to implement timeout detection in "ProcessGetAllScan()".
local canScan, canGetAll = CanSendAuctionQuery()
if not canGetAll then return TSM:Print(L["Can't run a GetAll scan right now."]) end
if not canScan then return TSMAPI:CreateTimeDelay(0.5, Scan.GetAllScanQuery) end
QueryAuctionItems("", nil, nil, nil, nil, nil, nil, nil, nil, true)
Scan:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
TSMAPI.Threading:Start(Scan.ProcessGetAllScan, 1, function() Scan:DoneScanning() end)
QueryAuctionItems("", nil, nil, nil, nil, nil, nil, nil, nil, true)
TSMAPI.Threading:Start(Scan.ProcessGetAllScan, 1, function()
-- Pass through the cached "full scan complete elapsed" value, which ONLY
-- contains a value if the latest full scan was successfully completed.
-- NOTE: This callback runs when the thread is finished, no matter what
-- reason. That's why we must preserve the "time elapsed" value for display.
Scan:DoneScanning(Scan.fullScanCompleteElapsed)
end)
end
local function GroupScanCallback(event, ...)
@ -114,14 +409,23 @@ local function GroupScanCallback(event, ...)
elseif event == "QUERY_UPDATE" then
local current, total = ...
TSM.GUI:UpdateStatus(format(L["Preparing Filter %d / %d"], current, total))
elseif event == "SCAN_INTERRUPTED" then
elseif event == "SCAN_INTERRUPTED" or event == "INTERRUPTED" then
-- We've been interrupted by the Auction House closing.
-- NOTE: "SCAN_INTERRUPTED" is from LibAuctionScan-1.0, which isn't used
-- by TSM anymore, and "INTERRUPTED" is from "TSM/Auction/AuctionScanning.lua",
-- which is what this scanner uses nowadays.
Scan:DoneScanning()
elseif event == "SCAN_TIMEOUT" then
tremove(Scan.filterList, 1)
Scan:ScanNextGroupFilter()
elseif event == "SCAN_PAGE_UPDATE" then
local page, total = ...
TSM.GUI:UpdateStatus(format(L["Scanning %d / %d (Page %d / %d)"], Scan.numFilters-#Scan.filterList, Scan.numFilters, page+1, total), nil, page*100/total)
-- We have now received at least 1 page for this item. Show how many pages remain.
-- NOTE: We can't provide any time estimate here, since the other group sizes are unknown.
-- NOTE: We use this particular item's page-progress as the progress bar.
-- NOTE: We add "+1" to the page counter, to indicate that we've received that page and are working on the next page.
local progress_bar = min(100*(page/total), 100) -- Calculate progress bar from 0-100%.
TSM.GUI:UpdateStatus(format(L["Scanning %d / %d (Page %d / %d)"], ((Scan.numFilters-#Scan.filterList) + 1), Scan.numFilters, min(page + 1, total), total), nil, progress_bar)
elseif event == "SCAN_COMPLETE" then
local data = ...
for _, itemString in ipairs(Scan.filterList[1].items) do
@ -136,11 +440,23 @@ end
function Scan:ScanNextGroupFilter(data)
if #Scan.filterList == 0 then
-- Calculate how many seconds the completed "Group Scan" took.
local seconds_elapsed = abs(time() - Scan.groupScanStartTime)
-- Now process all of the fetched auctions, and display the total time elapsed.
Scan:ProcessScanData(Scan.groupScanData)
Scan:DoneScanning()
Scan:DoneScanning(seconds_elapsed)
return
end
TSM.GUI:UpdateStatus(format(L["Scanning %d / %d (Page %d / %d)"], Scan.numFilters-#Scan.filterList, Scan.numFilters, 1, 1), (Scan.numFilters-#Scan.filterList)*100/Scan.numFilters)
-- Apply the temporary label for when we've requested the item's 1st page,
-- but we don't yet know how many results or pages there are for this item.
-- NOTE: We can't provide any time estimate here, since the other group sizes are unknown.
-- NOTE: In the label, we count the items starting at 1, to say "Scanning 1 / 2"
-- (instead of "Scanning 0 / 2"), but for the progress bar we count starting
-- from 0, so that it fills up properly by only proceeding after an item is done.
local progress_bar = min(100*((Scan.numFilters-#Scan.filterList)/Scan.numFilters), 100) -- Calculate progress bar from 0-100%.
TSM.GUI:UpdateStatus(format(L["Scanning %d / %d (Page 1 / ?)"], ((Scan.numFilters-#Scan.filterList) + 1), Scan.numFilters), progress_bar)
TSMAPI.AuctionScan:RunQuery(Scan.filterList[1], GroupScanCallback)
end
@ -152,6 +468,7 @@ function Scan:StartGroupScan(items)
wipe(Scan.groupScanData)
Scan.numFilters = 0
TSMAPI.AuctionScan:StopScan()
Scan.groupScanStartTime = time() -- Keep track of when we started the "Group Scan".
TSMAPI:GenerateQueries(items, GroupScanCallback)
TSM.GUI:UpdateStatus(L["Preparing Filters..."])
end
@ -162,10 +479,23 @@ function Scan:StartFullScan()
Scan.isBuggedGetAll = nil
Scan.groupItems = nil
TSMAPI.AuctionScan:StopScan()
TSMAPI.AuctionScan:RunQuery({name=""}, ScanCallback)
Scan.fullScanStartTime = time() -- Keep track of when we started the "Full Scan".
Scan.fullScanSecondsPerPage = -1 -- Reset the page-speed timer.
Scan.fullScanCompleteElapsed = nil -- Reset the "full scan completed" information.
TSMAPI.AuctionScan:RunQuery({name=""}, FullScanCallback)
end
function Scan:StartGetAllScan()
-- Refuse to perform "GetAll" if we're called while "GetAll" is disabled.
-- NOTE: Only happens if the player has visited the auction house and looked
-- at the AuctionDB GUI, and THEN gone into TSM's options to disable "GetAll".
-- The "Run GetAll Scan" button remains until /reload, so we must block it.
if TSM.db.profile.disableGetAll then
TSM:Print(L["You have disabled GetAll scans via AuctionDB's options."])
return
end
-- Begin the "GetAll" scan.
TSM.db.profile.lastGetAll = time()
Scan.isScanning = "GetAll"
Scan.isBuggedGetAll = nil
@ -174,13 +504,21 @@ function Scan:StartGetAllScan()
Scan:GetAllScanQuery()
end
function Scan:DoneScanning()
function Scan:DoneScanning(seconds_elapsed)
if seconds_elapsed then
-- If given the "time elapsed", display it as "Done Scanning (1:35:27)".
TSM.GUI:UpdateStatus(format("%s (%s)", L["Done Scanning"], TSMAPI:FormatHMS(TSMAPI:SecondsToHMS(seconds_elapsed))), 100)
else
-- Used when we don't care about showing time (such as scan failures).
TSM.GUI:UpdateStatus(L["Done Scanning"], 100)
end
Scan.isScanning = nil
Scan.getAllLoaded = nil
end
function Scan:ProcessScanData(scanData)
-- Handle scans performed via "Full Scan" and "Group Scan", but not "GetAll".
-- NOTE: See "Scan.ProcessGetAllScan()" for full explanation of this algorithm.
local data = {}
for itemString, obj in pairs(scanData) do
@ -189,41 +527,97 @@ function Scan:ProcessScanData(scanData)
local quantity, minBuyout = 0, 0
local records = {}
for _, record in ipairs(obj.records) do
-- Only process this auction if we saw a valid buyout price (ignore bid-only auctions, etc).
if record.buyout and record.buyout > 0 then
-- Calculate the price per item, always rounded downwards.
-- NOTE: "GetItemBuyout" returns nil if no buyout or if buyout is "0".
local itemBuyout = record:GetItemBuyout()
if itemBuyout and (itemBuyout < minBuyout or minBuyout == 0) then
if itemBuyout then
-- Calculate the lowest "per-item buyout price" we're seeing for this item.
if (itemBuyout < minBuyout or minBuyout == 0) then
minBuyout = itemBuyout
end
-- Count the total amount of this item that exists on the auction house (adds together all stacks).
quantity = quantity + record.count
for i=1, record.count do
tinsert(records, itemBuyout)
-- BRAINDEAD OLD TSM CODE WHICH ADDS 1 RECORD PER ITEM IN THE STACK,
-- MEANING 500 STACKS OF 1000 ARROWS WOULD BE HALF A MILLION TABLE
-- ROWS AND WOULD LEAD TO "OUT OF MEMORY" ERRORS. DON'T DO THIS!
-- for i=1, record.count do
-- tinsert(records, itemBuyout)
-- end
-- Rewritten, intelligent code which adds 1 record per "stack" (auction) instead.
tinsert(records, {record.count, itemBuyout})
end
end
end
-- Add this item to "data to process" even if there's zero records,
-- which can happen if they're all bid-only auctions.
-- NOTE: This differs from the behavior of "ProcessGetAllScan", which
-- only adds items that have at least 1 record with a buyout value.
-- NOTE: Empty records are totally fine either way, since "ProcessData"
-- simply ignores items that don't contain any buyout prices.
-- NOTE: If no buyout records were found, the "minBuyout" and "quantity"
-- fields below both have the default value of "0" (initialized above).
data[itemID] = {records=records, minBuyout=minBuyout, quantity=quantity}
end
end
-- Mark the collected auction data as a new "complete scan" with today's date,
-- but only if this was a normal "Full Scan" (not just a "TSM item group" scan).
if Scan.isScanning ~= "group" then
TSM.db.factionrealm.lastCompleteScan = time()
end
TSM.Data:ProcessData(data, Scan.groupItems)
-- Process the collected auction data.
TSM.Data:ProcessData(data, Scan.groupItems, verifyNewAlgorithm)
end
function Scan:ProcessImportedData(auctionData)
-- Handle manually imported auction scan data.
-- NOTE: This function is deprecated? Nothing seems to call it, unless they're
-- somehow calling it via another non-named technique, or perhaps it's internal
-- for developer-use only (basically just a quick way to emulate a full scan).
local data = {}
for itemID, auctions in pairs(auctionData) do
local quantity, minBuyout, records = 0, 0, {}
-- Process all imported auction records for this item.
local quantity, minBuyout = 0, 0
local records = {}
for _, auction in ipairs(auctions) do
-- Fetch the "price per item" and "item-count in this stack" from the auction's data.
-- NOTE: We only import auctions with per-item buyout values (ignore bid-only auctions, etc).
local itemBuyout, count = unpack(auction)
if itemBuyout and (itemBuyout < minBuyout or minBuyout == 0) then
if itemBuyout then
-- Calculate the lowest "per-item buyout price" we're seeing for this item.
if (itemBuyout < minBuyout or minBuyout == 0) then
minBuyout = itemBuyout
end
-- Count the total amount of this item that exists on the auction house (adds together all stacks).
quantity = quantity + count
for i=1, count do
tinsert(records, itemBuyout)
-- BRAINDEAD OLD TSM CODE WHICH ADDS 1 RECORD PER ITEM IN THE STACK,
-- MEANING 500 STACKS OF 1000 ARROWS WOULD BE HALF A MILLION TABLE
-- ROWS AND WOULD LEAD TO "OUT OF MEMORY" ERRORS. DON'T DO THIS!
-- for i=1, count do
-- tinsert(records, itemBuyout)
-- end
-- Rewritten, intelligent code which adds 1 record per "stack" (auction) instead.
tinsert(records, {count, itemBuyout})
end
end
-- Add this item to "data to process" even if there's zero records,
-- which can happen if they're all bid-only auctions.
data[itemID] = {records=records, minBuyout=minBuyout, quantity=quantity}
end
-- Process the imported auction data as a new "complete scan" with today's date.
TSM.db.factionrealm.lastCompleteScan = time()
TSM.Data:ProcessData(data)
TSM.Data:ProcessData(data, nil, verifyNewAlgorithm)
end

7
TradeSkillMaster_AuctionDB/Modules/config.lua

@ -390,6 +390,13 @@ function Config:LoadOptions(container)
relativeWidth = 0.5,
tooltip = L["If checked, AuctionDB will add a tab to the AH to allow for in-game scans. If you are using the TSM app exclusively for your scans, you may want to hide it by unchecking this option. This option requires a reload to take effect."],
},
{
type = "CheckBox",
label = L["Disable \"GetAll\" Auction Scans"],
settingInfo = { TSM.db.profile, "disableGetAll" },
relativeWidth = 0.5,
tooltip = L["If checked, AuctionDB will not perform \"GetAll\" scans. This is useful if your server doesn't return all auctions in its \"GetAll\" results, which means that you'll get incorrect market value calculations for all items. If you're playing on such servers, it's best to disable the \"GetAll\" feature to avoid accidentally polluting your price database with incorrect data. This option takes effect immediately, but requires a reload to completely hide the \"Run GetAll Scan\" button."],
},
},
},
{

474
TradeSkillMaster_AuctionDB/Modules/data.lua

@ -10,6 +10,21 @@
local TSM = select(2, ...)
local Data = TSM:NewModule("Data")
-- locals to speed up function access
local abs = abs
local CopyTable = CopyTable
local debugprofilestop = debugprofilestop
local floor = floor
local format = format
local ipairs = ipairs
local pairs = pairs
local sqrt = sqrt
local time = time
local tinsert = tinsert
local tsort = table.sort
local type = type
local unpack = unpack
-- weight for the market value from X days ago (where X is the index of the table)
local WEIGHTS = {[0] = 132, [1] = 125, [2] = 100, [3] = 75, [4] = 45, [5] = 34, [6] = 33,
[7] = 38, [8] = 28, [9] = 21, [10] = 15, [11] = 10, [12] = 7, [13] = 5, [14] = 4}
@ -115,53 +130,176 @@ function Data:GetMarketValue(scans)
return totalWeight > 0 and floor(totalAmount / totalWeight + 0.5) or 0
end
function Data:ProcessData(scanData, groupItems)
if TSM.processingData then return TSMAPI:CreateTimeDelay(0.2, function() Data:ProcessData(scanData, groupItems) end) end
--- Process a table of new market scan data.
-- @param scanData The market scan data.
-- @param[opt] groupItems Affects how the minBuyout data is wiped. Use nil for regular behavior.
-- @param[opt] verifyNewAlgorithm Boolean 'true' if you want to benchmark and verify the new market value algorithm.
function Data:ProcessData(scanData, groupItems, verifyNewAlgorithm)
-- If we're currently processing data, retry in 0.2 seconds.
-- NOTE: This will retry itself over and over until it's able to process.
if TSM.processingData then
return TSMAPI:CreateTimeDelay(0.2, function() Data:ProcessData(scanData, groupItems, verifyNewAlgorithm) end)
end
-- wipe all the minBuyout data
-- Wipe all of our existing "minBuyout" data for the items included in the
-- new, incoming scan data in case of "Item Group scan", or for ALL currently
-- cached items in memory in other cases (such as "Full" and "GetAll" scans).
-- NOTE: It's no problem if we leave some items empty with "nil" minBuyout
-- values. That's how TSM is supposed to work, with items having an empty "minBuyout"
-- if there wasn't any "minBuyout" data for that item in the newest data batch.
if groupItems then
-- A list of items ("group scan") was provided. Wipe data for those items.
for itemString in pairs(groupItems) do
local itemID = TSMAPI:GetItemID(itemString)
if TSM.data[itemID] then
if TSM.data[itemID] then -- If we have existing data for this item.
TSM:DecodeItemData(itemID)
TSM.data[itemID].minBuyout = nil
TSM.data[itemID].minBuyout = nil -- Erase its stored minBuyout value.
TSM:EncodeItemData(itemID)
end
end
else
-- Wipe data for all items in memory, regardless of whether they're actually
-- included in the incoming scan data or not...
for itemID, data in pairs(TSM.data) do
TSM:DecodeItemData(itemID)
data.minBuyout = nil
data.minBuyout = nil -- Directly updates TSM.data[itemID] via reference.
TSM:EncodeItemData(itemID)
end
end
-- Convert the incoming "scanData" hashmap to a numerically indexed table,
-- to allow us to perform batched processing (since "pairs()" wouldn't know
-- how to resume at the current spot between our batch processing callbacks).
-- NOTE: Doesn't use much memory, since we're re-using "data" table refs.
local scanDataList = {}
for itemID, data in pairs(scanData) do
tinsert(scanDataList, {itemID, data})
scanDataList[#scanDataList + 1] = {itemID, data}
end
-- go through each item and figure out the market value / update the data table
-- Go through each item and figure out their market value / update the data table.
-- NOTE: This processes the data in batched chunks of 500 items at a time,
-- pausing between each chunk to allow the game client to avoid freezing.
local index = 1
local day = Data:GetDay()
local function DoDataProcessing()
for i = 1, 500 do
-- Abort if we've reached the end of the processing queue.
if index > #scanDataList then
TSMAPI:CancelFrame("adbProcessDelay")
TSM.processingData = nil
break
end
-- Detect which Item ID we're processing, and read its new data (new auction records).
local itemID, data = unpack(scanDataList[index])
-- Calculate the market value, and optionally perform benchmarks and
-- validation of the "new, compact algorithm" to prove correctness.
-- NOTE: We refuse to verify data with over 200 000 "item rows", since
-- that would risk bloating RAM and leading to a script crash. This
-- has been VERIFIED to still WORK with 169 000 rows, but fail for
-- an item with 686 900 table rows (script stops executing), so 200k
-- should be a safe cutoff to prevent memory overflows during benchmark.
local marketValue = -1
if (not verifyNewAlgorithm) or (data.quantity >= 200000) then
-- NOTE: Returns -1 if there aren't enough records to calculate.
marketValue = Data:CalculateMarketValue(data, verifyNewAlgorithm)
else
-- Verification and benchmark requested, and item is safe to check.
-- Perform the two calculations and benchmark the algorithms.
-- NOTE: Debug profiling is counted in milliseconds. For the old
-- algorithm, we're also including the time it takes to create
-- the "bloated table", since that's how the old TSM algorithm
-- created the table too (adds 1 row per "individual stack item").
-- SEE: https://wowpedia.fandom.com/wiki/API_debugprofilestop
local time_start_ms = debugprofilestop()
-- Generate an old-school data table, where we insert one row
-- per "item" per stack, so 5 stacks of 1000 items would mean
-- a total of 5000 rows, each with the individual item's price.
local data_new_records = data.records
local data_old_table = {records={}, minBuyout=data.minBuyout, quantity=data.quantity}
local data_old_records = data_old_table.records
for stack_idx=1, #data_new_records do
local stack_size, buyout_per_item = unpack(data_new_records[stack_idx])
for this_stack_item_idx=1, stack_size do
-- NOTE: The old algorithm used this exact tinsert()
-- method instead of the faster "tbl[#tbl + 1]" technique,
-- so that's why we're keeping that slow method here.
tinsert(data_old_records, buyout_per_item)
end
end
-- Verify that the "old-school table" contains ALL "expanded" items.
if #data_old_records ~= data.quantity then
TSM:Print(format("TABLE CREATION ERROR: item=%d, expected quantity=%d, created quantity=%d", itemID, data.quantity, #data_old_records))
end
-- Generate the old algorithm's value and finish its benchmark.
local old_marketValue = Data:CalculateMarketValue(data_old_table, verifyNewAlgorithm)
local time_elapsed_old_ms = (debugprofilestop() - time_start_ms)
-- Now generate the new algorithm's value and benchmark it too.
-- NOTE: This new algorithm is 1.3x-27.3x faster depending on
-- input data size, and on average 5x faster for most data. :)
time_start_ms = debugprofilestop()
marketValue = Data:CalculateMarketValue(data, verifyNewAlgorithm)
local time_elapsed_new_ms = (debugprofilestop() - time_start_ms)
-- Verify the calculations to ensure the algorithms are equal.
-- NOTE: Yes, the algorithms are perfectly equal for all input data.
if old_marketValue ~= marketValue then
TSM:Print(format("! ALGORITHM ERROR: item=%d, old=%.1f, new=%.1f", itemID, old_marketValue, marketValue))
end
-- Output benchmark results, but only for items with at least 500 entries.
-- NOTE: Comment this out if you're only interested in errors
-- above, or feel free to raise cutoff quantity to reduce logging.
if data.quantity >= 500 then
TSM:Print(format("+ ALGORITHM SPEED: item=%d, quantity=%d, match=%s, old=%.1f, new=%.1f, old_speed=%f ms, new_speed=%f ms, speedup=%.2f x",
itemID, data.quantity, old_marketValue == marketValue and "YES" or "NO", old_marketValue, marketValue, time_elapsed_old_ms, time_elapsed_new_ms, time_elapsed_old_ms > 0 and (time_elapsed_old_ms / time_elapsed_new_ms) or math.huge)) -- Prevents division by zero.
end
end
-- Detect whether it was POSSIBLE to calculate a market value, and
-- ONLY proceed with the item updates if we were able to calculate
-- a new market value. Otherwise, skip the item as if it "didn't even
-- exist in this scan", since it basically "doesn't exist" if there
-- were no buyout prices to calculate a new market value from!
-- NOTE: This can happen if the scan data only contained "bid without
-- buyout" items, meaning they didn't have any per-item buyout data,
-- which can ONLY happen via "Scanning.lua:ProcessScanData()" when
-- doing a normal "Full Scan" or "Group Scan" (not "GetAll"). If
-- there aren't any buyout prices for the item, it still gets added
-- without any "records". This differs from "GetAll" which only adds
-- items to the queue if they had at least one "buyout price" auction.
-- NOTE: We're skipping the empty/indeterminable items to ensure that
-- we have identical behavior for both "GetAll" and all other scan
-- types, so that we NEVER add "empty/missing" market values for items!
-- NOTE: We allow a market value of "0", since it means there was
-- valid data in the calculations. However, "0" is extremely unlikely
-- since it would require a single, huge stack of items for a price
-- of 1 copper or so, to make the per-item market value end up at
-- just "0" for that item. Basically, it's never gonna happen!
if marketValue and (marketValue >= 0) then
-- Fetch our archived data (if we have any) for this itemID.
TSM:DecodeItemData(itemID)
TSM.data[itemID] = TSM.data[itemID] or {scans={}, lastScan = 0}
local marketValue = Data:CalculateMarketValue(data.records)
-- Update market scan statistics for this item.
local scanData = TSM.data[itemID].scans
scanData[day] = scanData[day] or {avg=0, count=0}
if type(scanData[day]) == "number" then
-- this should never happen...
-- Original code comment here: "This should never happen..."
-- NOTE: WTF was TSM's original author doing here? They're
-- converting "scanData[day]" into an array with 1 numeric
-- value, and mixing that array data with hashmap keys below,
-- so it seems like they're storing some data with numeric
-- keys and others with hashmap keys, all in the same table...
scanData[day] = {scanData[day]}
end
scanData[day].avg = scanData[day].avg or 0
@ -172,12 +310,22 @@ function Data:ProcessData(scanData, groupItems)
scanData[day].avg = floor((scanData[day].avg * scanData[day].count + marketValue) / (scanData[day].count + 1) + 0.5)
scanData[day].count = scanData[day].count + 1
-- Remember the item's scan date, cheapest buyout price on AH right now,
-- and how many items in total exist on AH (adds together all stacks).
-- NOTE: We only update "minBuyout" if the scanned data for that
-- item contains a "greater than 0" buyout value. That was mostly
-- necessary in the past, when TSM sloppily included bid-only items
-- in the data, but should no longer be able to happen with our new code!
TSM.data[itemID].lastScan = TSM.db.factionrealm.lastCompleteScan
TSM.data[itemID].minBuyout = data.minBuyout > 0 and data.minBuyout or nil
TSM.data[itemID].quantity = data.quantity
TSM.data[itemID].quantity = data.quantity -- Counts all items of all stacks.
Data:UpdateMarketValue(TSM.data[itemID])
-- Update our archived, encoded representation of this item's data.
TSM:EncodeItemData(itemID)
end
-- Update our processing-index to point at the next item.
index = index + 1
end
end
@ -186,10 +334,45 @@ function Data:ProcessData(scanData, groupItems)
TSMAPI:CreateTimeDelay("adbProcessDelay", 0, DoDataProcessing, 0.1)
end
function Data:CalculateMarketValue(records)
--- Calculate the current market value of an item, from the given scan data.
-- @param data The market scan data. Beware that we will automatically mutate the "data.records" table to sort the incoming data!
-- @param[opt] hide_oldschool_warning Boolean 'true' to suppress the warning if you're using the old-school algorithm. This is only useful when benchmarking!
function Data:CalculateMarketValue(data, hide_oldschool_warning)
-- All auctions/stacks for this item (contains their price per item, and each stack's item count).
-- NOTE: The old-school algorithm instead uses bloated records (see description further down).
local records = data.records
-- How many of this item currently exists in total on the auction house (combines all stacks).
-- NOTE: This is the sum of the per-stack counts of all "records", and can be trusted completely.
local total_quantity = data.quantity
-- If we've been given zero records, return a market value of -1 to signal the issue.
-- NOTE: If we don't do this filtering, we would end up with "division by zero" below.
if (type(records) ~= "table") or (#records <= 0) then
return -1
end
-- Determine which algorithm to use; either old-school or the smart, "compact" algorithm.
if type(records[1]) ~= "table" then
-- USE THE OLD, BRAINDEAD ALGORITHM IF WE'VE BEEN GIVEN OLD-SCHOOL "BLOATED" RECORDS.
-- NOTE: This old TSM algorithm relies on tables with millions of entries,
-- which often leads to out-of-memory crashes and is also extremely slow.
if not hide_oldschool_warning then
-- Warn if we've been called with old-school data and we haven't
-- been told to suppress this warning (benchmarks will suppress it).
TSM:Print("Warning: Calculating old-school market value. The calling code needs to be rewritten to use the new method!")
end
local totalNum, totalBuyout = 0, 0
local numRecords = #records
-- See "STEP 1" of new algorithm for explanation about why we MUST sort.
tsort(records, function(a_buyout_per_item, b_buyout_per_item)
-- Sort by "per-item buyout" in ascending order.
return a_buyout_per_item < b_buyout_per_item
end)
for i=1, numRecords do
totalNum = i - 1
if i ~= 1 and i > numRecords*MIN_PERCENTILE and (i > numRecords*MAX_PERCENTILE or records[i] >= MAX_JUMP*records[i-1]) then
@ -203,13 +386,13 @@ function Data:CalculateMarketValue(records)
end
local uncorrectedMean = totalBuyout / totalNum
local varience = 0
local variance = 0
for i=1, totalNum do
varience = varience + (records[i]-uncorrectedMean)^2
variance = variance + (records[i]-uncorrectedMean)^2
end
local stdDev = sqrt(varience/totalNum)
local stdDev = sqrt(variance/totalNum)
local correctedTotalNum, correctedTotalBuyout = 1, uncorrectedMean
for i=1, totalNum do
@ -222,4 +405,265 @@ function Data:CalculateMarketValue(records)
local correctedMean = floor(correctedTotalBuyout / correctedTotalNum + 0.5)
return correctedMean
else
-- Rewritten, cleaned up and faster algorithm, which uses almost zero memory
-- and NEVER causes any memory overflow crashes, unlike the old algorithm.
-- AUTHOR: Gnomezilla on Warmane-Icecrown [https://github.com/Bananaman].
-- NOTE: This new algorithm is 1.3x-27.3x faster depending on input data
-- size, and on average 5x faster for most data. :)
-- NOTE: All code is heavily commented, to help other programmers understand
-- the complex algorith, and to avoid future breakages due to misunderstandings.
-- NOTE: TSM's intended algorithm is also documented online, but they
-- describe a slightly altered (more modern) algorithm than what we're using:
-- https://support.tradeskillmaster.com/en_US/custom-strings/how-is-auctiondb-market-value-calculated
-- Archived in case TSM deletes the page: https://archive.ph/LhSOI
-- How many of the cheapest items to consider (default: at least
-- 15%, at most 30% of the cheapest items). All items which are more
-- expensive than them are ignored.
-- NOTE: This is considered as the total of all items (combined
-- quantity of all items in all stacks). So if the first (cheapest)
-- stack is massive, and subsequent stacks are small, then we'll
-- only be calculating the value of the items of the 1st stack.
local idx_min_percentile = total_quantity * MIN_PERCENTILE
local idx_max_percentile = total_quantity * MAX_PERCENTILE
-- Keep track of how many items we've processed and their combined buyout.
local processed_quantity, processed_total_buyout = 0, 0
-- Cutoff value for how much the "next auction" is allowed to cost,
-- so that we can ignore all overpriced items. Default: Any price
-- increase higher than 120% will discard all subsequent auctions.
-- NOTE: We only need to update this when we switch to another "stack",
-- and we're initializing it to a special "infinity" value (math.huge)
-- to ensure that we don't use this value until we've calculated it.
local max_jump_buyout_per_item = math.huge
-- Keep track of the total "item index" we're at while we're traversing
-- through our compact "stacks". This emulates the classic way TSM
-- keeps track of the item/record counter.
local item_idx = 0
-- Used for signaling that we want to abort processing the remaining records.
local skip_remaining_records = false
-- STEP 1 (EXTREMELY IMPORTANT): We CANNOT trust the input. Our market
-- value algorithm ONLY WORKS if the prices are SORTED in ASCENDING ORDER,
-- but the auctions themselves are in RANDOM ORDER by default, in most
-- cases. So we MUST forcibly sort them now, otherwise we'll randomly
-- end up with very expensive items at the start of the list, which then
-- becomes extremely INCORRECT market values in our database, such as
-- thinking that "Wool Cloth" could be worth crazy amounts like "50 gold
-- per 1 wool cloth" instead of its real market value, thus breaking TSM!
tsort(records, function(a, b)
-- NOTE: Direct table refs instead of unpack() speeds up the sorting
-- by 3x, due to all the calls/comparisons involved when sorting.
local a_stack_size, a_buyout_per_item = a[1], a[2]
local b_stack_size, b_buyout_per_item = b[1], b[2]
-- Sort by "per-item buyout" in ascending order, and use "stack size"
-- in ascending order as a fallback if two stacks have identical prices.
if a_buyout_per_item ~= b_buyout_per_item then
return a_buyout_per_item < b_buyout_per_item
else
return a_stack_size < b_stack_size
end
end)
-- STEP 2: Calculate the total buyout of all items we'll be processing.
-- Process all of the compact "stack" records.
-- NOTE: Every record is 1 auction/stack, with a size and buyout-per-item.
for stack_idx=1, #records do
-- Fetch the stack size and buyout-per-item of this stack.
local stack_size, buyout_per_item = unpack(records[stack_idx])
-- Run TSM's algorithm for EVERY individual "item" in the stack's size,
-- so that we perform the same per-"item" work as their braindead algo.
-- NOTE: Does nothing if "stack_size" is <= 0.
-- NOTE: Technically, it would be possible to further optimize this
-- algorithm to "skip past entire stacks" all at once, instead of
-- processing every individual "virtual item from the stack", but
-- the algorithm is already so fast that adding extra complexity
-- is pointless and would just make it much harder to maintain!
for this_stack_item_idx=1, stack_size do
-- Increment the "total item index" that we're at now (across all stacks).
-- NOTE: Emulates TSM's counting of every "item" as individual records.
item_idx = item_idx + 1
-- Calculate the max allowed "buyout-per-item" price jump of the
-- current item.
-- NOTE: Yeah this leads to weirdness if we ended up including items
-- that were overpriced but were required by the "must process 15%+
-- of all items no matter what" TSM requirement, but TSM fixes
-- that problem during later filtering "steps" of the algorithm.
-- NOTE: We're only recalculating the value when the "previous
-- item" changes, meaning that unlike the old-school TSM algo,
-- we don't waste time re-calculating it thousands of times in
-- a row for large stacks of identical "per-item" prices.
-- NOTE: This optimization has been verified to generate the EXACT
-- same behavior/result as the old-schoold TSM algorithm.
-- NOTE: Yes, item 1 of stack 1 keeps the pre-initialized value
-- of "math.huge (infinity)" (above), since we don't need it yet.
-- NOTE: In effect, we're ALWAYS comparing the CURRENT item's
-- buyout price to a "max buyout" calculated from the "PREVIOUS
-- item". This "stack magic" is just an optimization to reduce
-- the CPU usage of the algorithm!
if this_stack_item_idx == 1 and stack_idx >= 2 then
-- We're on the FIRST item of a NEW stack (not stack 1).
-- Re-calculate "max buyout price" based on PREVIOUS stack's price.
-- NOTE: We could cache the "previous" data to avoid the lookup
-- here, but we wouldn't really save any meaningful CPU usage
-- and it would be harder for other programmers to understand.
local previous_stack_size, previous_buyout_per_item = unpack(records[stack_idx - 1])
max_jump_buyout_per_item = MAX_JUMP * previous_buyout_per_item
elseif this_stack_item_idx == 2 then
-- We're on the SECOND item of SAME stack (any stack, even stack 1).
-- Re-calculate "max buyout price" based on THIS stack's price.
max_jump_buyout_per_item = MAX_JUMP * buyout_per_item
end
-- If we're on "total item index" 2 or higher, and we've processed
-- at least "min percentile" amount of records, AND we've -EITHER-
-- reached the "maximum percentile" too -OR- we've reached a severely
-- overpriced stack before we could reach the "maximum percentile",
-- and if so, we'll skip all other records (remaining stacks/items).
if item_idx >= 2
and item_idx > idx_min_percentile
and (item_idx > idx_max_percentile or buyout_per_item >= max_jump_buyout_per_item) then
-- Signal that we want to skip the remaining records.
skip_remaining_records = true
break
end
-- Keep track of the total buyout value of all processed items.
-- NOTE: This is the main purpose of this particular loop.
processed_total_buyout = processed_total_buyout + buyout_per_item
-- Calculate "how many items we have processed", which is simply
-- the same as the current item index.
-- NOTE: TSM's classic algo does this as "item_idx - 1" because
-- they update this counter BEFORE they've decided whether to
-- actually process the current item. We instead only update this
-- after we've processed and incremented "total buyout" above,
-- which has the same end result but is much easier to understand.
-- NOTE: TSM's classic algo also has a weird chunk after this line,
-- which does "if item_idx == total_quantity then
-- processed_quantity = item_idx; end", which is complete nonsense
-- and was only needed due to their braindead sequence of updating
-- the "processed_quantity" variable, since they clearly just tried
-- to avoid having "processed_quantity = 0" when "total_quantity = 1",
-- but that was an extremely idiotic "fix" for their bad code. :)
processed_quantity = item_idx
end
-- Exit from the nested loop if we've been told to stop processing records.
if skip_remaining_records then break end
end
-- TSM:Print(format("processed_quantity: %d, processed_total_buyout: %d", processed_quantity, processed_total_buyout)) -- DEBUG
-- STEP 3: Calculate the mean (simple average) of all processed items.
local uncorrected_mean = processed_total_buyout / processed_quantity
-- STEP 4: Calculate the standard deviation of all processed items.
local variance = 0
-- Process all of the compact "stack" records again, but stop when we hit the limit.
-- NOTE: To understand this looping algorithm, look at STEP 2's comments above.
item_idx = 0
skip_remaining_records = false
for stack_idx=1, #records do
local stack_size, buyout_per_item = unpack(records[stack_idx])
for this_stack_item_idx=1, stack_size do
item_idx = item_idx + 1
-- Process up to and including "processed_quantity", but not higher.
if item_idx > processed_quantity then
skip_remaining_records = true
break
end
-- Calculate the updated variance.
variance = variance + ((buyout_per_item - uncorrected_mean)^2)
end
if skip_remaining_records then break end
end
local std_dev = sqrt(variance / processed_quantity)
-- TSM:Print(format("std_dev: %f, variance: %f", std_dev, variance)) -- DEBUG
-- STEP 5: Ignore all data points that are more than 1.5x std_dev away from the average.
-- Initialize the "corrected" quantity and buyout with 1 "fake" item
-- that has the same value as the uncorrected mean.
-- NOTE: We're replicating TSM 2.8's classic algorithm here... even
-- though their algorithm might not be perfect.
local corrected_processed_quantity, corrected_processed_total_buyout = 1, uncorrected_mean
-- Calculate the standard deviation cutoff. Anything further away will be ignored.
local std_dev_cutoff = 1.5 * std_dev
-- Process all of the compact "stack" records again, but stop when we hit the limit.
-- NOTE: To understand this looping algorithm, look at STEP 2's comments above.
item_idx = 0
skip_remaining_records = false
for stack_idx=1, #records do
local stack_size, buyout_per_item = unpack(records[stack_idx])
-- Speedup: Since the "buyout_per_item" only changes when we switch
-- to a different stack (all items in a stack have the same per-item
-- prices), we can therefore pre-calculate their deviation from the
-- average (mean) here, to avoid having to do it per-item below.
local abs_deviation_from_mean = abs(uncorrected_mean - buyout_per_item)
-- Speedup: We can also pre-calculate whether the items of this "stack"
-- all fit the rule of "they're less than std_dev cutoff away from mean".
local stack_include_items = abs_deviation_from_mean < std_dev_cutoff
-- Loop through all virtual "items" of the current "stack".
for this_stack_item_idx=1, stack_size do
item_idx = item_idx + 1
-- Process up to and including "processed_quantity", but not higher.
if item_idx > processed_quantity then
skip_remaining_records = true
break
end
-- Calculate the filtered "quantity" and "total buyout", which
-- ignores anything that's more than "std_dev_cutoff" away from avg.
if stack_include_items then
corrected_processed_quantity = corrected_processed_quantity + 1
corrected_processed_total_buyout = corrected_processed_total_buyout + buyout_per_item
end
end
if skip_remaining_records then break end
end
-- TSM:Print(format("corrected_processed_quantity: %d, corrected_processed_total_buyout: %d", corrected_processed_quantity, corrected_processed_total_buyout)) -- DEBUG
-- STEP 6: Calculate our current market value by simply taking the
-- average of the remaining (filtered) data points.
-- NOTE: This method ensures that no poisoning of our market value can
-- take place by those who post high volume items at astronomical prices.
-- It also gets rid of more subtle outliers to determine the average.
local corrected_mean = floor((corrected_processed_total_buyout / corrected_processed_quantity) + 0.5)
return corrected_mean
end
end

26
TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.lua

@ -22,6 +22,7 @@ local savedDBDefaults = {
scanData = "",
time = 0,
lastCompleteScan = 0,
lastScanSecondsPerPage = -1,
appDataUpdate = 0,
},
profile = {
@ -33,6 +34,7 @@ local savedDBDefaults = {
marketValueTooltip = true,
minBuyoutTooltip = true,
showAHTab = true,
disableGetAll = false,
},
}
@ -372,9 +374,10 @@ local function encodeScans(scans)
local tbl, tbl2 = {}, {}
for day, data in pairs(scans) do
if type(data) == "table" and data.count and data.avg then
-- New method of encoding scans.
data = encode(data.avg).."@"..encode(data.count)
elseif type(data) == "table" then
-- Old method of encoding scans
-- Old method of encoding scans.
for i = 1, #data do
tbl2[i] = encode(data[i])
end
@ -394,13 +397,24 @@ local function decodeScans(rope)
local currentDay = TSM.Data:GetDay()
for _, data in ipairs(days) do
local day, marketValueData = (":"):split(data)
-- BUG FIXED: Guard against incorrectly encoded "day" or "marketValueData",
-- which can happen extremely rarely due to some very rare, random bug
-- somewhere else in TSM (or perhaps due to mixing different versions
-- of TSM data). The cause of the rare corruption hasn't been found.
-- NOTE: We simply skip any "days/market values" that cannot be decoded,
-- which thereby ensures that we get a cleaned-up "decode" of the data,
-- so that TSM will then write the fixed data when it next "re-encodes"
-- the "decoded in-memory representation" of this item's data!
if day ~= nil and day ~= "" and marketValueData ~= nil and marketValueData ~= "" then
day = decode(day)
-- BUG FIXED: Verify yet again that the day itself was properly decoded,
-- but this time only check for "nil" which indicates "decode()" failure.
if day ~= nil then
-- Create a "scans" table entry for the decoded day.
scans[day] = {}
--bug fix? ...SkillMaster_AuctionDB\TradeSkillMaster_AuctionDB.lua:398: bad argument #1 to 'strfind' (string expected, got nil)
if marketValueData ~= nil then
if strfind(marketValueData, "@") then
-- New method of decoding scans.
local avg, count = ("@"):split(marketValueData)
avg = decode(avg)
count = decode(count)
@ -413,7 +427,7 @@ local function decodeScans(rope)
end
end
else
-- Old method of decoding scans
-- Old method of decoding scans.
for _, value in ipairs({(";"):split(marketValueData)}) do
local decodedValue = decode(value)
if decodedValue ~= "~" then
@ -424,7 +438,7 @@ local function decodeScans(rope)
scans[day] = TSM.Data:GetAverage(scans[day])
end
end
end
end
end

1
TradeSkillMaster_Auctioning/locale/enUS.lua

@ -222,6 +222,7 @@ L["Round Normal Price"] = true
L["Running Scan..."] = true
L["Save New Price"] = true
L["Scan Complete!"] = true
L["Scanning %d / %d (Page 1 / ?)"] = true
L["Scanning %d / %d (Page %d / %d)"] = true
L["Scanning %d / %d"] = true
L["ScrollWheel Direction (both recommended):"] = true

1
TradeSkillMaster_Auctioning/locale/frFR.lua

@ -210,6 +210,7 @@ L["Right-Click to add %s to your friends list."] = "Clic-droit pour ajouter %s
-- L["Save New Price"] = ""
L["Scan Complete!"] = "Scan terminé !" -- Needs review
L["Scanning %d / %d"] = "Scan %d / %d" -- Needs review
L["Scanning %d / %d (Page 1 / ?)"] = "Scan %d / %d (Page 1 / ?)" -- Needs review
L["Scanning %d / %d (Page %d / %d)"] = "Scan %d / %d (Page %d / %d)" -- Needs review
L["ScrollWheel Direction (both recommended):"] = "Direction de la molette (les deux sont recommandées)"
-- L["Select a duration in this dropdown and click on the button below to cancel all auctions at or below this duration."] = ""

1
TradeSkillMaster_Auctioning/locale/koKR.lua

@ -210,6 +210,7 @@ L["Running Scan..."] = "검색 진행 중..." -- Needs review
L["Save New Price"] = "새 가격 저장" -- Needs review
L["Scan Complete!"] = "검색 완료!" -- Needs review
L["Scanning %d / %d"] = "검색 중 %d / %d" -- Needs review
L["Scanning %d / %d (Page 1 / ?)"] = "검색 중 %d / %d (페이지 1 / ?)" -- Needs review
L["Scanning %d / %d (Page %d / %d)"] = "검색 중 %d / %d (페이지 %d / %d)" -- Needs review
L["ScrollWheel Direction (both recommended):"] = "스크롤휠 방향 (둘다 체크 추천)"
L["Select a duration in this dropdown and click on the button below to cancel all auctions at or below this duration."] = "이 드롭다운에서 기간을 선택하고 아래의 버튼을 클릭하면 지정한 기간 또는 그 이하의 모든 경매는 취소됩니다." -- Needs review

1
TradeSkillMaster_Auctioning/locale/ruRU.lua

@ -215,6 +215,7 @@ L["Running Scan..."] = "Запуск скана..."
L["Save New Price"] = "Сохр.новую цену"
L["Scan Complete!"] = "Скан завершен!"
L["Scanning %d / %d"] = "Скан %d / %d"
L["Scanning %d / %d (Page 1 / ?)"] = "Скан %d / %d (стр 1 / ?)"
L["Scanning %d / %d (Page %d / %d)"] = "Скан %d / %d (стр %d / %d)"
L["ScrollWheel Direction (both recommended):"] = "Направление прокрутки колесика (рекомендованы оба):"
L["Select a duration in this dropdown and click on the button below to cancel all auctions at or below this duration."] = "Выберите длительность из списка и кликните на кнопку ниже, чтобы отменить все акционы ниже этой длительности."

1
TradeSkillMaster_Auctioning/locale/zhCN.lua

@ -216,6 +216,7 @@ L["Running Scan..."] = "扫描中..."
L["Save New Price"] = "保存新价格"
L["Scan Complete!"] = "扫描完成!"
L["Scanning %d / %d"] = "扫描中 %d/%d"
L["Scanning %d / %d (Page 1 / ?)"] = "扫描中 %d / %d (第1/?页)"
L["Scanning %d / %d (Page %d / %d)"] = "扫描中 %d / %d (第%d/%d页)"
L["ScrollWheel Direction (both recommended):"] = "滚轮方向 (建议都选):"
L["Select a duration in this dropdown and click on the button below to cancel all auctions at or below this duration."] = "在本下拉列表中选择一个期限,然后点击下面的按钮来取消低于这个时间的拍卖。"

27
TradeSkillMaster_Auctioning/modules/ScanUtil.lua

@ -17,10 +17,6 @@ Scan.skipped = {}
local function CallbackHandler(event, ...)
if event == "QUERY_COMPLETE" then
local filterList = ...
local numItems = 0
for _, v in ipairs(filterList) do
numItems = numItems + #v.items
end
Scan.filterList = filterList
Scan.numFilters = #filterList
Scan:ScanNextFilter()
@ -32,8 +28,19 @@ local function CallbackHandler(event, ...)
tinsert(Scan.skipped, itemString)
end
elseif event == "SCAN_PAGE_UPDATE" then
-- Simply forward the "currently received" and "total" page counts
-- for the current item we're scanning.
-- NOTE: Private servers sometimes give totally insane "total page counts"
-- on the first response, such as "page 1/142", but those server errors
-- always correct themselves to "page 2/REAL" when the scan has reached
-- page 2. There's nothing we can do to avoid that rare page-count issue
-- (which happens on many popular private servers, such as Warmane).
TSM.Manage:UpdateStatus("page", ...)
elseif event == "SCAN_INTERRUPTED" then
elseif event == "SCAN_INTERRUPTED" or event == "INTERRUPTED" then
-- We've been interrupted by the Auction House closing.
-- NOTE: "SCAN_INTERRUPTED" is from LibAuctionScan-1.0, which isn't used
-- by TSM anymore, and "INTERRUPTED" is from "TSM/Auction/AuctionScanning.lua",
-- which is what this scanner uses nowadays.
TSM.Manage:ScanComplete(true)
elseif event == "SCAN_TIMEOUT" then
tremove(Scan.filterList, 1)
@ -60,6 +67,16 @@ function Scan:StartItemScan(itemList)
end
function Scan:ScanNextFilter()
-- We must reset the page counter, otherwise the next scan will keep the
-- page count of the previous item until we receive "SCAN_PAGE_UPDATE".
-- NOTE: The "nil" signals that we don't know the item's page count yet.
-- NOTE: The recipient may want to ignore "page" events that have nil values
-- and not update their status bars based on those, since we send these empty
-- page events before we start each new scan!
TSM.Manage:UpdateStatus("page", nil, nil)
-- Now update the scan counter.
-- NOTE: Our scan progress counter below starts counting from 0 as the first item.
if #Scan.filterList == 0 then
TSM.Manage:UpdateStatus("scan", Scan.numFilters, Scan.numFilters)
return TSM.Manage:ScanComplete()

33
TradeSkillMaster_Auctioning/modules/manage.lua

@ -85,6 +85,9 @@ end
function Manage:ScanComplete(interrupted)
if interrupted then
-- If our scan has been interrupted by the Auction House closing,
-- simply act as if the user clicked "Stop", but with an extra flag
-- to also clarify that this was interrupted.
Util:Stop(true)
else
local numToManage = Util:DoneScanning()
@ -106,24 +109,40 @@ local function IsStepDone(step)
end
-- update the statusbar
function Manage:UpdateStatus(statusType, current, total)
-- The ScanUtil.lua events trigger us with the "statusType" and the arguments
-- such as what page we're on. We then update our current scan status based on that.
scanStatus[statusType] = {current, total}
-- Ignore the empty "reset page count" events that happen before every new
-- scan. We've reset the page counts, which is the only thing that matters.
if statusType == "page" and current == nil and total == nil then
return
end
-- Handle the progress event by updating our status bar.
if statusType == "query" then
if total >= 0 then
GUI.statusBar:SetStatusText(format(L["Preparing Filter %d / %d"], current, total))
else
GUI.statusBar:SetStatusText(format(L["Preparing Filters..."], current, total))
end
elseif IsStepDone("scan") and IsStepDone("manage") and IsStepDone("confirm") then -- scan complete
elseif IsStepDone("scan") and IsStepDone("manage") and IsStepDone("confirm") then
-- The entire scan of all items is complete.
GUI.statusBar:SetStatusText(L["Scan Complete!"])
else
local parts = {}
if IsStepDone("scan") then
tinsert(parts, L["Done Scanning"])
elseif IsStepStarted("scan") then
-- NOTE: In the label, we count the items starting at 1, to say
-- "Scanning 1 / 2" (instead of "Scanning 0 / 2").
if IsStepStarted("page") then
tinsert(parts, format(L["Scanning %d / %d (Page %d / %d)"], scanStatus.scan[1], scanStatus.scan[2], scanStatus.page[1], scanStatus.page[2]))
-- We have received the page counter ("current page / total pages") for the current item.
-- NOTE: We add "+1" to the page counter, to indicate that we've received that page and are working on the next page.
tinsert(parts, format(L["Scanning %d / %d (Page %d / %d)"], scanStatus.scan[1] + 1, scanStatus.scan[2], min(scanStatus.page[1] + 1, scanStatus.page[2]), scanStatus.page[2]))
else
tinsert(parts, format(L["Scanning %d / %d"], scanStatus.scan[1], scanStatus.scan[2]))
-- We have started a new item scan but haven't received the page count yet.
tinsert(parts, format(L["Scanning %d / %d (Page 1 / ?)"], scanStatus.scan[1] + 1, scanStatus.scan[2]))
end
end
if IsStepDone("manage") then
@ -133,18 +152,18 @@ function Manage:UpdateStatus(statusType, current, total)
tinsert(parts, L["Done Canceling"])
end
if IsStepStarted("confirm") then
tinsert(parts, format(L["Confirming %d / %d"], scanStatus.confirm[1]+1, scanStatus.confirm[2]))
tinsert(parts, format(L["Confirming %d / %d"], scanStatus.confirm[1] + 1, scanStatus.confirm[2]))
else
tinsert(parts, format(L["Confirming %d / %d"], 1, scanStatus.manage[2]))
end
elseif IsStepDone("scan") and IsStepStarted("manage") then
if mode == "Post" then
tinsert(parts, format(L["Posting %d / %d"], scanStatus.manage[1]+1, scanStatus.manage[2]))
tinsert(parts, format(L["Posting %d / %d"], scanStatus.manage[1] + 1, scanStatus.manage[2]))
elseif mode == "Cancel" then
tinsert(parts, format(L["Canceling %d / %d"], scanStatus.manage[1]+1, scanStatus.manage[2]))
tinsert(parts, format(L["Canceling %d / %d"], scanStatus.manage[1] + 1, scanStatus.manage[2]))
end
if IsStepStarted("confirm") then
tinsert(parts, format(L["Confirming %d / %d"], scanStatus.confirm[1]+1, scanStatus.confirm[2]))
tinsert(parts, format(L["Confirming %d / %d"], scanStatus.confirm[1] + 1, scanStatus.confirm[2]))
else
tinsert(parts, format(L["Confirming %d / %d"], 1, scanStatus.manage[2]))
end

12
TradeSkillMaster_Crafting/Modules/Cost.lua

@ -72,12 +72,12 @@ function Cost:GetCraftCost(itemID)
for matID, matQuantity in pairs(craft.mats) do
local MatName = GetItemInfo(matID)
-- if MatName ~= nil and strfind(MatName, "Vellum") then
-- local NewItemString = CheapestVellum(matID)
-- if matID ~= NewItemString then
-- matID = NewItemString
-- end
-- end
if MatName ~= nil and strfind(MatName, "Vellum") then
local NewItemString = CheapestVellum(matID)
if matID ~= NewItemString then
matID = NewItemString
end
end
local matCost = Cost:GetMatCost(matID)
if not matCost or matCost == 0 then
costIsValid = false

42
TradeSkillMaster_Crafting/Modules/CraftingGUI.lua

@ -675,15 +675,21 @@ function GUI:CreateQueueFrame(parent)
if strfind(name, "Vellum") then
velName = name
end
if (velName ~= nil) then
if strfind(velName, "Weapon") then
itemIDx = "item:52511:0:0:0:0:0:0"
if (velName ~= nil) and (not strfind(velName, "III")) then
local VellumReplacePrice = TSM.Cost:GetMatCost(itemIDx)
if strfind(velName, "Weapon Vellum") then
if VellumReplacePrice > TSM.Cost:GetMatCost("item:43146:0:0:0:0:0:0") then
itemIDx = "item:43146:0:0:0:0:0:0"
name = TSMAPI:GetSafeItemInfo(itemIDx)
end
else
itemIDx = "item:52510:0:0:0:0:0:0"
if VellumReplacePrice > TSM.Cost:GetMatCost("item:43145:0:0:0:0:0:0") then
itemIDx = "item:43145:0:0:0:0:0:0"
name = TSMAPI:GetSafeItemInfo(itemIDx)
end
end
end
local inventory = TSM.Inventory:GetPlayerBagNum(itemIDx)
local need = matQuantity * data.numQueued
@ -1449,7 +1455,7 @@ function GUI:CreateCraftInfoFrame(parent)
-- Enable display of items created
local lNum, hNum = GetTradeSkillNumMade(skillIndex)
local numMade = floor(((lNum or 1) + (hNum or 1)) / 2)
if altVerb ~= nil and strfind(name,"Enchant ") then
if altVerb == ENSCRIBE then
numMade = 1
end
if numMade > 1 then
@ -1486,14 +1492,15 @@ function GUI:CreateCraftInfoFrame(parent)
end
end
-- if altVerb == ENSCRIBE then
if altVerb ~= nil and strfind(name,"Enchant ") then
if altVerb == ENSCRIBE then
createAllBtn:SetText(L["Enchant Vellum"])
-- createAllBtn.vellum = TSMAPI:GetSafeItemInfo("item:38682:0:0:0:0:0:0")
if strfind(name, "Weapon") or strfind(name, "Staff") then
createAllBtn.vellum = TSMAPI:GetSafeItemInfo("item:52511:0:0:0:0:0:0") -- Weapon Vellum
createAllBtn.vellum = TSMAPI:GetSafeItemInfo("item:43146:0:0:0:0:0:0") -- Weapon Vellum III
else
createAllBtn.vellum = TSMAPI:GetSafeItemInfo("item:52510:0:0:0:0:0:0") -- Armor Vellum
createAllBtn.vellum = TSMAPI:GetSafeItemInfo("item:43145:0:0:0:0:0:0") -- Armor Vellum III
end
else
createAllBtn:SetText(CREATE_ALL)
createAllBtn.vellum = nil
@ -1817,6 +1824,12 @@ function GUI:UpdateQueue()
canCraft = min(canCraft, floor(numHave / quantity))
end
-- local velName
-- local VELLUM_ID = "item:38682:0:0:0:0:0:0"
-- if TSM.db.factionrealm.crafts[spellID].mats[VELLUM_ID] then
-- velName = GetItemInfo(VELLUM_ID) or TSM.db.factionrealm.mats[VELLUM_ID].name
-- end
local color
local craftIndex = skillIndexLookup[spellID]
if canCraft >= numQueued then
@ -2740,7 +2753,7 @@ end
function CheapestVellum(itemPassed)
-- Return one of the two vellum available
-- Get Cheapest vellum, lower vellum types can be replaced by III
local MatName = GetItemInfo(itemPassed)
-- MatName is sometimes nil ???
if MatName ~= nil then
@ -2748,11 +2761,12 @@ function CheapestVellum(itemPassed)
if strfind(MatName, "Vellum") then
velName = MatName
end
if (velName ~= nil) then
if strfind(velName, "Weapon") then
itemPassed = "item:52511:0:0:0:0:0:0"
if (velName ~= nil) and (not strfind(velName, "III")) then
local VellumReplacePrice = TSM.Cost:GetMatCost(itemPassed) or 0
if strfind(velName, "Weapon Vellum") then
if VellumReplacePrice > (TSM.Cost:GetMatCost("item:43146:0:0:0:0:0:0") or 0) then itemPassed = "item:43146:0:0:0:0:0:0" end
else
itemPassed = "item:52510:0:0:0:0:0:0"
if VellumReplacePrice > (TSM.Cost:GetMatCost("item:43145:0:0:0:0:0:0") or 0) then itemPassed = "item:43145:0:0:0:0:0:0" end
end
end
end

192
TradeSkillMaster_Crafting/Modules/EnchantingInfo.lua

@ -14,7 +14,89 @@ TSM.enchantingName = GetSpellInfo(7411)
-- looks up the itemID of the scroll that the enchant makes
-- index = spellID of the enchant
-- value = itemID of scroll
-- Enchant List:
TSM.enchantingItemIDs = {
-- -- MOP
-- [110764] = 79061, -- Enchant Weapon - Pandamonium
-- [104338] = 74700, -- Enchant Bracer - Mastery
-- [104385] = 74701, -- Enchant Bracer - Major Dodge
-- [104389] = 74703, -- Enchant Bracer - Super Intellect
-- [104390] = 74704, -- Enchant Bracer - Exceptional Strength
-- [104391] = 74705, -- Enchant Bracer - Greater Agility
-- [104392] = 74706, -- Enchant Chest - Super Resilience
-- [104393] = 74707, -- Enchant Chest - Mighty Spirit
-- [104395] = 74708, -- Enchant Chest - Glorious Stats
-- [104397] = 74709, -- Enchant Chest - Superior Stamina
-- [104398] = 74710, -- Enchant Cloak - Accuracy
-- [104401] = 74711, -- Enchant Cloak - Greater Protection
-- [104403] = 74712, -- Enchant Cloak - Superior Intellect
-- [104404] = 74713, -- Enchant Cloak - Superior Critical Strike
-- [104407] = 74715, -- Enchant Boots - Greater Haste
-- [104408] = 74716, -- Enchant Boots - Greater Precision
-- [104409] = 74717, -- Enchant Boots - Blurred Speed
-- [104414] = 74718, -- Enchant Boots - Pandaren's Step
-- [104416] = 74719, -- Enchant Gloves - Greater Haste
-- [104417] = 74720, -- Enchant Gloves - Superior Expertise
-- [104419] = 74721, -- Enchant Gloves - Super Strength
-- [104420] = 74722, -- Enchant Gloves - Superior Mastery
-- [104445] = 74729, -- Enchant Off-Hand - Major Intellect
-- [130758] = 89737, -- Enchant Shield - Greater Parry
-- [104425] = 74723, -- Enchant Weapon - Windsong
-- [104427] = 74724, -- Enchant Weapon - Jade Spirit
-- [104430] = 74725, -- Enchant Weapon - Elemental Force
-- [104434] = 74726, -- Enchant Weapon - Dancing Steel
-- [104440] = 74727, -- Enchant Weapon - Colossus
-- [104442] = 74728, -- Enchant Weapon - River's Song
-- -- CATA
-- [74132] = 52687, -- Enchant Gloves - Mastery
-- [74189] = 52743, -- Enchant Boots - Earthen Vitality
-- [74191] = 52744, -- Enchant Chest - Mighty Stats
-- [74192] = 52745, -- Enchant Cloak - Lesser Power
-- [74193] = 52746, -- Enchant Bracer - Speed
-- [74197] = 52748, -- Enchant Weapon - Avalanche
-- [74198] = 52749, -- Enchant Gloves - Haste
-- [74199] = 52750, -- Enchant Boots - Haste
-- [74200] = 52751, -- Enchant Chest - Stamina
-- [74201] = 52752, -- Enchant Bracer - Critical Strike
-- [74202] = 52753, -- Enchant Cloak - Intellect
-- [74207] = 52754, -- Enchant Shield - Protection
-- [74211] = 52755, -- Enchant Weapon - Elemental Slayer
-- [74212] = 52756, -- Enchant Gloves - Exceptional Strength
-- [74213] = 52757, -- Enchant Boots - Major Agility
-- [74214] = 52758, -- Enchant Chest - Mighty Resilience
-- [74220] = 52759, -- Enchant Gloves - Greater Expertise
-- [74223] = 52760, -- Enchant Weapon - Hurricane
-- [74225] = 52761, -- Enchant Weapon - Heartsong
-- [74226] = 52762, -- Enchant Shield - Mastery
-- [74229] = 52763, -- Enchant Bracer - Superior Dodge
-- [74230] = 52764, -- Enchant Cloak - Critical Strike
-- [74231] = 52765, -- Enchant Chest - Exceptional Spirit
-- [74232] = 52766, -- Enchant Bracer - Precision
-- [74234] = 52767, -- Enchant Cloak - Protection
-- [74235] = 52768, -- Enchant Off-Hand - Superior Intellect
-- [74236] = 52769, -- Enchant Boots - Precision
-- [74237] = 52770, -- Enchant Bracer - Exceptional Spirit
-- [74238] = 52771, -- Enchant Boots - Mastery
-- [74239] = 52772, -- Enchant Bracer - Greater Expertise
-- [74240] = 52773, -- Enchant Cloak - Greater Intellect
-- [74242] = 52774, -- Enchant Weapon - Power Torrent
-- [74244] = 52775, -- Enchant Weapon - Windwalk
-- [74246] = 52776, -- Enchant Weapon - Landslide
-- [74247] = 52777, -- Enchant Cloak - Greater Critical Strike
-- [74248] = 52778, -- Enchant Bracer - Greater Critical Strike
-- [74250] = 52779, -- Enchant Chest - Peerless Stats
-- [74251] = 52780, -- Enchant Chest - Greater Stamina
-- [74252] = 52781, -- Enchant Boots - Assassin's Step
-- [74253] = 52782, -- Enchant Boots - Lavawalker
-- [74254] = 52783, -- Enchant Gloves - Mighty Strength
-- [74255] = 52784, -- Enchant Gloves - Greater Mastery
-- [74256] = 52785, -- Enchant Bracer - Greater Speed
-- [74195] = 52747, -- Enchant Weapon - Mending
-- [96264] = 68784, -- Enchant Bracer - Agility
-- [96261] = 68785, -- Enchant Bracer - Major Strength
-- [96262] = 68786, -- Enchant Bracer - Mighty Intellect
-- [95471] = 68134, -- Enchant 2H Weapon - Mighty Agility
-- WRATH
-- [44506] = 38960, -- Enchant Gloves - Gatherer
-- [44484] = 38951, -- Enchant Gloves - Expertise
@ -84,7 +166,7 @@ TSM.enchantingName = GetSpellInfo(7411)
-- [64579] = 46098, -- Enchant Weapon - Blood Draining
-- [71692] = 50816, -- Enchant Gloves - Angler
-- TBC
-- -- TBC
-- [33990] = 38928, -- Enchant Chest - Major Spirit
-- [33991] = 38929, -- Enchant Chest - Restore Mana Prime
-- [33992] = 38930, -- Enchant Chest - Major Resilience
@ -105,7 +187,7 @@ TSM.enchantingName = GetSpellInfo(7411)
-- [42974] = 38948, -- Enchant Weapon - Executioner
-- [42620] = 38947, -- Enchant Weapon - Greater Agility
-- VANILLA
-- -- VANILLA
-- [7745] = 38772, -- Enchant 2H Weapon - Minor Impact
-- [7786] = 38779, -- Enchant Weapon - Minor Beastslayer
-- [7788] = 38780, -- Enchant Weapon - Minor Striking
@ -259,60 +341,6 @@ TSM.enchantingName = GetSpellInfo(7411)
-- [28003] = 38926, -- Enchant Weapon - Spellsurge
-- [28004] = 38927, -- Enchant Weapon - Battlemaster
-- High Risk Ascension Content
-- [968676] = 967760, -- Enchant Weapon - Unstoppable Assault I
-- [968677] = 967761, -- Enchant Weapon - Unstoppable Assault II
-- [968678] = 967762, -- Enchant Weapon - Unstoppable Assault III
-- [968679] = 967763, -- Enchant Weapon - Lucid Assault I
-- [968680] = 967764, -- Enchant Weapon - Lucid Assault II
-- [968681] = 967765, -- Enchant Weapon - Lucid Assault III
-- [968682] = 967766, -- Enchant Weapon - Spellbinder's Rage I
-- [968683] = 967767, -- Enchant Weapon - Spellbinder's Rage II
-- [968684] = 967768, -- Enchant Weapon - Spellbinder's Rage III
-- [968685] = 967769, -- Enchant Weapon - Ninja's Focus I
-- [968686] = 967770, -- Enchant Weapon - Ninja's Focus II
-- [968687] = 967771, -- Enchant Weapon - Ninja's Focus III
-- [968688] = 967772, -- Enchant Weapon - Grovewarden's Blessing I
-- [968689] = 967773, -- Enchant Weapon - Grovewarden's Blessing II
-- [968690] = 967774, -- Enchant Weapon - Grovewarden's Blessing III
-- [968691] = 967775, -- Enchant Weapon - Viscious Assault I
-- [968692] = 967776, -- Enchant Weapon - Viscious Assault II
-- [968693] = 967777, -- Enchant Weapon - Viscious Assault III
-- [968694] = 967778, -- Enchant Weapon - Arcane Dexterity I
-- [968695] = 967779, -- Enchant Weapon - Arcane Dexterity II
-- [968696] = 967780, -- Enchant Weapon - Arcane Dexterity III
-- [968697] = 967781, -- Enchant Weapon - Arcane Artillery I
-- [968698] = 967782, -- Enchant Weapon - Arcane Artillery II
-- [968699] = 967783, -- Enchant Weapon - Arcane Artillery III
-- [968700] = 967784, -- Enchant Weapon - Arcane Precision I
-- [968701] = 967785, -- Enchant Weapon - Arcane Precision II
-- [968702] = 967786, -- Enchant Weapon - Arcane Precision III
-- [968770] = 967787, -- Enchant Weapon - Crusader II
-- [968771] = 967788, -- Enchant Weapon - Crusader III
-- [1968677] = 1204125, -- Enchant Weapon - Void Assault
-- [1968678] = 1204126, -- Enchant Weapon - Overpowering Void Assault
-- [1968680] = 1204127, -- Enchant Weapon - Dread Assault
-- [1968681] = 1204128, -- Enchant Weapon - Overpowering Dread Assault
-- [1968683] = 1204129, -- Enchant Weapon - Twisted Evoker
-- [1968684] = 1204130, -- Enchant Weapon - Overpowering Twisted Evoker
-- [1968686] = 1204131, -- Enchant Weapon - Twisted Assault
-- [1968687] = 1204132, -- Enchant Weapon - Overpowering Twisted Assault
-- [1968689] = 1204133, -- Enchant Weapon - Twisted Channeler
-- [1968690] = 1204134, -- Enchant Weapon - Overpowering Twisted Channeler
-- [1968692] = 1204135, -- Enchant Weapon - Dread Omen Strikes
-- [1968693] = 1204136, -- Enchant Weapon - Overpowering Dread Omen Strikes
-- [1968695] = 1204137, -- Enchant Weapon - Void Flows
-- [1968696] = 1204138, -- Enchant Weapon - Overpowering Void Flows
-- [1968698] = 1204139, -- Enchant Weapon - Void Blasting
-- [1968699] = 1204140, -- Enchant Weapon - Overpowering Void Blasting
-- [1968701] = 1204141, -- Enchant Weapon - Dread Precision
-- [1968702] = 1204142, -- Enchant Weapon - Overpowering Dread Precision
-- [1968770] = 1204143, -- Enchant Weapon - Twisted Crusader
-- [1968771] = 1204144, -- Enchant Weapon - Overpowering Twisted Crusader
--
TSM.enchantingItemIDs = {
[7418] = 38679, -- Scroll of Enchant Bracer - Minor Health
[7420] = 38766, -- Scroll of Enchant Chest - Minor Health
[7426] = 38767, -- Scroll of Enchant Chest - Minor Absorption
@ -572,54 +600,4 @@ TSM.enchantingItemIDs = {
[64441] = 46026, -- Scroll of Enchant Weapon - Blade Ward
[64579] = 46098, -- Scroll of Enchant Weapon - Blood Draining
[71692] = 50816, -- Scroll of Enchant Gloves - Angler
[968676] = 967760, -- Scroll of Enchant Weapon - Unstoppable Assault I
[968677] = 967761, -- Scroll of Enchant Weapon - Unstoppable Assault II
[968678] = 967762, -- Scroll of Enchant Weapon - Unstoppable Assault III
[968679] = 967763, -- Scroll of Enchant Weapon - Lucid Assault I
[968680] = 967764, -- Scroll of Enchant Weapon - Lucid Assault II
[968681] = 967765, -- Scroll of Enchant Weapon - Lucid Assault III
[968682] = 967766, -- Scroll of Enchant Weapon - Spellbinder's Rage I
[968683] = 967767, -- Scroll of Enchant Weapon - Spellbinder's Rage II
[968684] = 967768, -- Scroll of Enchant Weapon - Spellbinder's Rage III
[968685] = 967769, -- Scroll of Enchant Weapon - Ninja's Focus I
[968686] = 967770, -- Scroll of Enchant Weapon - Ninja's Focus II
[968687] = 967771, -- Scroll of Enchant Weapon - Ninja's Focus III
[968688] = 967772, -- Scroll of Enchant Weapon - Grovewarden's Blessing I
[968689] = 967773, -- Scroll of Enchant Weapon - Grovewarden's Blessing II
[968690] = 967774, -- Scroll of Enchant Weapon - Grovewarden's Blessing III
[968691] = 967775, -- Scroll of Enchant Weapon - Viscious Assault I
[968692] = 967776, -- Scroll of Enchant Weapon - Viscious Assault II
[968693] = 967777, -- Scroll of Enchant Weapon - Viscious Assault III
[968694] = 967778, -- Scroll of Enchant Weapon - Arcane Dexterity I
[968695] = 967779, -- Scroll of Enchant Weapon - Arcane Dexterity II
[968696] = 967780, -- Scroll of Enchant Weapon - Arcane Dexterity III
[968697] = 967781, -- Scroll of Enchant Weapon - Arcane Artillery I
[968698] = 967782, -- Scroll of Enchant Weapon - Arcane Artillery II
[968699] = 967783, -- Scroll of Enchant Weapon - Arcane Artillery III
[968700] = 967784, -- Scroll of Enchant Weapon - Arcane Precision I
[968701] = 967785, -- Scroll of Enchant Weapon - Arcane Precision II
[968702] = 967786, -- Scroll of Enchant Weapon - Arcane Precision III
[968770] = 967787, -- Scroll of Enchant Weapon - Crusader II
[968771] = 967788, -- Scroll of Enchant Weapon - Crusader III
[1968677] = 1204125, -- Scroll of Enchant Weapon - Void Assault
[1968678] = 1204126, -- Scroll of Enchant Weapon - Overpowering Void Assault
[1968680] = 1204127, -- Scroll of Enchant Weapon - Dread Assault
[1968681] = 1204128, -- Scroll of Enchant Weapon - Overpowering Dread Assault
[1968683] = 1204129, -- Scroll of Enchant Weapon - Twisted Evoker
[1968684] = 1204130, -- Scroll of Enchant Weapon - Overpowering Twisted Evoker
[1968686] = 1204131, -- Scroll of Enchant Weapon - Twisted Assault
[1968687] = 1204132, -- Scroll of Enchant Weapon - Overpowering Twisted Assault
[1968689] = 1204133, -- Scroll of Enchant Weapon - Twisted Channeler
[1968690] = 1204134, -- Scroll of Enchant Weapon - Overpowering Twisted Channeler
[1968692] = 1204135, -- Scroll of Enchant Weapon - Dread Omen Strikes
[1968693] = 1204136, -- Scroll of Enchant Weapon - Overpowering Dread Omen Strikes
[1968695] = 1204137, -- Scroll of Enchant Weapon - Void Flows
[1968696] = 1204138, -- Scroll of Enchant Weapon - Overpowering Void Flows
[1968698] = 1204139, -- Scroll of Enchant Weapon - Void Blasting
[1968699] = 1204140, -- Scroll of Enchant Weapon - Overpowering Void Blasting
[1968701] = 1204141, -- Scroll of Enchant Weapon - Dread Precision
[1968702] = 1204142, -- Scroll of Enchant Weapon - Overpowering Dread Precision
[1968770] = 1204143, -- Scroll of Enchant Weapon - Twisted Crusader
[1968771] = 1204144, -- Scroll of Enchant Weapon - Overpowering Twisted Crusader
}

79
TradeSkillMaster_Crafting/Modules/SpellNames2IDs.lua

@ -764,12 +764,6 @@ TSM.SpellName2ID = {
["Treads of Destiny"] = 63190,
["Indestructible Plate Girdle"] = 63191,
["Spiked Deathdealers"] = 63192,
["Breastplate of the White Knight"] = 67091,
["Saronite Swordbreakers"] = 67092,
["Titanium Razorplate"] = 67093,
["Titanium Spikeguards"] = 67094,
["Sunforged Breastplate"] = 67095,
["Sunforged Bracers"] = 67096,
["Breastplate of the White Knight"] = 67130,
["Saronite Swordbreakers"] = 67131,
["Titanium Razorplate"] = 67132,
@ -883,7 +877,6 @@ TSM.SpellName2ID = {
["Lesser Mystic Wand"] = 14809,
["Greater Mystic Wand"] = 14810,
["Smoking Heart of the Mountain"] = 15596,
["Enchanted Thorium"] = 17180,
["Enchanted Leather"] = 17181,
["Enchant Bracer - Greater Intellect"] = 20008,
["Enchant Bracer - Superior Spirit"] = 20009,
@ -1086,55 +1079,7 @@ TSM.SpellName2ID = {
["Enchant Weapon - Blood Draining"] = 64579,
["Abyssal Shatter"] = 69412,
["Enchant Gloves - Angler"] = 71692,
["Enchant Weapon - Unstoppable Assault I"] = 968676,
["Enchant Weapon - Unstoppable Assault II"] = 968677,
["Enchant Weapon - Unstoppable Assault III"] = 968678,
["Enchant Weapon - Lucid Assault I"] = 968679,
["Enchant Weapon - Lucid Assault II"] = 968680,
["Enchant Weapon - Lucid Assault III"] = 968681,
["Enchant Weapon - Spellbinder's Rage I"] = 968682,
["Enchant Weapon - Spellbinder's Rage II"] = 968683,
["Enchant Weapon - Spellbinder's Rage III"] = 968684,
["Enchant Weapon - Ninja's Focus I"] = 968685,
["Enchant Weapon - Ninja's Focus II"] = 968686,
["Enchant Weapon - Ninja's Focus III"] = 968687,
["Enchant Weapon - Grovewarden's Blessing I"] = 968688,
["Enchant Weapon - Grovewarden's Blessing II"] = 968689,
["Enchant Weapon - Grovewarden's Blessing III"] = 968690,
["Enchant Weapon - Viscious Assault I"] = 968691,
["Enchant Weapon - Viscious Assault II"] = 968692,
["Enchant Weapon - Viscious Assault III"] = 968693,
["Enchant Weapon - Arcane Dexterity I"] = 968694,
["Enchant Weapon - Arcane Dexterity II"] = 968695,
["Enchant Weapon - Arcane Dexterity III"] = 968696,
["Enchant Weapon - Arcane Artillery I"] = 968697,
["Enchant Weapon - Arcane Artillery II"] = 968698,
["Enchant Weapon - Arcane Artillery III"] = 968699,
["Enchant Weapon - Arcane Precision I"] = 968700,
["Enchant Weapon - Arcane Precision II"] = 968701,
["Enchant Weapon - Arcane Precision III"] = 968702,
["Enchant Weapon - Crusader II"] = 968770,
["Enchant Weapon - Crusader III"] = 968771,
["Enchant Weapon - Void Assault"] = 1968677,
["Enchant Weapon - Overpowering Void Assault"] = 1968678,
["Enchant Weapon - Dread Assault"] = 1968680,
["Enchant Weapon - Overpowering Dread Assault"] = 1968681,
["Enchant Weapon - Twisted Evoker"] = 1968683,
["Enchant Weapon - Overpowering Twisted Evoker"] = 1968684,
["Enchant Weapon - Twisted Assault"] = 1968686,
["Enchant Weapon - Overpowering Twisted Assault"] = 1968687,
["Enchant Weapon - Twisted Channeler"] = 1968689,
["Enchant Weapon - Overpowering Twisted ChannelerI"] = 1968690,
["Enchant Weapon - Dread Omen Strikes"] = 1968692,
["Enchant Weapon - Overpowering Dread Omen Strikes"] = 1968693,
["Enchant Weapon - Void Flows"] = 1968695,
["Enchant Weapon - Overpowering Void Flows"] = 1968696,
["Enchant Weapon - Void Blasting"] = 1968698,
["Enchant Weapon - Overpowering Void Blasting"] = 1968699,
["Enchant Weapon - Dread Precision"] = 1968701,
["Enchant Weapon - Overpowering Dread Precision"] = 1968702,
["Enchant Weapon - Twisted Crusader"] = 1968770,
["Enchant Weapon - Overpowering Twisted Crusader"] = 1968771,
-- Engineering
@ -1398,7 +1343,6 @@ TSM.SpellName2ID = {
["Gnomish Army Knife"] = 56462,
["Explosive Decoy"] = 56463,
["Overcharged Capacitor"] = 56464,
["Mechanized Snow Goggles"] = 56465,
["Sonic Booster"] = 56466,
["Noise Machine"] = 56467,
["Box of Bombs"] = 56468,
@ -1425,8 +1369,6 @@ TSM.SpellName2ID = {
["Mekgineer's Chopper"] = 60867,
["Nesingwary 4000"] = 60874,
["Diamond-cut Refractor Scope"] = 61471,
["Mechanized Snow Goggles"] = 61481,
["Mechanized Snow Goggles"] = 61482,
["Mechanized Snow Goggles"] = 61483,
["Unbreakable Healing Amplifiers"] = 62271,
["High-powered Flashlight"] = 63750,
@ -2646,7 +2588,6 @@ TSM.SpellName2ID = {
["Rugged Leather"] = 22331,
["Shadowskin Gloves"] = 22711,
["Core Armor Kit"] = 22727,
["Gordok Ogre Suit"] = 22815,
["Girdle of Insight"] = 22921,
["Mongoose Boots"] = 22922,
["Swift Flight Bracers"] = 22923,
@ -2966,14 +2907,6 @@ TSM.SpellName2ID = {
["Belt of Arctic Life"] = 63200,
["Boots of Wintry Endurance"] = 63201,
["Borean Leather"] = 64661,
["Ensorcelled Nerubian Breastplate"] = 67080,
["Black Chitin Bracers"] = 67081,
["Crusader's Dragonscale Breastplate"] = 67082,
["Crusader's Dragonscale Bracers"] = 67083,
["Lunar Eclipse Robes"] = 67084,
["Moonshadow Armguards"] = 67085,
["Knightbane Carapace"] = 67086,
["Bracers of Swift Death"] = 67087,
["Ensorcelled Nerubian Breastplate"] = 67136,
["Black Chitin Bracers"] = 67137,
["Crusader's Dragonscale Breastplate"] = 67138,
@ -3348,7 +3281,6 @@ TSM.SpellName2ID = {
["Duskweave Robe"] = 55921,
["Duskweave Gloves"] = 55922,
["Duskweave Shoulders"] = 55923,
["Duskweave Boots"] = 55924,
["Black Duskweave Leggings"] = 55925,
["Black Duskweave Robe"] = 55941,
["Black Duskweave Wristwraps"] = 55943,
@ -3413,10 +3345,6 @@ TSM.SpellName2ID = {
["Emerald Bag"] = 63924,
["Frostguard Drape"] = 64729,
["Cloak of Crimson Snow"] = 64730,
["Royal Moonshroud Robe"] = 67064,
["Royal Moonshroud Bracers"] = 67065,
["Merlin's Robe"] = 67066,
["Bejeweled Wizard's Bracers"] = 67079,
["Royal Moonshroud Robe"] = 67144,
["Bejeweled Wizard's Bracers"] = 67145,
["Merlin's Robe"] = 67146,
@ -3621,11 +3549,6 @@ TSM.SpellName2ID = {
["Haunted Herring"] = 58525,
["Gigantic Feast"] = 58527,
["Small Feast"] = 58528,
["Pumpkin Pie"] = 62044,
["Slow-Roasted Turkey"] = 62045,
["Cranberry Chutney"] = 62049,
["Spice Bread Stuffing"] = 62050,
["Candied Sweet Potato"] = 62051,
["Worg Tartare"] = 62350,
["Clamlette Magnifique"] = 64054,
["Black Jelly"] = 64358,

49
TradeSkillMaster_Crafting/Modules/Util.lua

@ -105,7 +105,31 @@ function Util:ScanCurrentProfession()
local hasCD = select(2, GetTradeSkillCooldown(index)) and true or nil
local mats = {}
if currentTradeSkill == TSM.enchantingName and strfind(itemLink, "enchant:") then
-- mats[VELLUM_ID] = 1
-- local name = TSMAPI:GetSafeItemInfo(VELLUM_ID) or (GetLocale() == "enUS" and "Enchanting Vellum") or nil
-- TSM.db.factionrealm.mats[VELLUM_ID] = TSM.db.factionrealm.mats[VELLUM_ID] or {}
-- TSM.db.factionrealm.mats[VELLUM_ID].name = TSM.db.factionrealm.mats[VELLUM_ID].name or name
local VellumString = "item:"..TSM.VellumInfo[spellID]..":0:0:0:0:0:0"
-- -- Get Cheapest vellum, lower vellum types can be replaced by III
-- local velName
-- if TSM.VellumInfo[spellID] then
-- velName = GetItemInfo(TSM.VellumInfo[spellID])
-- end
-- if (velName ~= nil) and (not strfind(velName, "III")) then
-- local VellumReplacePrice = TSM.Cost:GetMatCost(VellumString)
-- if strfind(GetSpellInfo(spellID), "Weapon") or strfind(GetSpellInfo(spellID), "Staff")then
-- if VellumReplacePrice > TSM.Cost:GetMatCost("item:43146:0:0:0:0:0:0") then VellumString = "item:43146:0:0:0:0:0:0" end
-- else
-- if VellumReplacePrice > TSM.Cost:GetMatCost("item:43145:0:0:0:0:0:0") then VellumString = "item:4314:0:0:0:0:0:0" end
-- end
-- end
mats[VellumString] = 1
local name = TSMAPI:GetSafeItemInfo(VellumString) or nil
TSM.db.factionrealm.mats[VellumString] = TSM.db.factionrealm.mats[VellumString] or {}
@ -230,8 +254,33 @@ function Util.ScanSyncedProfessionThread(self)
local hasCD = select(2, GetTradeSkillCooldown(index)) and true or nil
local mats = {}
if currentTradeSkill == TSM.enchantingName and strfind(itemLink, "enchant:") then
-- mats[VELLUM_ID] = 1
-- local name = TSMAPI:GetSafeItemInfo(VELLUM_ID) or (GetLocale() == "enUS" and "Enchanting Vellum") or nil
-- TSM.db.factionrealm.mats[VELLUM_ID] = TSM.db.factionrealm.mats[VELLUM_ID] or {}
-- TSM.db.factionrealm.mats[VELLUM_ID].name = TSM.db.factionrealm.mats[VELLUM_ID].name or name
local VellumString = "item:"..TSM.VellumInfo[spellID]..":0:0:0:0:0:0"
-- -- Get Cheapest vellum, lower vellum types can be replaced by III
-- local velName
-- if TSM.VellumInfo[spellID] then
-- velName = GetItemInfo(TSM.VellumInfo[spellID])
-- end
-- if (velName ~= nil) and (not strfind(velName, "III")) then
-- local VellumReplacePrice = TSM.Cost:GetMatCost(VellumString)
-- if strfind(GetSpellInfo(spellID), "Weapon") or strfind(GetSpellInfo(spellID), "Staff")then
-- if VellumReplacePrice > TSM.Cost:GetMatCost("item:43146:0:0:0:0:0:0") then VellumString = "item:43146:0:0:0:0:0:0" end
-- else
-- if VellumReplacePrice > TSM.Cost:GetMatCost("item:43145:0:0:0:0:0:0") then VellumString = "item:4314:0:0:0:0:0:0" end
-- end
-- end
mats[VellumString] = 1
local name = TSMAPI:GetSafeItemInfo(VellumString) or nil
TSM.db.factionrealm.mats[VellumString] = TSM.db.factionrealm.mats[VellumString] or {}

567
TradeSkillMaster_Crafting/Modules/VellumInfo.lua

@ -2,311 +2,262 @@
local TSM = select(2, ...)
TSM.VellumInfo = {
[7418] = 52510,
[7420] = 52510,
[7426] = 52510,
[7428] = 52510,
[7443] = 52510,
[7454] = 52510,
[7457] = 52510,
[7745] = 52511,
[7748] = 52510,
[7766] = 52510,
[7771] = 52510,
[7776] = 52510,
[7779] = 52510,
[7782] = 52510,
[7786] = 52511,
[7788] = 52511,
[7793] = 52511,
[7857] = 52510,
[7859] = 52510,
[7861] = 52510,
[7863] = 52510,
[7867] = 52510,
[13378] = 52510,
[13380] = 52511,
[13419] = 52510,
[13421] = 52510,
[13464] = 52510,
[13485] = 52510,
[13501] = 52510,
[13503] = 52511,
[13522] = 52510,
[13529] = 52511,
[13536] = 52510,
[13538] = 52510,
[13607] = 52510,
[13612] = 52510,
[13617] = 52510,
[13620] = 52510,
[13622] = 52510,
[13626] = 52510,
[13631] = 52510,
[13635] = 52510,
[13637] = 52510,
[13640] = 52510,
[13642] = 52510,
[13644] = 52510,
[13646] = 52510,
[13648] = 52510,
[13653] = 52511,
[13655] = 52511,
[13657] = 52510,
[13659] = 52510,
[13661] = 52510,
[13663] = 52510,
[13687] = 52510,
[13689] = 52510,
[13693] = 52511,
[13695] = 52511,
[13698] = 52510,
[13700] = 52510,
[13746] = 52510,
[13794] = 52510,
[13815] = 52510,
[13817] = 52510,
[13822] = 52510,
[13836] = 52510,
[13841] = 52510,
[13846] = 52510,
[13858] = 52510,
[13868] = 52510,
[13882] = 52510,
[13887] = 52510,
[13890] = 52510,
[13898] = 52511,
[13905] = 52510,
[13915] = 52511,
[13917] = 52510,
[13931] = 52510,
[13933] = 52510,
[13935] = 52510,
[13937] = 52511,
[13939] = 52510,
[13941] = 52510,
[13943] = 52511,
[13945] = 52510,
[13947] = 52510,
[13948] = 52510,
[20008] = 52510,
[20009] = 52510,
[20010] = 52510,
[20011] = 52510,
[20012] = 52510,
[20013] = 52510,
[20014] = 52510,
[20015] = 52510,
[20016] = 52510,
[20017] = 52510,
[20020] = 52510,
[20023] = 52510,
[20024] = 52510,
[20025] = 52510,
[20026] = 52510,
[20028] = 52510,
[20029] = 52511,
[20030] = 52511,
[20031] = 52511,
[20032] = 52511,
[20033] = 52511,
[20034] = 52511,
[20035] = 52511,
[20036] = 52511,
[21931] = 52511,
[22749] = 52511,
[22750] = 52511,
[23799] = 52511,
[23800] = 52511,
[23801] = 52510,
[23802] = 52510,
[23803] = 52511,
[23804] = 52511,
[25072] = 52510,
[25073] = 52510,
[25074] = 52510,
[25078] = 52510,
[25079] = 52510,
[25080] = 52510,
[25081] = 52510,
[25082] = 52510,
[25083] = 52510,
[25084] = 52510,
[25086] = 52510,
[27837] = 52511,
[27899] = 52510,
[27905] = 52510,
[27906] = 52510,
[27911] = 52510,
[27913] = 52510,
[27914] = 52510,
[27917] = 52510,
[27944] = 52510,
[27945] = 52510,
[27946] = 52510,
[27947] = 52510,
[27948] = 52510,
[27950] = 52510,
[27951] = 52510,
[27954] = 52510,
[27957] = 52510,
[27958] = 52510,
[27960] = 52510,
[27961] = 52510,
[27962] = 52510,
[27967] = 52511,
[27968] = 52511,
[27971] = 52511,
[27972] = 52511,
[27975] = 52511,
[27977] = 52511,
[27981] = 52511,
[27982] = 52511,
[27984] = 52511,
[28003] = 52511,
[28004] = 52511,
[33990] = 52510,
[33991] = 52510,
[33992] = 52510,
[33993] = 52510,
[33994] = 52510,
[33995] = 52510,
[33996] = 52510,
[33997] = 52510,
[33999] = 52510,
[34001] = 52510,
[34002] = 52510,
[34003] = 52510,
[34004] = 52510,
[34005] = 52510,
[34006] = 52510,
[34007] = 52510,
[34008] = 52510,
[34009] = 52510,
[34010] = 52511,
[42620] = 52511,
[42974] = 52511,
[44383] = 52510,
[44483] = 52510,
[44484] = 52510,
[44488] = 52510,
[44489] = 52510,
[44492] = 52510,
[44494] = 52510,
[44500] = 52510,
[44506] = 52510,
[44508] = 52510,
[44509] = 52510,
[44510] = 52511,
[44513] = 52510,
[44524] = 52511,
[44528] = 52510,
[44529] = 52510,
[44555] = 52510,
[44556] = 52510,
[44575] = 52510,
[44576] = 52511,
[44582] = 52510,
[44584] = 52510,
[44588] = 52510,
[44589] = 52510,
[44590] = 52510,
[44591] = 52510,
[44592] = 52510,
[44593] = 52510,
[44595] = 52511,
[44596] = 52510,
[44598] = 52510,
[44612] = 52510,
[44616] = 52510,
[44621] = 52511,
[44623] = 52510,
[44625] = 52510,
[44629] = 52511,
[44630] = 52511,
[44631] = 52510,
[44633] = 52511,
[44635] = 52510,
[46578] = 52511,
[46594] = 52510,
[47051] = 52510,
[47672] = 52510,
[47766] = 52510,
[47898] = 52510,
[47899] = 52510,
[47900] = 52510,
[47901] = 52510,
[59619] = 52511,
[59621] = 52511,
[59625] = 52511,
[60606] = 52510,
[60609] = 52510,
[60616] = 52510,
[60621] = 52511,
[60623] = 52510,
[60653] = 52510,
[60663] = 52510,
[60668] = 52510,
[60691] = 52511,
[60692] = 52510,
[60707] = 52511,
[60714] = 52511,
[60763] = 52510,
[60767] = 52510,
[62256] = 52510,
[62257] = 52511,
[62948] = 52511,
[62959] = 52511,
[63746] = 52510,
[64441] = 52511,
[64579] = 52511,
[71692] = 52510,
[968676] = 52511,
[968677] = 52511,
[968678] = 52511,
[968679] = 52511,
[968680] = 52511,
[968681] = 52511,
[968682] = 52511,
[968683] = 52511,
[968684] = 52511,
[968685] = 52511,
[968686] = 52511,
[968687] = 52511,
[968688] = 52511,
[968689] = 52511,
[968690] = 52511,
[968691] = 52511,
[968692] = 52511,
[968693] = 52511,
[968694] = 52511,
[968695] = 52511,
[968696] = 52511,
[968697] = 52511,
[968698] = 52511,
[968699] = 52511,
[968700] = 52511,
[968701] = 52511,
[968702] = 52511,
[968770] = 52511,
[968771] = 52511,
[1968677] = 52511,
[1968678] = 52511,
[1968680] = 52511,
[1968681] = 52511,
[1968683] = 52511,
[1968684] = 52511,
[1968686] = 52511,
[1968687] = 52511,
[1968689] = 52511,
[1968690] = 52511,
[1968692] = 52511,
[1968693] = 52511,
[1968695] = 52511,
[1968696] = 52511,
[1968698] = 52511,
[1968699] = 52511,
[1968701] = 52511,
[1968702] = 52511,
[1968770] = 52511,
[1968771] = 52511}
[7418] = 38682,
[7420] = 38682,
[7426] = 38682,
[7428] = 38682,
[7443] = 38682,
[7454] = 38682,
[7457] = 38682,
[7745] = 39349,
[7748] = 38682,
[7766] = 38682,
[7771] = 38682,
[7776] = 38682,
[7779] = 38682,
[7782] = 38682,
[7786] = 39349,
[7788] = 39349,
[7793] = 39349,
[7857] = 38682,
[7859] = 38682,
[7861] = 38682,
[7863] = 38682,
[7867] = 38682,
[13378] = 38682,
[13380] = 39349,
[13419] = 38682,
[13421] = 38682,
[13464] = 38682,
[13485] = 38682,
[13501] = 38682,
[13503] = 39349,
[13522] = 38682,
[13529] = 39349,
[13536] = 38682,
[13538] = 38682,
[13607] = 38682,
[13612] = 38682,
[13617] = 38682,
[13620] = 38682,
[13622] = 38682,
[13626] = 38682,
[13631] = 38682,
[13635] = 38682,
[13637] = 38682,
[13640] = 38682,
[13642] = 38682,
[13644] = 38682,
[13646] = 38682,
[13648] = 38682,
[13653] = 39349,
[13655] = 39349,
[13657] = 38682,
[13659] = 38682,
[13661] = 38682,
[13663] = 38682,
[13687] = 38682,
[13689] = 38682,
[13693] = 39349,
[13695] = 39349,
[13698] = 38682,
[13700] = 38682,
[13746] = 38682,
[13794] = 38682,
[13815] = 38682,
[13817] = 38682,
[13822] = 38682,
[13836] = 38682,
[13841] = 38682,
[13846] = 38682,
[13858] = 38682,
[13868] = 38682,
[13882] = 38682,
[13887] = 38682,
[13890] = 38682,
[13898] = 39349,
[13905] = 38682,
[13915] = 39349,
[13917] = 38682,
[13931] = 38682,
[13933] = 38682,
[13935] = 38682,
[13937] = 39349,
[13939] = 38682,
[13941] = 38682,
[13943] = 39349,
[13945] = 38682,
[13947] = 38682,
[13948] = 38682,
[20008] = 38682,
[20009] = 38682,
[20010] = 38682,
[20011] = 38682,
[20012] = 38682,
[20013] = 38682,
[20014] = 38682,
[20015] = 38682,
[20016] = 38682,
[20017] = 38682,
[20020] = 38682,
[20023] = 38682,
[20024] = 38682,
[20025] = 38682,
[20026] = 38682,
[20028] = 38682,
[20029] = 39349,
[20030] = 39349,
[20031] = 39349,
[20032] = 39349,
[20033] = 39349,
[20034] = 39349,
[20035] = 39349,
[20036] = 39349,
[21931] = 39349,
[22749] = 39349,
[22750] = 39349,
[23799] = 39349,
[23800] = 39349,
[23801] = 38682,
[23802] = 38682,
[23803] = 39349,
[23804] = 39349,
[25072] = 38682,
[25073] = 38682,
[25074] = 38682,
[25078] = 38682,
[25079] = 38682,
[25080] = 38682,
[25081] = 38682,
[25082] = 38682,
[25083] = 38682,
[25084] = 38682,
[25086] = 37602,
[27837] = 39349,
[27899] = 37602,
[27905] = 37602,
[27906] = 37602,
[27911] = 37602,
[27913] = 37602,
[27914] = 37602,
[27917] = 37602,
[27944] = 37602,
[27945] = 37602,
[27946] = 37602,
[27947] = 37602,
[27948] = 37602,
[27950] = 37602,
[27951] = 37602,
[27954] = 37602,
[27957] = 37602,
[27958] = 43145,
[27960] = 37602,
[27961] = 37602,
[27962] = 37602,
[27967] = 39350,
[27968] = 39350,
[27971] = 39350,
[27972] = 39350,
[27975] = 39350,
[27977] = 39350,
[27981] = 39350,
[27982] = 39350,
[27984] = 39350,
[28003] = 39350,
[28004] = 39350,
[33990] = 37602,
[33991] = 37602,
[33992] = 37602,
[33993] = 37602,
[33994] = 37602,
[33995] = 37602,
[33996] = 37602,
[33997] = 37602,
[33999] = 37602,
[34001] = 37602,
[34002] = 37602,
[34003] = 37602,
[34004] = 37602,
[34005] = 37602,
[34006] = 37602,
[34007] = 37602,
[34008] = 37602,
[34009] = 37602,
[34010] = 39350,
[42620] = 39350,
[42974] = 43146,
[44383] = 37602,
[44483] = 43145,
[44484] = 43145,
[44488] = 43145,
[44489] = 43145,
[44492] = 43145,
[44494] = 43145,
[44500] = 43145,
[44506] = 43145,
[44508] = 43145,
[44509] = 43145,
[44510] = 43146,
[44513] = 43145,
[44524] = 43146,
[44528] = 43145,
[44529] = 43145,
[44555] = 43145,
[44556] = 43145,
[44575] = 43145,
[44576] = 43146,
[44582] = 43145,
[44584] = 43145,
[44588] = 43145,
[44589] = 43145,
[44590] = 43145,
[44591] = 43145,
[44592] = 43145,
[44593] = 43145,
[44595] = 43146,
[44596] = 43145,
[44598] = 43145,
[44612] = 43145,
[44616] = 43145,
[44621] = 43146,
[44623] = 43145,
[44625] = 43145,
[44629] = 43146,
[44630] = 43146,
[44631] = 43145,
[44633] = 43146,
[44635] = 43145,
[46578] = 43146,
[46594] = 37602,
[47051] = 37602,
[47672] = 43145,
[47766] = 43145,
[47898] = 43145,
[47899] = 43145,
[47900] = 43145,
[47901] = 43145,
[59619] = 43146,
[59621] = 43146,
[59625] = 43146,
[60606] = 43145,
[60609] = 43145,
[60616] = 43145,
[60621] = 43146,
[60623] = 43145,
[60653] = 43145,
[60663] = 43145,
[60668] = 43145,
[60691] = 43146,
[60692] = 43145,
[60707] = 43146,
[60714] = 43146,
[60763] = 43145,
[60767] = 43145,
[62256] = 43145,
[62257] = 43146,
[62948] = 43146,
[62959] = 43146,
[63746] = 38682,
[64441] = 39349,
[64579] = 39349,
[71692] = 38682}

107
TradeSkillMaster_Crafting/TradeSkillMaster_Crafting.lua

@ -58,26 +58,76 @@ function TSM:OnEnable()
-- fix vellum issue
for spellid, data in pairs(TSM.db.factionrealm.crafts) do
for itemString in pairs(data.mats) do
if itemString == "item:52510" then
TSM.db.factionrealm.crafts[spellid].mats["item:52510:0:0:0:0:0:0"] = 1
-- if itemString == "item:38682" then
-- TSM.db.factionrealm.crafts[spellid].mats["item:38682:0:0:0:0:0:0"] = 1
if itemString == "item:43146" then
TSM.db.factionrealm.crafts[spellid].mats["item:43146:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
elseif itemString == "item:52511" then
TSM.db.factionrealm.crafts[spellid].mats["item:52511:0:0:0:0:0:0"] = 1
elseif itemString == "item:43145" then
TSM.db.factionrealm.crafts[spellid].mats["item:43145:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
elseif itemString == "item:39350" then
TSM.db.factionrealm.crafts[spellid].mats["item:39350:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
elseif itemString == "item:37602" then
TSM.db.factionrealm.crafts[spellid].mats["item:37602:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
elseif itemString == "item:39349" then
TSM.db.factionrealm.crafts[spellid].mats["item:39349:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
elseif itemString == "item:38682" then
TSM.db.factionrealm.crafts[spellid].mats["item:38682:0:0:0:0:0:0"] = 1
TSM.db.factionrealm.crafts[spellid].mats[itemString] = nil
end
end
end
if TSM.db.factionrealm.mats["item:52510"] then
local name = TSMAPI:GetSafeItemInfo("item:52510:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:52510:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:52510:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:52510"] = nil
end
if TSM.db.factionrealm.mats["item:52511"] then
local name = TSMAPI:GetSafeItemInfo("item:52511:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:52511:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:52511:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:52511"] = nil
-- if TSM.db.factionrealm.mats["item:38682"] then
-- local name = TSMAPI:GetSafeItemInfo("item:38682:0:0:0:0:0:0") or (GetLocale() == "enUS" and "Enchanting Vellum") or nil
-- TSM.db.factionrealm.mats["item:38682:0:0:0:0:0:0"] = {}
-- TSM.db.factionrealm.mats["item:38682:0:0:0:0:0:0"].name = name
-- TSM.db.factionrealm.mats["item:38682"] = nil
-- end
if TSM.db.factionrealm.mats["item:43146"] then
local name = TSMAPI:GetSafeItemInfo("item:43146:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:43146:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:43146:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:43146"] = nil
end
if TSM.db.factionrealm.mats["item:43145"] then
local name = TSMAPI:GetSafeItemInfo("item:43145:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:43145:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:43145:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:43145"] = nil
end
if TSM.db.factionrealm.mats["item:39350"] then
local name = TSMAPI:GetSafeItemInfo("item:39350:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:39350:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:39350:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:39350"] = nil
end
if TSM.db.factionrealm.mats["item:39350"] then
local name = TSMAPI:GetSafeItemInfo("item:39350:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:39350:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:39350:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:39350"] = nil
end
if TSM.db.factionrealm.mats["item:37602"] then
local name = TSMAPI:GetSafeItemInfo("item:37602:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:37602:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:37602:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:37602"] = nil
end
if TSM.db.factionrealm.mats["item:39349"] then
local name = TSMAPI:GetSafeItemInfo("item:39349:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:39349:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:39349:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:39349"] = nil
end
if TSM.db.factionrealm.mats["item:38682"] then
local name = TSMAPI:GetSafeItemInfo("item:38682:0:0:0:0:0:0") or nil
TSM.db.factionrealm.mats["item:38682:0:0:0:0:0:0"] = {}
TSM.db.factionrealm.mats["item:38682:0:0:0:0:0:0"].name = name
TSM.db.factionrealm.mats["item:38682"] = nil
end
local func, err = TSMAPI:ParseCustomPrice(TSM.db.global.defaultCraftPriceMethod, "crafting")
@ -188,6 +238,33 @@ function TSM:GetTooltip(itemString)
local name, _, quality = TSMAPI:GetSafeItemInfo(matItemString)
if name then
local mat = TSM.db.factionrealm.mats[matItemString]
-- Get Cheapest vellum, lower vellum types can be replaced by III
local velName
if strfind(name, "Vellum") then
velName = name
end
if (velName ~= nil) and (not strfind(velName, "III")) then
local VellumReplacePrice = TSM.Cost:GetMatCost(matItemString)
if strfind(velName, "Weapon Vellum") then
if VellumReplacePrice > TSM.Cost:GetMatCost("item:43146:0:0:0:0:0:0") then
matItemString = "item:43146:0:0:0:0:0:0"
name = TSMAPI:GetSafeItemInfo(matItemString)
end
else
if VellumReplacePrice > TSM.Cost:GetMatCost("item:43145:0:0:0:0:0:0") then
matItemString = "item:43145:0:0:0:0:0:0"
name = TSMAPI:GetSafeItemInfo(matItemString)
end
end
end
if mat then
local cost = TSM:GetCustomPrice(mat.customValue or TSM.db.global.defaultMatCostMethod, matItemString)
if cost then

1
TradeSkillMaster_Shopping/Locale/enUS.lua

@ -115,6 +115,7 @@ L["Reset Filters"] = true
L["Right-Click to favorite this recent search."] = true
L["Right-Click to remove from favorite searches."] = true
L["Saved Searches"] = true
L["Scanning %d / %d (Page 1 / ?)"] = true
L["Scanning %d / %d (Page %d / %d)"] = true
L["Search Filter:"] = true
L["Select the groups which you would like to include in the search."] = true

1
TradeSkillMaster_Shopping/Locale/koKR.lua

@ -110,6 +110,7 @@ L["Reset Filters"] = "필터 리셋" -- Needs review
L["Right-Click to favorite this recent search."] = "Right-Click으로 이 검색을 즐겨찾기에 등록합니다." -- Needs review
L["Right-Click to remove from favorite searches."] = "Right-Click으로 츨겨찾기 검색에서 제거합니다." -- Needs review
L["Saved Searches"] = "저장된 검색"
L["Scanning %d / %d (Page 1 / ?)"] = "검색 중 %d / %d (페이지 1 / ?)" -- Needs review
L["Scanning %d / %d (Page %d / %d)"] = "검색 중 %d / %d (페이지 %d / %d)" -- Needs review
L["Search Filter:"] = "검색 필터:" -- Needs review
L["Select the groups which you would like to include in the search."] = "검색에 포함할 그룹을 선택하세요." -- Needs review

3
TradeSkillMaster_Shopping/Locale/ruRU.lua

@ -111,7 +111,8 @@ L["Recent Searches"] = "Недавние поиски" -- Needs review
-- L["Right-Click to favorite this recent search."] = ""
-- L["Right-Click to remove from favorite searches."] = ""
L["Saved Searches"] = "Сохраненные поиски" -- Needs review
-- L["Scanning %d / %d (Page %d / %d)"] = ""
L["Scanning %d / %d (Page 1 / ?)"] = "Скан %d / %d (стр 1 / ?)"
L["Scanning %d / %d (Page %d / %d)"] = "Скан %d / %d (стр %d / %d)"
-- L["Search Filter:"] = ""
-- L["Select the groups which you would like to include in the search."] = ""
L["'%s' has a Shopping operation of '%s' which no longer exists. Shopping will ignore this group until this is fixed."] = "\"% s \" имеет Торговые операции '%s', которая больше не существует. Торговые будете игнорировать эту группу до тех пор, пока это будет Исправлено." -- Needs review

1
TradeSkillMaster_Shopping/Locale/zhCN.lua

@ -112,6 +112,7 @@ L["Reset Filters"] = "重置筛选器"
L["Right-Click to favorite this recent search."] = "右键点击 保存到最喜欢的搜索"
L["Right-Click to remove from favorite searches."] = "右键点击 从最喜欢的搜索里移除"
L["Saved Searches"] = "保存搜索"
L["Scanning %d / %d (Page 1 / ?)"] = "扫描 %d / %d(页面 1 / ?)"
L["Scanning %d / %d (Page %d / %d)"] = "扫描 %d / %d(页面 %d / %d)"
L["Search Filter:"] = "搜索筛选器:"
L["Select the groups which you would like to include in the search."] = "选择你想搜索的分组"

71
TradeSkillMaster_Shopping/modules/Util.lua

@ -202,17 +202,29 @@ function private:PrepareForScan(callback, isLastPageScan)
TSM.moduleAPICallback = nil
end
local scanStatus, pageStatus
function private.ScanCallback(event, ...)
if event == "QUERY_COMPLETE" then
private.filterList = ...
private.numFilters = #private.filterList
private:ScanNextFilter()
elseif event == "QUERY_UPDATE" then
local arg1, arg2 = ...
private:UpdateStatus("query", arg1, arg2)
local current, total, skipped = ...
private:UpdateStatus("query", current, total)
elseif event == "SCAN_PAGE_UPDATE" then
-- Simply forward the "currently received" and "total" page counts
-- for the current item we're scanning.
-- NOTE: Private servers sometimes give totally insane "total page counts"
-- on the first response, such as "page 1/142", but those server errors
-- always correct themselves to "page 2/REAL" when the scan has reached
-- page 2. There's nothing we can do to avoid that rare page-count issue
-- (which happens on many popular private servers, such as Warmane).
private:UpdateStatus("page", ...)
elseif event == "SCAN_INTERRUPTED" or event == "INTERRUPTED" then
-- We've been interrupted by the Auction House closing.
-- NOTE: "SCAN_INTERRUPTED" is from LibAuctionScan-1.0, which isn't used
-- by TSM anymore, and "INTERRUPTED" is from "TSM/Auction/AuctionScanning.lua",
-- which is what this scanner uses nowadays.
private:ScanComplete(true)
elseif event == "SCAN_TIMEOUT" then
tremove(private.filterList, 1)
private:ScanNextFilter()
@ -288,14 +300,25 @@ function private.ScanCallback(event, ...)
end
function private:ScanNextFilter()
-- We must reset the page counter, otherwise the next scan will keep the
-- page count of the previous item until we receive "SCAN_PAGE_UPDATE".
-- NOTE: The "nil" signals that we don't know the item's page count yet.
-- NOTE: The recipient may want to ignore "page" events that have nil values
-- and not update their status bars based on those, since we send these empty
-- page events before we start each new scan!
private:UpdateStatus("page", nil, nil)
-- Now update the scan counter.
-- NOTE: Our scan progress counter below starts counting from 0 as the first item.
if #private.filterList == 0 then
private:UpdateStatus("scan", private.numFilters, private.numFilters)
return private:ScanComplete()
end
pageStatus = {0, 1}
private:UpdateStatus("scan", private.numFilters-#private.filterList+1, private.numFilters)
private:UpdateStatus("scan", private.numFilters-#private.filterList, private.numFilters)
TSMAPI.AuctionScan:RunQuery(private.filterList[1], private.ScanCallback, true, private.callback("filter", private.filterList[1]), true)
end
local scanStatus, pageStatus
function private:UpdateStatus(statusType, ...)
if statusType == "query" then
private.searchFrame.statusBar:SetStatusText(format(L["Preparing Filter %d / %d"], ...))
@ -305,13 +328,44 @@ function private:UpdateStatus(statusType, ...)
scanStatus = {...}
elseif statusType == "page" then
pageStatus = {...}
-- Ignore the empty "reset page count" events that happen before every new
-- scan. We've reset the page counts, which is the only thing that matters.
if pageStatus[1] == nil and pageStatus[2] == nil then
return
end
private.searchFrame.statusBar:SetStatusText(format(L["Scanning %d / %d (Page %d / %d)"], scanStatus[1], scanStatus[2], pageStatus[1]+1, pageStatus[2]))
private.searchFrame.statusBar:UpdateStatus(100*(scanStatus[1]-1)/scanStatus[2], 100*pageStatus[1]/pageStatus[2])
end
-- NOTE: We don't do +1 for the item-scan or page-status counters below,
-- since we want our progress bar to show FULLY RECEIVED/FINISHED items
-- and pages, and not fill up until we've fully received each item/page.
local progress_bar_items = min(100*(scanStatus[1]/scanStatus[2]), 100) -- Calculate "total items" progress bar from 0-100%.
local progress_bar_pages = 0
-- NOTE: In the status text label, we count the items starting at 1,
-- to say "Scanning 1 / 2" (instead of "Scanning 0 / 2"). For pages,
-- we show which page we're waiting for (so if we have received 2/4 pages,
-- the label will say 3/4, meaning "we're waiting for page 3...").
if pageStatus[1] ~= nil and pageStatus[2] ~= nil then
-- We have received the page counter ("current page / total pages") for the current item.
-- NOTE: We add "+1" to the page counter, to indicate that we've received that page and are working on the next page.
private.searchFrame.statusBar:SetStatusText(format(L["Scanning %d / %d (Page %d / %d)"], scanStatus[1] + 1, scanStatus[2], min(pageStatus[1] + 1, pageStatus[2]), pageStatus[2]))
progress_bar_pages = min(100*(pageStatus[1]/pageStatus[2]), 100) -- Calculate "total pages of current item" progress bar from 0-100%.
else
-- We have started a new item scan but haven't received the page count yet.
-- NOTE: If we don't initialize the page-progress to 0%, we'd get a "jumpy"
-- progress bar that goes from 100% at page 1/? to "real%" after page 1.
private.searchFrame.statusBar:SetStatusText(format(L["Scanning %d / %d (Page 1 / ?)"], scanStatus[1] + 1, scanStatus[2]))
progress_bar_pages = 0 -- Begin at 0% for the gray page-counter bar of the current item.
end
private.searchFrame.statusBar:UpdateStatus(progress_bar_items, progress_bar_pages)
end
end
function private:ScanComplete()
function private:ScanComplete(interrupted)
if interrupted then
-- If our scan has been interrupted by the Auction House closing,
-- simply act as if the user clicked "Stop".
Util:StopScan()
else
if not private.callback then return end
private.searchFrame.statusBar:SetStatusText(L["Done Scanning"])
private.searchFrame.statusBar:UpdateStatus(100, 100)
@ -330,6 +384,7 @@ function private:ScanComplete()
end
private.callback("done", private.auctions)
TSMAPI:FireEvent("SHOPPING:SEARCH:SCANDONE", #private.searchFrame.rt.auctionData)
end
end
-- processes scan data for a specific item

Loading…
Cancel
Save