From 02cfc420dd3d08387e7cfb59cb9098c0cbbe0898 Mon Sep 17 00:00:00 2001 From: geojak Date: Sun, 20 Feb 2022 23:51:19 +0100 Subject: [PATCH 1/6] aux auto bid and price heuristics this commit does two things 1. it implements auto bid just like auto buy 2. it has some minimum pricing heuristics when posting items. this behavoiur actiaves when the post bid price is set to 0, the buyout price behaves like a minimum --- aux-addon/README.md | 156 +++ aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP | Bin 0 -> 3908 bytes aux-addon/aux-addon.lua | 243 ++++ aux-addon/aux-addon.toc | 54 + aux-addon/color.lua | 51 + aux-addon/control.lua | 121 ++ aux-addon/core/cache.lua | 211 ++++ aux-addon/core/crafting.lua | 99 ++ aux-addon/core/disenchant.lua | 163 +++ aux-addon/core/history.lua | 109 ++ aux-addon/core/post.lua | 165 +++ aux-addon/core/scan.lua | 236 ++++ aux-addon/core/shortcut.lua | 31 + aux-addon/core/slash.lua | 74 ++ aux-addon/core/stack.lua | 130 +++ aux-addon/core/tooltip.lua | 216 ++++ aux-addon/frame.lua | 51 + aux-addon/gui/auction_listing.lua | 1046 ++++++++++++++++++ aux-addon/gui/core.lua | 569 ++++++++++ aux-addon/gui/item_listing.lua | 129 +++ aux-addon/gui/listing.lua | 300 +++++ aux-addon/libs/T.lua | 149 +++ aux-addon/libs/package.lua | 37 + aux-addon/tabs/auctions/core.lua | 101 ++ aux-addon/tabs/auctions/frame.lua | 62 ++ aux-addon/tabs/bids/core.lua | 125 +++ aux-addon/tabs/bids/frame.lua | 71 ++ aux-addon/tabs/post/core.lua | 560 ++++++++++ aux-addon/tabs/post/frame.lua | 326 ++++++ aux-addon/tabs/search/core.lua | 85 ++ aux-addon/tabs/search/filter.lua | 395 +++++++ aux-addon/tabs/search/frame.lua | 559 ++++++++++ aux-addon/tabs/search/results.lua | 443 ++++++++ aux-addon/tabs/search/saved.lua | 181 +++ aux-addon/util.lua | 188 ++++ aux-addon/util/completion.lua | 45 + aux-addon/util/filter.lua | 549 +++++++++ aux-addon/util/info.lua | 443 ++++++++ aux-addon/util/money.lua | 131 +++ aux-addon/util/persistence.lua | 75 ++ aux-addon/util/scan.lua | 82 ++ aux-addon/util/sort.lua | 30 + 42 files changed, 8791 insertions(+) create mode 100644 aux-addon/README.md create mode 100644 aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP create mode 100644 aux-addon/aux-addon.lua create mode 100644 aux-addon/aux-addon.toc create mode 100644 aux-addon/color.lua create mode 100644 aux-addon/control.lua create mode 100644 aux-addon/core/cache.lua create mode 100644 aux-addon/core/crafting.lua create mode 100644 aux-addon/core/disenchant.lua create mode 100644 aux-addon/core/history.lua create mode 100644 aux-addon/core/post.lua create mode 100644 aux-addon/core/scan.lua create mode 100644 aux-addon/core/shortcut.lua create mode 100644 aux-addon/core/slash.lua create mode 100644 aux-addon/core/stack.lua create mode 100644 aux-addon/core/tooltip.lua create mode 100644 aux-addon/frame.lua create mode 100644 aux-addon/gui/auction_listing.lua create mode 100644 aux-addon/gui/core.lua create mode 100644 aux-addon/gui/item_listing.lua create mode 100644 aux-addon/gui/listing.lua create mode 100644 aux-addon/libs/T.lua create mode 100644 aux-addon/libs/package.lua create mode 100644 aux-addon/tabs/auctions/core.lua create mode 100644 aux-addon/tabs/auctions/frame.lua create mode 100644 aux-addon/tabs/bids/core.lua create mode 100644 aux-addon/tabs/bids/frame.lua create mode 100644 aux-addon/tabs/post/core.lua create mode 100644 aux-addon/tabs/post/frame.lua create mode 100644 aux-addon/tabs/search/core.lua create mode 100644 aux-addon/tabs/search/filter.lua create mode 100644 aux-addon/tabs/search/frame.lua create mode 100644 aux-addon/tabs/search/results.lua create mode 100644 aux-addon/tabs/search/saved.lua create mode 100644 aux-addon/util.lua create mode 100644 aux-addon/util/completion.lua create mode 100644 aux-addon/util/filter.lua create mode 100644 aux-addon/util/info.lua create mode 100644 aux-addon/util/money.lua create mode 100644 aux-addon/util/persistence.lua create mode 100644 aux-addon/util/scan.lua create mode 100644 aux-addon/util/sort.lua diff --git a/aux-addon/README.md b/aux-addon/README.md new file mode 100644 index 0000000..34cc2da --- /dev/null +++ b/aux-addon/README.md @@ -0,0 +1,156 @@ +# aux - WoW 1.12 AddOn + +The most advanced auction house addOn for the 1.12 client with some features more advanced than anything even on retail. + +## Core Features + +### General +* Completely independent replacement for the Blizzard interface. +* Elegant look based on the retail addOn TSM. +* Many convenient shortcuts. +* Convenient access to the unaltered Blizzard interface. + +### Search +* Automatic scanning of all pages for a query. +* Saving of recent and favorite queries. +* History of result listings with internet browser-like interface. +* Advanced search filters which can be combined with logical operators. +* Autocompletion for entering filters. +* Concise listings cleary showing the most important information. +* Sorting by percentage of historical value and unit price. +* Sorting across all scanned pages. +* Quick buying from any page without rescanning everything. +* Real time mode which continuously scans the last page. + +### Post +* Automatic assembling and posting of multiple stacks. +* Automatic scanning of existing auctions. +* Concise listing of existing auctions. +* Undercutting of existing auctions by click. +* Concise listing of inventory items excluding the non auctionable. +* Manual exclusion of specific items from the inventory listing. +* Saving post configuration per item. +* Efficient price input inspired by the retail addOn TSM. + +### History +* Automatic gathering of historical data from all scans. +* Automatic collection of vendor prices. +* Intricate calculations for a reliable historical value. +* Tooltip with historical value, vendor prices and disenchant value. +* Efficient storage of data. + +## Slash Commands +### General +**/aux** (Lists the settings)
+**/aux scale _factor_** (Scales the aux GUI by _factor_)
+**/aux ignore owner** (Disables waiting for owner names when scanning. Recommended)
+**/aux post bid** (Adds a bid price listing to the post tab)
+**/aux crafting cost** (Toggles the crafting price information)
+**/aux post duration _hours_** (Sets the default auction duration to _2_/_8_/_24_ hours)
+### Tooltip +**/aux tooltip value**
+**/aux tooltip daily**
+**/aux tooltip disenchant value**
+**/aux tooltip disenchant distribution**
+**/aux tooltip vendor buy**
+**/aux tooltip vendor sell**
+ +## Usage +### General +For the auction listings in the search, auctions and bids tabs the following shortcuts are available. +- Double-click on a row with blue colored count to expand it. +- Alt-left-click on the selected row for buyout/cancel. +- Alt-right-click on the selected row for bid/cancel. +- Right-click on a row to start a search for the auctioned item. +- Control-click on a row the show a preview in the wardrobe frame. +- Shift-click on a row to copy the link to the chatframe. +- Left-click on a header to sort. +- Right-click on a header of a price column to switch between unit and stack price. + +Furthermore +- Double-click in editboxes will highlight everything. + +### Search +- Hitting tab in the search box will accept an autocompletion. +- Dragging inventory items to the search box or right-clicking them will start a search. +- Right-clicking item links will start a search. + +#### Search Results +![Alt text](http://i.imgur.com/hI6ODqM.png) +- Bid prices for your own active bids are colored in green. +- Bid prices for other auctions with an active bid are colored in orange. + +#### Saved Searches +![Alt text](http://i.imgur.com/dICDnxR.png) +- When hovering over an entry the tooltip shows a longer and more nicely formatted version. +- Left-click on an entry will start a search. +- Right-click on an entry will show a menu with various options, including toggling Auto Buy. +- Shift-left-click on an entry will copy a search to the search box. +- Shift-right-click on an entry will add a search to the existing query in the search box. + +#### Filter Builder +![Alt text](http://i.imgur.com/8hilZc9.png) +While it is faster to type filters directly into the search box this sub-tab serves as a tutorial to learn how to formulate queries. +The filters on the left side are Blizzard filters which may reduce the number of pages to be scanned and those on the right side are post filters which do not affect the scan time but can be combined with logical operators to formulate very complex filters. +### Post +![Alt text](http://i.imgur.com/otzOT2I.png) +- When entering prices **g**, **s** and **c** denote gold, silver and copper respectively. +- A price value without explicit denotations will count as gold. (e.g., 10.5 = 10g50s) +- Price values can contain decimals. (e.g., 1.5g = 1g50s) +- Right-clicking an item in the inventory listing will start a search. +- Right-clicking a bag item will select it in the listing. +- In the listing of bids/buyouts a red price is undercutting stack/unit price. +- Clicking an entry in the in the listings of bids/buyouts of existing auctions will undercut with your bid stack/buyout unit price. +- Double-click in the bids/buyouts listings will also match the stack size. + +### Auctions +![Alt text](http://i.imgur.com/6HjaIo2.png) + +### Bids +![Alt text](http://i.imgur.com/NOjPKNW.png) + +## Search Filters +AddOns do not have any additional Blizzard filters available to them beyond the ones in the default auction house interface, nor do they have any other ways to combine them. +Of course it is possible for an addOn to apply arbitrary filters after the Blizzard query but only the Blizzard query will affect the number of pages to be scanned and thus the time it takes for a scan. +Since the Vanilla API will only let you request a page every 4 seconds having no Blizzard query in your filter can lead to very long scan times. + +aux queries are separated by semicolons and always contain exactly one Blizzard query. The Blizzard query may be empty, i.e., all pages are scanned, which can be useful for collecting historical data. +The real time mode only supports empty Blizzard queries. +Semicolons always mean "or", i.e., **q1;q2;q3** means all items matching **q1** or **q2** or **q3** will be listed. + +The parts of individual queries are separated by slashes, e.g., **q1p1/q1p2;q2p1/q2p2/q2p3**. All parts either belong to the Blizzard filter or the post processing filter. + +Blizzard filters can be created through the form on the left side of the "New Filter" sub-tab of the "Search" tab or typed directly into the search box. +For learning to write queries you can fill in the form, add the query to the search box with the "Add" or "Replace" buttons and inspect the generated output until you feel comfortable typing them out yourself. +For the most part it should be rather intuitive. +The first part is special in that if it doesn't match any specific filter keyword it will be treated as a Blizzard name search. E.g., a query consisting only of **felcloth** would list the items Felcloth, Pattern: Felcloth Hood, Felcloth Bag etc. +Usually you would want to use the **exact** modifier which only matches auctions where the name, apart from case, exactly equals the first part of the query. +**exact** is the only modifier which is part Blizzard and part post filter, though it is mostly treated as a Blizzard filter. **exact** will tailor the Blizzard query as well as possible towards the item searched (level range, item class/subclass/slot, quality ...) and it cannot be used together with Blizzard filters for these properties. + +Post processing filters are more flexible. +They are specified using the filter primitives you find on the right side of the "New Filter" sub-tab and can be combined with **and**, **or** and **not** using polish notation (https://en.wikipedia.org/wiki/Polish_notation). +Filter parts other than the first which don't match any specific filter, just like the first part is treated as a Blizzard name filter, are treated as a tooltip filter. +For using a tooltip filter as the first filter part there is an explicit **tooltip** modifier. + +Here are some queries I use myself for illustration: + +**or/and2/profit/5g/percent/60/and3/bid-profit/5g/bid-percent/60/left/30m**
+This filter will search the whole auction house for auctions either with a buyout price of 5g or more below market value and 60% or less of the market value or a bid price for which the same is true and in addition only 30m or less remaining. + +**wrangler's wristbands/exact/or2/and2/+3 agility/+3 stamina/+5 stamina/price/1g**
+This will search for wrangler's wristband with 3/3 monkey or 5 stam suffixes for at most 1g buyout price. + +**recipe/usable/not/libram**
+This will scan for usable recipes and exclude those with "libram" in the tooltip (i.e., librams) + +**armor/cloth/50/intellect/stamina**
+This will scan the auction house for cloth armor which has a requirement of at least lvl 50 as well both intellect and stamina stats. + +## Historical Value + +aux condenses the prices you've scanned during a day (midnight to midnight) into a single value, similarly to retail Auctioneer's "stat-simple" module. +This daily value is calculated as the minimum buyout of the day which in practice gives a similar enough value to that of retail TSM while using much less memory. +Limiting the memory usage is important because like Auctioneer and unlike TSM aux is using a day based instead of scan based interval and thus has to store the daily progress in the savedvariables. +Finally these daily values are collected in a list of the last 11 of them from which the market value is taken as the median. The values are weighted by their age but it doesn't have a large effect unless they're older than a month. + +The bottom line is that you get a fairly accurate market value for both very active markets as well as rarer items that has a reasonably short reaction time to market changes, recovers easily and never needs to be reset while still being reasonably stable and hard to manipulate and dealing with outliers very well, not getting distorted by multiple scans per day, not needing a single full scan per day but instead naturally picking up every price you scan while going about your usual business, letting you focus on a certain part of the auction house by scanning only that part regularly and avoiding information overload by giving you a single concise value for the tooltip. diff --git a/aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP b/aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP new file mode 100644 index 0000000000000000000000000000000000000000..2abd5465ad0449a65b3cdf9007ad929c0daaa704 GIT binary patch literal 3908 zcmeH~y=&Y^7{({U0a=)23m03sFp8XupPUUTV8eJG?I0|CWmf^kavbmsfVk z`IBQk-tg~#wO!}a_wAVd4cqbCQ~Im(y43v)o3FLM&vETcw||ZO&fW#*`&LqS$f)#p zP^f>N7X|t1)yWCTc9QL>AGrkzd4xhr>3NZx`aGt{*ZiYm>Sw{$*5M&ZKeO0FeHNrX z+wBnXf?~*e&+@GQf-Io_I_)qEiefS$^|)IOP_h8}DZhGt_M@EzIDeGN_DIjN#C&Ln zoTnXwlgUI8j@z*ruf`qPVLR^idI|X#`jfwbSupO>ub~~cV^k;OuHIk213S-ilIyKe zD*H$((hqGZElo#Qv~k^wey<;L{J7iiB+x_A*fkcF4XFOWko`fWJLh2>IF4mSu8s%l zeS$J->V4C;P^dS@E8Ve9(z6_lo7VkP{rC6xw`S?ic}@M7^yj>F-HlD%AQR4C>0VSs z{A9r_{f+L1?B}5v-3_UGo-+>g{%H?Us5941CP;ur=g-mTPu&vuBlEJDslOZ^9cjId z?$gBUC#Q7BzNsVhRfH}o4nU*xOkx|V9@{9Z_dg*KR*x(FA2R=c_)u8P{|H~FqG0}q zdpYionxJ6*r_KD2@sjx;{Y&P5lKG!x-e>tx<%|648|&=2!J$^6gxnfE!~ka?eWoy_~}Z^*n)KXo$i zvmf(4{d|qg`|MX|zq58m|F=}<1N+-Z-H#Ffe81=O;xr2)_YoFGy{$=}x8u~eV6X1z zdz?D)zNe0#vazk}sTc42ELh#q_p|20dgL-to;=f}4jjLBW{2!=hr1iRw|L&PpEvM5 zfB@zBvLDZr_3c=H2Q;=*pZ<7mY&YaOS)UyDI}-g3c|Kf^;o6yAKlhK%o)-6iIvmb( z{}}a%`^PW`W$D<$$!7W|F{^4#aK@P+l%Z-LCK_!n8k)jj$VAifRZ literal 0 HcmV?d00001 diff --git a/aux-addon/aux-addon.lua b/aux-addon/aux-addon.lua new file mode 100644 index 0000000..b86e066 --- /dev/null +++ b/aux-addon/aux-addon.lua @@ -0,0 +1,243 @@ +module 'aux' + +local T = require 'T' +local post = require 'aux.tabs.post' + +M.print = T.vararg-function(arg) + DEFAULT_CHAT_FRAME:AddMessage(LIGHTYELLOW_FONT_COLOR_CODE .. ' ' .. join(map(arg, tostring), ' ')) +end + +local bids_loaded +function M.bids_loaded() return bids_loaded end + +local current_owner_page +function M.current_owner_page() return current_owner_page end + +local event_frame = CreateFrame'Frame' +for event in T.temp-T.set('ADDON_LOADED', 'VARIABLES_LOADED', 'PLAYER_LOGIN', 'AUCTION_HOUSE_SHOW', 'AUCTION_HOUSE_CLOSED', 'AUCTION_BIDDER_LIST_UPDATE', 'AUCTION_OWNED_LIST_UPDATE') do + event_frame:RegisterEvent(event) +end + +local set_handler = {} +M.handle = setmetatable({}, {__metatable=false, __newindex=function(_, k, v) set_handler[k](v) end}) + +do + local handlers, handlers2 = {}, {} + function set_handler.LOAD(f) + tinsert(handlers, f) + end + function set_handler.LOAD2(f) + tinsert(handlers2, f) + end + event_frame:SetScript('OnEvent', function() + if event == 'ADDON_LOADED' then + if arg1 == 'Blizzard_AuctionUI' then + auction_ui_loaded() + end + elseif event == 'VARIABLES_LOADED' then + for _, f in handlers do f() end + elseif event == 'PLAYER_LOGIN' then + for _, f in handlers2 do f() end + print('loaded - /aux') + else + _M[event]() + end + end) +end + +function handle.LOAD() + _G.aux = aux or {} + assign(aux, { + account = {}, + realm = {}, + faction = {}, + character = {}, + }) + M.account_data = assign(aux.account, { + scale = 1, + ignore_owner = true, + crafting_cost = true, + post_bid = false, + post_duration = post.DURATION_24, + items = {}, + item_ids = {}, + auctionable_items = {}, + merchant_buy = {}, + merchant_sell = {}, + }) + do + local key = format('%s|%s', GetCVar'realmName', UnitName'player') + aux.character[key] = aux.character[key] or {} + M.character_data = assign(aux.character[key], { + tooltip = { + value = true, + merchant_sell = false, + merchant_buy = false, + daily = false, + disenchant_value = false, + disenchant_distribution = false, + } + }) + end + do + local key = GetCVar'realmName' + aux.realm[key] = aux.realm[key] or {} + M.realm_data = assign(aux.realm[key], { + characters = {}, + recent_searches = {}, + favorite_searches = {}, + }) + end +end + +function handle.LOAD2() + local key = format('%s|%s', GetCVar'realmName', UnitFactionGroup'player') + aux.faction[key] = aux.faction[key] or {} + M.faction_data = assign(aux.faction[key], { + history = {}, + post = {}, + }) +end + +tab_info = {} +function M.tab(name) + local tab = T.map('name', name) + local tab_event = { + OPEN = function(f) tab.OPEN = f end, + CLOSE = function(f) tab.CLOSE = f end, + USE_ITEM = function(f) tab.USE_ITEM = f end, + CLICK_LINK = function(f) tab.CLICK_LINK = f end, + } + tinsert(tab_info, tab) + return setmetatable({}, {__metatable=false, __newindex=function(_, k, v) tab_event[k](v) end}) +end + +do + local index + function M.get_tab() return tab_info[index] end + function on_tab_click(i) + CloseDropDownMenus() + do (index and get_tab().CLOSE or pass)() end + index = i + do (index and get_tab().OPEN or pass)() end + end +end + +M.orig = setmetatable({[_G]=T.acquire()}, {__index=function(self, key) return self[_G][key] end}) +M.hook = T.vararg-function(arg) + local name, object, handler + if getn(arg) == 3 then + name, object, handler = unpack(arg) + else + object, name, handler = _G, unpack(arg) + end + handler = handler or getfenv(3)[name] + orig[object] = orig[object] or T.acquire() + assert(not orig[object][name], '"' .. name .. '" is already hooked into.') + orig[object][name], object[name] = object[name], handler + return hook +end + +do + local locked + function M.bid_in_progress() return locked end + function M.place_bid(type, index, amount, on_success) + if locked then return end + local money = GetMoney() + PlaceAuctionBid(type, index, amount) + if money >= amount then + locked = true + local send_signal, signal_received = signal() + thread(when, signal_received, function() + do (on_success or pass)() end + locked = false + end) + thread(when, later(5), send_signal) + event_listener('CHAT_MSG_SYSTEM', function(kill) + if arg1 == ERR_AUCTION_BID_PLACED then + send_signal() + kill() + end + end) + end + end +end + +do + local locked + function M.cancel_in_progress() return locked end + function M.cancel_auction(index, on_success) + if locked then return end + locked = true + CancelAuction(index) + local send_signal, signal_received = signal() + thread(when, signal_received, function() + do (on_success or pass)() end + locked = false + end) + thread(when, later(5), send_signal) + event_listener('CHAT_MSG_SYSTEM', function(kill) + if arg1 == ERR_AUCTION_REMOVED then + send_signal() + kill() + end + end) + end +end + +function handle.LOAD2() + frame:SetScale(account_data.scale) +end + +function AUCTION_HOUSE_SHOW() + AuctionFrame:Hide() + frame:Show() + set_tab(1) +end + +do + local handlers = {} + function set_handler.CLOSE(f) + tinsert(handlers, f) + end + function AUCTION_HOUSE_CLOSED() + bids_loaded = false + current_owner_page = nil + for _, handler in handlers do + handler() + end + set_tab() + frame:Hide() + end +end + +function AUCTION_BIDDER_LIST_UPDATE() + bids_loaded = true +end + +do + local last_owner_page_requested + function GetOwnerAuctionItems(index) + last_owner_page_requested = index + return orig.GetOwnerAuctionItems(index) + end + function AUCTION_OWNED_LIST_UPDATE() + current_owner_page = last_owner_page_requested or 0 + end +end + +function auction_ui_loaded() + AuctionFrame:UnregisterEvent('AUCTION_HOUSE_SHOW') + AuctionFrame:SetScript('OnHide', nil) + hook('ShowUIPanel', T.vararg-function(arg) + if arg[1] == AuctionFrame then return AuctionFrame:Show() end + return orig.ShowUIPanel(unpack(arg)) + end) + hook 'GetOwnerAuctionItems' 'SetItemRef' 'UseContainerItem' 'AuctionFrameAuctions_OnEvent' +end + +AuctionFrameAuctions_OnEvent = T.vararg-function(arg) + if AuctionFrameAuctions:IsVisible() then + return orig.AuctionFrameAuctions_OnEvent(unpack(arg)) + end +end \ No newline at end of file diff --git a/aux-addon/aux-addon.toc b/aux-addon/aux-addon.toc new file mode 100644 index 0000000..82b078a --- /dev/null +++ b/aux-addon/aux-addon.toc @@ -0,0 +1,54 @@ +## Interface: 11200 +## Author: shirsig +## Title: aux +## Notes: Auction House replacement +## SavedVariables: aux + +libs\package.lua +libs\T.lua + +aux-addon.lua +util.lua +control.lua +color.lua +gui\core.lua +frame.lua +util\persistence.lua +util\sort.lua +util\money.lua +core\cache.lua +util\info.lua +core\history.lua +core\disenchant.lua +util\filter.lua +util\completion.lua +core\slash.lua +core\scan.lua +util\scan.lua +core\stack.lua +core\post.lua +core\tooltip.lua +core\shortcut.lua + +gui\listing.lua +gui\auction_listing.lua +gui\item_listing.lua + +tabs\search\core.lua +tabs\search\results.lua +tabs\search\saved.lua +tabs\search\filter.lua +tabs\search\frame.lua + +tabs\post\core.lua +tabs\post\frame.lua + +tabs\auctions\core.lua +tabs\auctions\frame.lua + +tabs\bids\core.lua +tabs\bids\frame.lua + +core\crafting.lua + + diff --git a/aux-addon/color.lua b/aux-addon/color.lua new file mode 100644 index 0000000..cb7e2ce --- /dev/null +++ b/aux-addon/color.lua @@ -0,0 +1,51 @@ +module 'aux' + +local T = require 'T' + +function C(r, g, b, a) + local mt = T.map('__metatable', false, '__newindex', pass, 'color', T.list(r, g, b, a)) + function mt:__call(text) + local r, g, b, a = unpack(mt.color) + if text then + return format('|c%02X%02X%02X%02X', a, r, g, b) .. text .. FONT_COLOR_CODE_CLOSE + else + return r/255, g/255, b/255, a + end + end + function mt:__concat(text) + local r, g, b, a = unpack(mt.color) + return format('|c%02X%02X%02X%02X', a, r, g, b) .. text + end + return setmetatable(T.acquire(), mt) +end + +M.color = immutable-{ + none = setmetatable({}, {__metatable=false, __newindex=pass, __call=function(_, v) return v end, __concat=function(_, v) return v end}), + text = immutable-{enabled = C(255, 254, 250, 1), disabled = C(147, 151, 139, 1)}, + label = immutable-{enabled = C(216, 225, 211, 1), disabled = C(150, 148, 140, 1)}, + link = C(153, 255, 255, 1), + window = immutable-{background = C(24, 24, 24, .93), border = C(30, 30, 30, 1)}, + panel = immutable-{background = C(24, 24, 24, 1), border = C(255, 255, 255, .03)}, + content = immutable-{background = C(42, 42, 42, 1), border = C(0, 0, 0, 0)}, + state = immutable-{enabled = C(70, 140, 70, 1), disabled = C(140, 70, 70, 1)}, + + tooltip = immutable-{ + value = C(255, 255, 154, 1), + merchant = C(204, 127, 25, 1), + disenchant = immutable-{ + value = C(25, 153, 153, 1), + distribution = C(204, 204, 51, 1), + source = C(178, 178, 178, 1), + } + }, + + blue = C(41, 146, 255, 1), + green = C(22, 255, 22, 1), + yellow = C(255, 255, 0, 1), + orange = C(255, 146, 24, 1), + red = C(255, 0, 0, 1), + gray = C(187, 187, 187, 1), + gold = C(255, 255, 154, 1), + + blizzard = C(0, 180, 255, 1), +} diff --git a/aux-addon/control.lua b/aux-addon/control.lua new file mode 100644 index 0000000..7875f4e --- /dev/null +++ b/aux-addon/control.lua @@ -0,0 +1,121 @@ +module 'aux' + +local T = require 'T' + +local event_frame = CreateFrame'Frame' + +local listeners, threads = T.acquire(), T.acquire() + +local thread_id +function M.thread_id() return thread_id end + +function handle.LOAD() + event_frame:SetScript('OnUpdate', UPDATE) + event_frame:SetScript('OnEvent', EVENT) +end + +function EVENT() + for id, listener in listeners do + if listener.killed then + listeners[id] = nil + elseif event == listener.event then + listener.cb(listener.kill) + end + end +end + +do + function UPDATE() + for _, listener in listeners do + local event, needed = listener.event, false + for _, listener in listeners do + needed = needed or listener.event == event and not listener.killed + end + if not needed then + event_frame:UnregisterEvent(event) + end + end + + for id, thread in threads do + if thread.killed or not thread.k then + threads[id] = nil + else + local k = thread.k + thread.k = nil + thread_id = id + k() + thread_id = nil + end + end + end +end + +do + local id = 0 + function unique_id() + id = id + 1 + return id + end +end + +function M.kill_listener(listener_id) + local listener = listeners[listener_id] + if listener then + listener.killed = true + end +end + +function M.kill_thread(thread_id) + local thread = threads[thread_id] + if thread then + thread.killed = true + end +end + +function M.event_listener(event, cb) + local listener_id = unique_id() + listeners[listener_id] = T.map( + 'event', event, + 'cb', cb, + 'kill', T.vararg-function(arg) if getn(arg) == 0 or arg[1] then kill_listener(listener_id) end end + ) + event_frame:RegisterEvent(event) + return listener_id +end + +function M.on_next_event(event, callback) + event_listener(event, function(kill) callback(); kill() end) +end + +do + local mt = { + __call = function(self) + T.temp(self) + return self.f(unpack(self)) + end, + } + + M.thread = T.vararg-function(arg) + T.static(arg) + arg.f = tremove(arg, 1) + local thread_id = unique_id() + threads[thread_id] = T.map('k', setmetatable(arg, mt)) + return thread_id + end + + M.wait = T.vararg-function(arg) + T.static(arg) + arg.f = tremove(arg, 1) + threads[thread_id].k = setmetatable(arg, mt) + end +end + +M.when = T.vararg-function(arg) + local c = tremove(arg, 1) + local k = tremove(arg, 1) + if c() then + return k(unpack(arg)) + else + return wait(when, c, k, unpack(arg)) + end +end diff --git a/aux-addon/core/cache.lua b/aux-addon/core/cache.lua new file mode 100644 index 0000000..563045c --- /dev/null +++ b/aux-addon/core/cache.lua @@ -0,0 +1,211 @@ +module 'aux.util.info' + +local T = require 'T' +local aux = require 'aux' +local persistence = require 'aux.util.persistence' + +local MIN_ITEM_ID = 1 +local MAX_ITEM_ID = 30000 + +local items_schema = {'tuple', '#', {name='string'}, {quality='number'}, {level='number'}, {class='string'}, {subclass='string'}, {slot='string'}, {max_stack='number'}, {texture='string'}} +local merchant_buy_schema = {'tuple', '#', {unit_price='number'}, {limited='boolean'}} + +function aux.handle.LOAD() + scan_wdb() + + aux.event_listener('MERCHANT_SHOW', on_merchant_show) + aux.event_listener('MERCHANT_CLOSED', on_merchant_closed) + aux.event_listener('MERCHANT_UPDATE', on_merchant_update) + aux.event_listener('BAG_UPDATE', on_bag_update) + + CreateFrame('Frame', nil, MerchantFrame):SetScript('OnUpdate', merchant_on_update) + + aux.event_listener('NEW_AUCTION_UPDATE', function() + local data = auction_sell_item() + if data then + local item_id = item_id(data.name) + if item_id then + aux.account_data.merchant_sell[item_id] = data.vendor_price / (max_item_charges(item_id) or data.count) + end + end + end) +end + +do + local characters = {} + function M.is_player(name) + return not not characters[name] + end + function aux.handle.LOAD() + characters = aux.realm_data.characters + for k, v in characters do + if GetTime() > v + 60 * 60 * 24 * 30 then + characters[k] = nil + end + end + characters[UnitName'player'] = GetTime() + end +end + +do + local sell_scan_queued, incomplete_buy_data + function on_merchant_show() + merchant_sell_scan() + incomplete_buy_data = not merchant_buy_scan() + end + function on_merchant_closed() + sell_scan_queued = nil + incomplete_buy_data = false + end + function on_merchant_update() + if incomplete_buy_data then + incomplete_buy_data = not merchant_buy_scan() + end + end + function on_bag_update() + if MerchantFrame:IsVisible() then + sell_scan_queued = true + end + end + function merchant_on_update() + if sell_scan_queued then + sell_scan_queued = nil + merchant_sell_scan() + end + end +end + +function merchant_loaded() + for i = 1, GetMerchantNumItems() do + if not GetMerchantItemLink(i) then + return false + end + end + return true +end + +function M.merchant_info(item_id) + local buy_info + if aux.account_data.merchant_buy[item_id] then + buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id]) + end + return aux.account_data.merchant_sell[item_id], buy_info and buy_info.unit_price, buy_info and buy_info.limited +end + +function M.item_info(item_id) + local data_string = aux.account_data.items[item_id] + if data_string then + local cached_data = persistence.read(items_schema, data_string) + return T.map( + 'name', cached_data.name, + 'itemstring', 'item:' .. item_id .. ':0:0:0', + 'quality', cached_data.quality, + 'level', cached_data.level, + 'class', cached_data.class, + 'subclass', cached_data.subclass, + 'slot', cached_data.slot, + 'max_stack', cached_data.max_stack, + 'texture', cached_data.texture + ) + end +end + +function M.item_id(item_name) + return aux.account_data.item_ids[strlower(item_name)] +end + +function merchant_buy_scan() + local incomplete_data + for i = 1, GetMerchantNumItems() do + local _, _, price, count, stock = GetMerchantItemInfo(i) + local link = GetMerchantItemLink(i) + if link then + local item_id = parse_link(link) + local new_unit_price, new_limited = price / count, stock >= 0 + if aux.account_data.merchant_buy[item_id] then + local buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id]) + + local unit_price + if buy_info.limited and not new_limited then + unit_price = new_unit_price + elseif new_limited and not buy_info.limited then + unit_price = buy_info.unit_price + else + unit_price = min(buy_info.unit_price, new_unit_price) + end + + aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map( + 'unit_price', unit_price, + 'limited', buy_info.limited and new_limited + )) + else + aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map( + 'unit_price', new_unit_price, + 'limited', new_limited + )) + end + else + incomplete_data = true + end + end + + return not incomplete_data +end + +function merchant_sell_scan() + for slot in inventory() do + T.temp(slot) + local item_info = T.temp-container_item(unpack(slot)) + if item_info then + aux.account_data.merchant_sell[item_info.item_id] = item_info.tooltip_money / item_info.aux_quantity + end + end +end + +function scan_wdb(item_id) + item_id = item_id or MIN_ITEM_ID + + local processed = 0 + while processed <= 100 and item_id <= MAX_ITEM_ID do + local itemstring = 'item:' .. item_id + local name, _, quality, level, class, subclass, max_stack, slot, texture = GetItemInfo(itemstring) + if name and not aux.account_data.item_ids[strlower(name)] then + aux.account_data.item_ids[strlower(name)] = item_id + aux.account_data.items[item_id] = persistence.write(items_schema, T.temp-T.map( + 'name', name, + 'quality', quality, + 'level', level, + 'class', class, + 'subclass', subclass, + 'slot', slot, + 'max_stack', max_stack, + 'texture', texture + )) + local tooltip = tooltip('link', itemstring) + if auctionable(tooltip, quality) then + tinsert(aux.account_data.auctionable_items, strlower(name)) + end + processed = processed + 1 + end + item_id = item_id + 1 + end + + if item_id <= MAX_ITEM_ID then + aux.thread(aux.when, aux.later(.5), scan_wdb, item_id) + else + sort(aux.account_data.auctionable_items, function(a, b) return strlen(a) < strlen(b) or (strlen(a) == strlen(b) and a < b) end) + end +end + +function M.populate_wdb(item_id) + item_id = item_id or MIN_ITEM_ID + if item_id > MAX_ITEM_ID then + aux.print('Cache populated.') + return + end + if not GetItemInfo('item:' .. item_id) then + aux.print('Fetching item ' .. item_id .. '.') + AuxTooltip:SetHyperlink('item:' .. item_id) + end + aux.thread(populate_wdb, item_id + 1) +end \ No newline at end of file diff --git a/aux-addon/core/crafting.lua b/aux-addon/core/crafting.lua new file mode 100644 index 0000000..b00df11 --- /dev/null +++ b/aux-addon/core/crafting.lua @@ -0,0 +1,99 @@ +module 'aux.core.crafting' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local money = require 'aux.util.money' +local history = require 'aux.core.history' +local search_tab = require 'aux.tabs.search' + +function aux.handle.LOAD() + if not aux.account_data.crafting_cost then + return + end + aux.event_listener('ADDON_LOADED', function() + if arg1 == 'Blizzard_CraftUI' then + craft_ui_loaded() + elseif arg1 == 'Blizzard_TradeSkillUI' then + trade_skill_ui_loaded() + end + end) +end + +do + local function cost_label(cost) + local label = LIGHTYELLOW_FONT_COLOR_CODE .. '(Total Cost: ' .. FONT_COLOR_CODE_CLOSE + label = label .. (cost and money.to_string2(cost, nil, LIGHTYELLOW_FONT_COLOR_CODE) or GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE) + label = label .. LIGHTYELLOW_FONT_COLOR_CODE .. ')' .. FONT_COLOR_CODE_CLOSE + return label + end + local function hook_quest_item(f) + f:SetScript('OnMouseUp', function() + if arg1 == 'RightButton' then + if aux.get_tab() then + aux.set_tab(1) + search_tab.set_filter(_G[this:GetName() .. 'Name']:GetText() .. '/exact') + search_tab.execute(nil, false) + end + end + end) + end + function craft_ui_loaded() + aux.hook('CraftFrame_SetSelection', T.vararg-function(arg) + local ret = T.temp-T.list(aux.orig.CraftFrame_SetSelection(unpack(arg))) + local id = GetCraftSelectionIndex() + local total_cost = 0 + for i = 1, GetCraftNumReagents(id) do + local link = GetCraftReagentItemLink(id, i) + if not link then + total_cost = nil + break + end + local item_id, suffix_id = info.parse_link(link) + local count = aux.select(3, GetCraftReagentInfo(id, i)) + local _, price, limited = info.merchant_info(item_id) + local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id) + if not value then + total_cost = nil + break + else + total_cost = total_cost + value * count + end + end + CraftReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) + return unpack(ret) + end) + for i = 1, 8 do + hook_quest_item(_G['CraftReagent' .. i]) + end + end + function trade_skill_ui_loaded() + aux.hook('TradeSkillFrame_SetSelection', T.vararg-function(arg) + local ret = T.temp-T.list(aux.orig.TradeSkillFrame_SetSelection(unpack(arg))) + local id = GetTradeSkillSelectionIndex() + local total_cost = 0 + for i = 1, GetTradeSkillNumReagents(id) do + local link = GetTradeSkillReagentItemLink(id, i) + if not link then + total_cost = nil + break + end + local item_id, suffix_id = info.parse_link(link) + local count = aux.select(3, GetTradeSkillReagentInfo(id, i)) + local _, price, limited = info.merchant_info(item_id) + local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id) + if not value then + total_cost = nil + break + else + total_cost = total_cost + value * count + end + end + TradeSkillReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) + return unpack(ret) + end) + for i = 1, 8 do + hook_quest_item(_G['TradeSkillReagent' .. i]) + end + end +end \ No newline at end of file diff --git a/aux-addon/core/disenchant.lua b/aux-addon/core/disenchant.lua new file mode 100644 index 0000000..3d61dab --- /dev/null +++ b/aux-addon/core/disenchant.lua @@ -0,0 +1,163 @@ +module 'aux.core.disenchant' + +local T = require 'T' +local history = require 'aux.core.history' + +local UNCOMMON, RARE, EPIC = 2, 3, 4 + +local ARMOR = T.set( + 'INVTYPE_HEAD', + 'INVTYPE_NECK', + 'INVTYPE_SHOULDER', + 'INVTYPE_BODY', + 'INVTYPE_CHEST', + 'INVTYPE_ROBE', + 'INVTYPE_WAIST', + 'INVTYPE_LEGS', + 'INVTYPE_FEET', + 'INVTYPE_WRIST', + 'INVTYPE_HAND', + 'INVTYPE_FINGER', + 'INVTYPE_TRINKET', + 'INVTYPE_CLOAK', + 'INVTYPE_HOLDABLE' +) + +local WEAPON = T.set( + 'INVTYPE_2HWEAPON', + 'INVTYPE_WEAPONMAINHAND', + 'INVTYPE_WEAPON', + 'INVTYPE_WEAPONOFFHAND', + 'INVTYPE_SHIELD', + 'INVTYPE_RANGED', + 'INVTYPE_RANGEDRIGHT' +) + +function M.value(slot, quality, level) + local expectation + for _, event in distribution(slot, quality, level) do + local value = history.value(event.item_id .. ':' .. 0) + if not value then + return + else + expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value + end + end + return expectation +end + +function M.distribution(slot, quality, level) + if not ARMOR[slot] and not WEAPON[slot] or level == 0 then + return T.acquire() + end + + local function p(probability_armor, probability_weapon) + if ARMOR[slot] then + return probability_armor + elseif WEAPON[slot] then + return probability_weapon + end + end + + if quality == UNCOMMON then + if level <= 10 then + return T.temp-T.list( + T.temp-T.map('item_id', 10940, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.8, .2)), + T.temp-T.map('item_id', 10938, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .8)) + ) + elseif level <= 15 then + return T.temp-T.list( + T.temp-T.map('item_id', 10940, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 10939, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 20 then + return T.temp-T.list( + T.temp-T.map('item_id', 10940, 'min_quantity', 4, 'max_quantity', 6, 'probability', p(.75, .15)), + T.temp-T.map('item_id', 10998, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.15, .75)), + T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .10) + ) + elseif level <= 25 then + return T.temp-T.list( + T.temp-T.map('item_id', 11083, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 11082, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 30 then + return T.temp-T.list( + T.temp-T.map('item_id', 11083, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 11134, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 35 then + return T.temp-T.list( + T.temp-T.map('item_id', 11137, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 11135, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 40 then + return T.temp-T.list( + T.temp-T.map('item_id', 11137, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 11174, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 45 then + return T.temp-T.list( + T.temp-T.map('item_id', 11176, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), + T.temp-T.map('item_id', 11175, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) + ) + elseif level <= 50 then + return T.temp-T.list( + T.temp-T.map('item_id', 11176, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)), + T.temp-T.map('item_id', 16202, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) + ) + elseif level <= 55 then + return T.temp-T.list( + T.temp-T.map('item_id', 16204, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .22)), + T.temp-T.map('item_id', 16203, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) + ) + elseif level <= 60 then + return T.temp-T.list( + T.temp-T.map('item_id', 16204, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)), + T.temp-T.map('item_id', 16203, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.2, .75)), + T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) + ) + end + elseif quality == RARE then + if level <= 20 then + return T.temp-T.list(T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 25 then + return T.temp-T.list(T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 30 then + return T.temp-T.list(T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 35 then + return T.temp-T.list(T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 40 then + return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 45 then + return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 50 then + return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 55 then + return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005)) + elseif level <= 60 then + return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005)) + end + elseif quality == EPIC then + if level <= 40 then + return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) + elseif level <= 45 then + return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) + elseif level <= 50 then + return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) + elseif level <= 55 then + return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) + elseif level <= 60 then + return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 2, 'probability', 1)) + end + end + return T.acquire() +end \ No newline at end of file diff --git a/aux-addon/core/history.lua b/aux-addon/core/history.lua new file mode 100644 index 0000000..140c77b --- /dev/null +++ b/aux-addon/core/history.lua @@ -0,0 +1,109 @@ +module 'aux.core.history' + +local T = require 'T' +local aux = require 'aux' + +local persistence = require 'aux.util.persistence' + +local history_schema = {'tuple', '#', {next_push='number'}, {daily_min_buyout='number'}, {data_points={'list', ';', {'tuple', '@', {value='number'}, {time='number'}}}}} + +local value_cache = {} + +function aux.handle.LOAD2() + data = aux.faction_data.history +end + +do + local next_push = 0 + function get_next_push() + if time() > next_push then + local date = date('*t') + date.hour, date.min, date.sec = 24, 0, 0 + next_push = time(date) + end + return next_push + end +end + +function new_record() + return T.temp-T.map('next_push', get_next_push(), 'data_points', T.acquire()) +end + +function read_record(item_key) + local record = data[item_key] and persistence.read(history_schema, data[item_key]) or new_record() + if record.next_push <= time() then + push_record(record) + write_record(item_key, record) + end + return record +end + +function write_record(item_key, record) + data[item_key] = persistence.write(history_schema, record) + if value_cache[item_key] then + T.release(value_cache[item_key]) + value_cache[item_key] = nil + end +end + +function M.process_auction(auction_record) + local item_record = read_record(auction_record.item_key) + local unit_buyout_price = ceil(auction_record.buyout_price / auction_record.aux_quantity) + if unit_buyout_price > 0 and unit_buyout_price < (item_record.daily_min_buyout or aux.huge) then + item_record.daily_min_buyout = unit_buyout_price + write_record(auction_record.item_key, item_record) + end +end + +function M.data_points(item_key) + return read_record(item_key).data_points +end + +function M.value(item_key) + if not value_cache[item_key] or value_cache[item_key].next_push <= time() then + local item_record, value + item_record = read_record(item_key) + if getn(item_record.data_points) > 0 then + local total_weight, weighted_values = 0, T.temp-T.acquire() + for _, data_point in item_record.data_points do + local weight = .99 ^ aux.round((item_record.data_points[1].time - data_point.time) / (60 * 60 * 24)) + total_weight = total_weight + weight + tinsert(weighted_values, T.map('value', data_point.value, 'weight', weight)) + end + for _, weighted_value in weighted_values do + weighted_value.weight = weighted_value.weight / total_weight + end + value = weighted_median(weighted_values) + else + value = item_record.daily_min_buyout + end + value_cache[item_key] = T.map('value', value, 'next_push', item_record.next_push) + end + return value_cache[item_key].value +end + +function M.market_value(item_key) + return read_record(item_key).daily_min_buyout +end + +function weighted_median(list) + sort(list, function(a,b) return a.value < b.value end) + local weight = 0 + for _, v in ipairs(list) do + weight = weight + v.weight + if weight >= .5 then + return v.value + end + end +end + +function push_record(item_record) + if item_record.daily_min_buyout then + tinsert(item_record.data_points, 1, T.map('value', item_record.daily_min_buyout, 'time', item_record.next_push)) + while getn(item_record.data_points) > 11 do + T.release(item_record.data_points[getn(item_record.data_points)]) + tremove(item_record.data_points) + end + end + item_record.next_push, item_record.daily_min_buyout = get_next_push(), nil +end \ No newline at end of file diff --git a/aux-addon/core/post.lua b/aux-addon/core/post.lua new file mode 100644 index 0000000..37a506a --- /dev/null +++ b/aux-addon/core/post.lua @@ -0,0 +1,165 @@ +module 'aux.core.post' + +local aux = require 'aux' +local info = require 'aux.util.info' +local stack = require 'aux.core.stack' +local history = require 'aux.core.history' +local disenchant = require 'aux.core.disenchant' + +local state + +function aux.handle.CLOSE() + stop() +end + +function process() + if state.posted < state.count then + + local stacking_complete + + local send_signal, signal_received = aux.signal() + aux.when(signal_received, function() + local slot = signal_received()[1] + if slot then + return post_auction(slot, process) + else + return stop() + end + end) + + return stack.start(state.item_key, state.stack_size, send_signal) + end + + return stop() +end + +function post_auction2(slot, k) + local item_info = info.container_item(unpack(slot)) + if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then + + ClearCursor() + ClickAuctionSellItemButton() + ClearCursor() + PickupContainerItem(unpack(slot)) + ClickAuctionSellItemButton() + ClearCursor() + + StartAuction(max(1, aux.round(state.unit_start_price * item_info.aux_quantity)), aux.round(state.unit_buyout_price * item_info.aux_quantity), state.duration) + + local send_signal, signal_received = aux.signal() + aux.when(signal_received, function() + state.posted = state.posted + 1 + return k() + end) + + local posted + aux.event_listener('CHAT_MSG_SYSTEM', function(kill) + if arg1 == ERR_AUCTION_STARTED then + send_signal() + kill() + end + end) + else + return stop() + end +end + +function post_auction(slot, k) + local item_info = info.container_item(unpack(slot)) + if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then + + ClearCursor() + ClickAuctionSellItemButton() + ClearCursor() + PickupContainerItem(unpack(slot)) + ClickAuctionSellItemButton() + ClearCursor() + + local start_price = state.unit_start_price + local buyout_price = state.unit_buyout_price + local kz_undercut = 0 + + if start_price == 0 then + + if history.market_value(state.item_key) ~= nil then + start_price = max(start_price,0.93*tonumber(history.market_value(state.item_key))) + buyout_price = max(buyout_price,0.99*tonumber(history.market_value(state.item_key))) + + kz_undercut = 1 + end + + if disenchant.value(item_info.slot, item_info.quality, item_info.level) ~= nil then + buyout_price = max(buyout_price,0.80* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level))) + end + + if aux.account_data.merchant_sell[item_info.item_id] ~= nil then + start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * 1.35) + buyout_price = max(buyout_price,start_price) + + if kz_undercut == 0 then + start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * (1.35+3.65*math.exp(-(1/4000)* tonumber(aux.account_data.merchant_sell[item_info.item_id] )))) + buyout_price = max(buyout_price,start_price) + end + end + + if history.value(state.item_key) ~= nil then + start_price = max(start_price,0.89*tonumber(history.value(state.item_key))) + buyout_price = max(buyout_price,0.89*tonumber(history.value(state.item_key))) + + if kz_undercut == 0 then + buyout_price = max(buyout_price,1.0* tonumber(history.value(state.item_key))) + end + end + + start_price = max(start_price,0.75 * buyout_price) + end + + StartAuction(max(1, aux.round(start_price * item_info.aux_quantity)), aux.round(buyout_price * item_info.aux_quantity), state.duration) + + local send_signal, signal_received = aux.signal() + aux.when(signal_received, function() + state.posted = state.posted + 1 + return k() + end) + + local posted + aux.event_listener('CHAT_MSG_SYSTEM', function(kill) + if arg1 == ERR_AUCTION_STARTED then + send_signal() + kill() + end + end) + else + return stop() + end +end + +function M.stop() + if state then + aux.kill_thread(state.thread_id) + + local callback = state.callback + local posted = state.posted + + state = nil + + if callback then + callback(posted) + end + end +end + +function M.start(item_key, stack_size, duration, unit_start_price, unit_buyout_price, count, callback) + stop() + state = { + thread_id = aux.thread(process), + item_key = item_key, + stack_size = stack_size, + duration = duration, + unit_start_price = unit_start_price, + unit_buyout_price = unit_buyout_price, + count = count, + posted = 0, + callback = callback, + } +end \ No newline at end of file diff --git a/aux-addon/core/scan.lua b/aux-addon/core/scan.lua new file mode 100644 index 0000000..f59e70e --- /dev/null +++ b/aux-addon/core/scan.lua @@ -0,0 +1,236 @@ +module 'aux.core.scan' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local history = require 'aux.core.history' + +local PAGE_SIZE = 50 + +function aux.handle.CLOSE() + abort() +end + +do + local scan_states = {} + + function M.start(params) + local old_state = scan_states[params.type] + if old_state then + abort(old_state.id) + end + do (params.on_scan_start or pass)() end + local thread_id = aux.thread(scan) + scan_states[params.type] = { + id = thread_id, + params = params, + } + return thread_id + end + + function M.abort(scan_id) + local aborted = T.acquire() + for type, state in scan_states do + if not scan_id or state.id == scan_id then + aux.kill_thread(state.id) + scan_states[type] = nil + tinsert(aborted, state) + end + end + for _, state in aborted do + do (state.params.on_abort or pass)() end + end + end + + function M.stop() + get_state().stopped = true + end + + function complete() + local on_complete = get_state().params.on_complete + scan_states[get_state().params.type] = nil + do (on_complete or pass)() end + end + + function get_state() + for _, state in scan_states do + if state.id == aux.thread_id() then + return state + end + end + end +end + +function get_query() + return get_state().params.queries[get_state().query_index] +end + +function total_pages(total_auctions) + return ceil(total_auctions / PAGE_SIZE) +end + +function last_page(total_auctions) + local last_page = max(total_pages(total_auctions) - 1, 0) + local last_page_limit = get_query().blizzard_query.last_page or last_page + return min(last_page_limit, last_page) +end + +function scan() + get_state().query_index = get_state().query_index and get_state().query_index + 1 or 1 + if get_query() and not get_state().stopped then + do (get_state().params.on_start_query or pass)(get_state().query_index) end + if get_query().blizzard_query then + if (get_query().blizzard_query.first_page or 0) <= (get_query().blizzard_query.last_page or aux.huge) then + get_state().page = get_query().blizzard_query.first_page or 0 + return submit_query() + end + else + get_state().page = nil + return scan_page() + end + end + return complete() +end + +do + local function submit() + if get_state().params.type == 'bidder' then + GetBidderAuctionItems(get_state().page) + elseif get_state().params.type == 'owner' then + GetOwnerAuctionItems(get_state().page) + else + get_state().last_list_query = GetTime() + local blizzard_query = get_query().blizzard_query or T.acquire() + QueryAuctionItems( + blizzard_query.name, + blizzard_query.min_level, + blizzard_query.max_level, + blizzard_query.slot, + blizzard_query.class, + blizzard_query.subclass, + get_state().page, + blizzard_query.usable, + blizzard_query.quality + ) + end + return wait_for_results() + end + function submit_query() + if get_state().stopped then return end + if get_state().params.type ~= 'list' then + return submit() + else + return aux.when(CanSendAuctionQuery, submit) + end + end +end + +function scan_page(i) + i = i or 1 + + if i > PAGE_SIZE then + do (get_state().params.on_page_scanned or pass)() end + if get_query().blizzard_query and get_state().page < last_page(get_state().total_auctions) then + get_state().page = get_state().page + 1 + return submit_query() + else + return scan() + end + end + + local auction_info = info.auction(i, get_state().params.type) + if auction_info and (auction_info.owner or get_state().params.ignore_owner or aux.account_data.ignore_owner) then + auction_info.index = i + auction_info.page = get_state().page + auction_info.blizzard_query = get_query().blizzard_query + auction_info.query_type = get_state().params.type + + history.process_auction(auction_info) + + if (get_state().params.auto_bid_validator or pass)(auction_info) then + local send_signal, signal_received = aux.signal() + aux.when(signal_received, scan_page, i) + return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.bid_price, send_signal) + elseif (get_state().params.auto_buy_validator or pass)(auction_info) then + local send_signal, signal_received = aux.signal() + aux.when(signal_received, scan_page, i) + return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, send_signal) + elseif not get_query().validator or get_query().validator(auction_info) then + do (get_state().params.on_auction or pass)(auction_info) end + end + end + + return scan_page(i + 1) +end + +function wait_for_results() + if get_state().params.type == 'bidder' then + return aux.when(function() return aux.bids_loaded() end, accept_results) + elseif get_state().params.type == 'owner' then + return wait_for_owner_results() + elseif get_state().params.type == 'list' then + return wait_for_list_results() + end +end + +function accept_results() + _, get_state().total_auctions = GetNumAuctionItems(get_state().params.type) + do + (get_state().params.on_page_loaded or pass)( + get_state().page - (get_query().blizzard_query.first_page or 0) + 1, + last_page(get_state().total_auctions) - (get_query().blizzard_query.first_page or 0) + 1, + total_pages(get_state().total_auctions) - 1 + ) + end + return scan_page() +end + +function wait_for_owner_results() + if get_state().page == aux.current_owner_page() then + return accept_results() + else + local updated + aux.on_next_event('AUCTION_OWNED_LIST_UPDATE', function() updated = true end) + return aux.when(function() return updated end, accept_results) + end +end + +function wait_for_list_results() + local updated, last_update + local listener_id = aux.event_listener('AUCTION_ITEM_LIST_UPDATE', function() + last_update = GetTime() + updated = true + end) + local timeout = aux.later(5, get_state().last_list_query) + local ignore_owner = get_state().params.ignore_owner or aux.account_data.ignore_owner + return aux.when(function() + if not last_update and timeout() then + return true + end + if last_update and GetTime() - last_update > 5 then + return true + end + -- short circuiting order important, owner_data_complete must be called iif an update has happened. + if updated and (ignore_owner or owner_data_complete()) then + return true + end + updated = false + end, function() + aux.kill_listener(listener_id) + if not last_update and timeout() then + return submit_query() + else + return accept_results() + end + end) +end + +function owner_data_complete() + for i = 1, PAGE_SIZE do + local auction_info = info.auction(i, 'list') + if auction_info and not auction_info.owner then + return false + end + end + return true +end \ No newline at end of file diff --git a/aux-addon/core/shortcut.lua b/aux-addon/core/shortcut.lua new file mode 100644 index 0000000..84ac6fa --- /dev/null +++ b/aux-addon/core/shortcut.lua @@ -0,0 +1,31 @@ +module 'aux.core.shortcut' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' + +do + local orig = SetItemRef + _G.SetItemRef = T.vararg-function(arg) + if arg[3] ~= 'RightButton' or not aux.index(aux.get_tab(), 'CLICK_LINK') or not strfind(arg[1], '^item:%d+') then + return orig(unpack(arg)) + end + local item_info = info.item(tonumber(aux.select(3, strfind(arg[1], '^item:(%d+)')))) + if item_info then + return aux.get_tab().CLICK_LINK(item_info) + end + end +end + +do + local orig = UseContainerItem + _G.UseContainerItem = T.vararg-function(arg) + if aux.modified() or not aux.get_tab() then + return orig(unpack(arg)) + end + local item_info = info.container_item(arg[1], arg[2]) + if item_info and aux.get_tab().USE_ITEM then + aux.get_tab().USE_ITEM(item_info) + end + end +end \ No newline at end of file diff --git a/aux-addon/core/slash.lua b/aux-addon/core/slash.lua new file mode 100644 index 0000000..5adb2f9 --- /dev/null +++ b/aux-addon/core/slash.lua @@ -0,0 +1,74 @@ +module 'aux.core.slash' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local post = require 'aux.tabs.post' + +function status(enabled) + return (enabled and aux.color.green'on' or aux.color.red'off') +end + +_G.SLASH_AUX1 = '/aux' +function SlashCmdList.AUX(command) + if not command then return end + local arguments = aux.tokenize(command) + local tooltip_settings = aux.character_data.tooltip + if arguments[1] == 'scale' and tonumber(arguments[2]) then + local scale = tonumber(arguments[2]) + aux.frame:SetScale(scale) + aux.account_data.scale = scale + elseif arguments[1] == 'ignore' and arguments[2] == 'owner' then + aux.account_data.ignore_owner = not aux.account_data.ignore_owner + aux.print('ignore owner ' .. status(aux.account_data.ignore_owner)) + elseif arguments[1] == 'post' and arguments[2] == 'bid' then + aux.account_data.post_bid = not aux.account_data.post_bid + aux.print('post bid ' .. status(aux.account_data.post_bid)) + elseif arguments[1] == 'post' and arguments[2] == 'duration' and T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]] then + aux.account_data.post_duration = T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]] + aux.print('post duration ' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h')) + elseif arguments[1] == 'crafting' and arguments[2] == 'cost' then + aux.account_data.crafting_cost = not aux.account_data.crafting_cost + aux.print('crafting cost ' .. status(aux.account_data.crafting_cost)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'value' then + tooltip_settings.value = not tooltip_settings.value + aux.print('tooltip value ' .. status(tooltip_settings.value)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'daily' then + tooltip_settings.daily = not tooltip_settings.daily + aux.print('tooltip daily ' .. status(tooltip_settings.daily)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'buy' then + tooltip_settings.merchant_buy = not tooltip_settings.merchant_buy + aux.print('tooltip merchant buy ' .. status(tooltip_settings.merchant_buy)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'sell' then + tooltip_settings.merchant_sell = not tooltip_settings.merchant_sell + aux.print('tooltip merchant sell ' .. status(tooltip_settings.merchant_sell)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'value' then + tooltip_settings.disenchant_value = not tooltip_settings.disenchant_value + aux.print('tooltip disenchant value ' .. status(tooltip_settings.disenchant_value)) + elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'distribution' then + tooltip_settings.disenchant_distribution = not tooltip_settings.disenchant_distribution + aux.print('tooltip disenchant distribution ' .. status(tooltip_settings.disenchant_distribution)) + elseif arguments[1] == 'clear' and arguments[2] == 'item' and arguments[3] == 'cache' then + aux.account_data.items = {} + aux.account_data.item_ids = {} + aux.account_data.auctionable_items = {} + aux.print('Item cache cleared.') + elseif arguments[1] == 'populate' and arguments[2] == 'wdb' then + info.populate_wdb() + else + aux.print('Usage:') + aux.print('- scale [' .. aux.color.blue(aux.account_data.scale) .. ']') + aux.print('- ignore owner [' .. status(aux.account_data.ignore_owner) .. ']') + aux.print('- post bid [' .. status(aux.account_data.post_bid) .. ']') + aux.print('- post duration [' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h') .. ']') + aux.print('- crafting cost [' .. status(aux.account_data.crafting_cost) .. ']') + aux.print('- tooltip value [' .. status(tooltip_settings.value) .. ']') + aux.print('- tooltip daily [' .. status(tooltip_settings.daily) .. ']') + aux.print('- tooltip merchant buy [' .. status(tooltip_settings.merchant_buy) .. ']') + aux.print('- tooltip merchant sell [' .. status(tooltip_settings.merchant_sell) .. ']') + aux.print('- tooltip disenchant value [' .. status(tooltip_settings.disenchant_value) .. ']') + aux.print('- tooltip disenchant distribution [' .. status(tooltip_settings.disenchant_distribution) .. ']') + aux.print('- clear item cache') + aux.print('- populate wdb') + end +end \ No newline at end of file diff --git a/aux-addon/core/stack.lua b/aux-addon/core/stack.lua new file mode 100644 index 0000000..c954e38 --- /dev/null +++ b/aux-addon/core/stack.lua @@ -0,0 +1,130 @@ +module 'aux.core.stack' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' + +local state + +function aux.handle.CLOSE() + stop() +end + +function stack_size(slot) + local container_item_info = T.temp-info.container_item(unpack(slot)) + return container_item_info and container_item_info.count or 0 +end + +function charges(slot) + local container_item_info = T.temp-info.container_item(unpack(slot)) + return container_item_info and container_item_info.charges +end + +function max_stack(slot) + local container_item_info = T.temp-info.container_item(unpack(slot)) + return container_item_info and container_item_info.max_stack +end + +function locked(slot) + local container_item_info = T.temp-info.container_item(unpack(slot)) + return container_item_info and container_item_info.locked +end + +function find_item_slot(partial) + for slot in info.inventory() do + if matching_item(slot, partial) and not aux.eq(slot, state.target_slot) then + return slot + end + end +end + +function matching_item(slot, partial) + local item_info = T.temp-info.container_item(unpack(slot)) + return item_info and item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and (not partial or item_info.count < item_info.max_stack) +end + +function find_empty_slot() + for slot, type in info.inventory() do + if type == 1 and not GetContainerItemInfo(unpack(slot)) then + return slot + end + end +end + +function find_charge_item_slot() + for slot in info.inventory() do + if matching_item(slot) and charges(slot) == state.target_size then + return slot + end + end +end + +function move_item(from_slot, to_slot, amount, k) + if locked(from_slot) or locked(to_slot) then + return aux.wait(k) + end + + amount = min(max_stack(from_slot) - stack_size(to_slot), stack_size(from_slot), amount) + local expected_size = stack_size(to_slot) + amount + + ClearCursor() + SplitContainerItem(from_slot[1], from_slot[2], amount) + PickupContainerItem(unpack(to_slot)) + + return aux.when(function() return stack_size(to_slot) == expected_size end, k) +end + +function process() + if not state.target_slot or not matching_item(state.target_slot) then + state.target_slot = find_item_slot() + if not state.target_slot then + return stop() + end + end + if charges(state.target_slot) then + state.target_slot = find_charge_item_slot() + return stop() + end + if stack_size(state.target_slot) > state.target_size then + local slot = find_item_slot(true) or find_empty_slot() + if slot then + return move_item( + state.target_slot, + slot, + stack_size(state.target_slot) - state.target_size, + process + ) + end + elseif stack_size(state.target_slot) < state.target_size then + local slot = find_item_slot() + if slot then + return move_item( + slot, + state.target_slot, + state.target_size - stack_size(state.target_slot), + process + ) + end + end + return stop() +end + +function M.stop() + if state then + aux.kill_thread(state.thread_id) + local callback, slot = state.callback, state.target_slot + slot = slot and matching_item(slot) and slot or nil + state = nil + do (callback or pass)(slot) end + end +end + +function M.start(item_key, size, callback) + stop() + state = { + thread_id = aux.thread(process), + item_key = item_key, + target_size = size, + callback = callback, + } +end \ No newline at end of file diff --git a/aux-addon/core/tooltip.lua b/aux-addon/core/tooltip.lua new file mode 100644 index 0000000..c43365b --- /dev/null +++ b/aux-addon/core/tooltip.lua @@ -0,0 +1,216 @@ +module 'aux.core.tooltip' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local money = require 'aux.util.money' +local disenchant = require 'aux.core.disenchant' +local history = require 'aux.core.history' +local gui = require 'aux.gui' + +local UNKNOWN = GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE + +local game_tooltip_hooks, game_tooltip_money = {}, 0 + +function aux.handle.LOAD() + settings = aux.character_data.tooltip + do + local inside_hook = false + for name, f in game_tooltip_hooks do + local name, f = name, f + aux.hook(name, GameTooltip, T.vararg-function(arg) + game_tooltip_money = 0 + inside_hook = true + local tmp = T.list(aux.orig[GameTooltip][name](unpack(arg))) + inside_hook = false + f(unpack(arg)) + return T.unpack(tmp) + end) + end + SetTooltipMoney = SetTooltipMoney + _G.SetTooltipMoney = T.vararg-function(arg) + if inside_hook then + game_tooltip_money = arg[2] + else + return SetTooltipMoney(unpack(arg)) + end + end + end + local orig = SetItemRef + setglobal('SetItemRef', T.vararg-function(arg) + local name, _, quality = GetItemInfo(arg[1]) + local tmp = T.list(orig(unpack(arg))) + if not IsShiftKeyDown() and not IsControlKeyDown() and name then + local color_code = aux.select(4, GetItemQualityColor(quality)) + local link = color_code .. '|H' .. arg[1] .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE + extend_tooltip(ItemRefTooltip, link, 1) + end + return T.unpack(tmp) + end) +end + +function M.extend_tooltip(tooltip, link, quantity) + local item_id, suffix_id = info.parse_link(link) + quantity = IsShiftKeyDown() and quantity or 1 + local item_info = T.temp-info.item(item_id) + if item_info then + local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level) + if getn(distribution) > 0 then + if settings.disenchant_distribution then + tooltip:AddLine('Disenchants into:', aux.color.tooltip.disenchant.distribution()) + sort(distribution, function(a,b) return a.probability > b.probability end) + for _, event in ipairs(distribution) do + tooltip:AddLine(format(' %s%% %s (%s-%s)', event.probability * 100, info.display_name(event.item_id, true) or 'item:' .. event.item_id, event.min_quantity, event.max_quantity), aux.color.tooltip.disenchant.distribution()) + end + end + if settings.disenchant_value then + local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level) + tooltip:AddLine('Disenchant: ' .. (disenchant_value and money.to_string2(disenchant_value) or UNKNOWN), aux.color.tooltip.disenchant.value()) + end + end + end + if settings.merchant_buy then + local _, price, limited = info.merchant_info(item_id) + if price then + tooltip:AddLine('Vendor Buy ' .. (limited and '(limited): ' or ': ') .. money.to_string2(price * quantity), aux.color.tooltip.merchant()) + end + end + if settings.merchant_sell then + local price = info.merchant_info(item_id) + if price ~= 0 then + tooltip:AddLine('Vendor: ' .. (price and money.to_string2(price * quantity) or UNKNOWN), aux.color.tooltip.merchant()) + end + end + local auctionable = not item_info or info.auctionable(T.temp-info.tooltip('link', item_info.itemstring), item_info.quality) + local item_key = (item_id or 0) .. ':' .. (suffix_id or 0) + local value = history.value(item_key) + if auctionable then + if settings.value then + tooltip:AddLine('Value: ' .. (value and money.to_string2(value * quantity) or UNKNOWN), aux.color.tooltip.value()) + end + if settings.daily then + local market_value = history.market_value(item_key) + tooltip:AddLine('Today: ' .. (market_value and money.to_string2(market_value * quantity) .. ' (' .. gui.percentage_historical(aux.round(market_value / value * 100)) .. ')' or UNKNOWN), aux.color.tooltip.value()) + end + end + + if tooltip == GameTooltip and game_tooltip_money > 0 then + SetTooltipMoney(tooltip, game_tooltip_money) + end + tooltip:Show() +end + +function game_tooltip_hooks:SetHyperlink(itemstring) + local name, _, quality = GetItemInfo(itemstring) + if name then + local hex = aux.select(4, GetItemQualityColor(quality)) + local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE + extend_tooltip(GameTooltip, link, 1) + end +end + +function game_tooltip_hooks:SetAuctionItem(type, index) + local link = GetAuctionItemLink(type, index) + if link then + extend_tooltip(GameTooltip, link, aux.select(3, GetAuctionItemInfo(type, index))) + end +end + +function game_tooltip_hooks:SetLootItem(slot) + local link = GetLootSlotLink(slot) + if link then + extend_tooltip(GameTooltip, link, aux.select(3, GetLootSlotInfo(slot))) + end +end + +function game_tooltip_hooks:SetQuestItem(qtype, slot) + local link = GetQuestItemLink(qtype, slot) + if link then + extend_tooltip(GameTooltip, link, aux.select(3, GetQuestItemInfo(qtype, slot))) + end +end + +function game_tooltip_hooks:SetQuestLogItem(qtype, slot) + local link = GetQuestLogItemLink(qtype, slot) + if link then + extend_tooltip(GameTooltip, link, aux.select(3, GetQuestLogRewardInfo(slot))) + end +end + +function game_tooltip_hooks:SetBagItem(bag, slot) + local link = GetContainerItemLink(bag, slot) + if link then + extend_tooltip(GameTooltip, link, aux.select(2, GetContainerItemInfo(bag, slot))) + end +end + +function game_tooltip_hooks:SetInboxItem(index) + local name, _, quantity = GetInboxItem(index) + local id = name and info.item_id(name) + if id then + local _, itemstring, quality = GetItemInfo(id) + local hex = aux.select(4, GetItemQualityColor(tonumber(quality))) + local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE + extend_tooltip(GameTooltip, link, quantity) + end +end + +function game_tooltip_hooks:SetInventoryItem(unit, slot) + local link = GetInventoryItemLink(unit, slot) + if link then + extend_tooltip(GameTooltip, link, 1) + end +end + +function game_tooltip_hooks:SetMerchantItem(slot) + local link = GetMerchantItemLink(slot) + if link then + local quantity = aux.select(4, GetMerchantItemInfo(slot)) + extend_tooltip(GameTooltip, link, quantity) + end +end + +function game_tooltip_hooks:SetCraftItem(skill, slot) + local link, quantity + if slot then + link, quantity = GetCraftReagentItemLink(skill, slot), aux.select(3, GetCraftReagentInfo(skill, slot)) + else + link, quantity = GetCraftItemLink(skill), 1 + end + if link then + extend_tooltip(GameTooltip, link, quantity) + end +end + +function game_tooltip_hooks:SetCraftSpell(slot) + local link = GetCraftItemLink(slot) + if link then + extend_tooltip(GameTooltip, link, 1) + end +end + +function game_tooltip_hooks:SetTradeSkillItem(skill, slot) + local link, quantity + if slot then + link, quantity = GetTradeSkillReagentItemLink(skill, slot), aux.select(3, GetTradeSkillReagentInfo(skill, slot)) + else + link, quantity = GetTradeSkillItemLink(skill), 1 + end + if link then + extend_tooltip(GameTooltip, link, quantity) + end +end + +function game_tooltip_hooks:SetAuctionSellItem() + local name, _, quantity = GetAuctionSellItemInfo() + if name then + for slot in info.inventory() do + T.temp(slot) + local link = GetContainerItemLink(unpack(slot)) + if link and aux.select(5, info.parse_link(link)) == name then + extend_tooltip(GameTooltip, link, quantity) + return + end + end + end +end \ No newline at end of file diff --git a/aux-addon/frame.lua b/aux-addon/frame.lua new file mode 100644 index 0000000..819912f --- /dev/null +++ b/aux-addon/frame.lua @@ -0,0 +1,51 @@ +module 'aux' + +local gui = require 'aux.gui' + +function handle.LOAD() + for _, v in ipairs(tab_info) do + tabs:create_tab(v.name) + end +end + +do + local frame = CreateFrame('Frame', 'aux_frame', UIParent) + tinsert(UISpecialFrames, 'aux_frame') + gui.set_window_style(frame) + gui.set_size(frame, 768, 447) + frame:SetPoint('LEFT', 100, 0) + frame:SetToplevel(true) + frame:SetMovable(true) + frame:EnableMouse(true) + frame:SetClampedToScreen(true) + frame:CreateTitleRegion():SetAllPoints() + frame:SetScript('OnShow', function() PlaySound('AuctionWindowOpen') end) + frame:SetScript('OnHide', function() PlaySound('AuctionWindowClose'); CloseAuctionHouse() end) + frame.content = CreateFrame('Frame', nil, frame) + frame.content:SetPoint('TOPLEFT', 4, -80) + frame.content:SetPoint('BOTTOMRIGHT', -4, 35) + frame:Hide() + M.frame = frame +end +do + tabs = gui.tabs(frame, 'DOWN') + tabs._on_select = on_tab_click + function M.set_tab(id) tabs:select(id) end +end +do + local btn = gui.button(frame) + btn:SetPoint('BOTTOMRIGHT', -5, 5) + gui.set_size(btn, 60, 24) + btn:SetText('Close') + btn:SetScript('OnClick', function() frame:Hide() end) + close_button = btn +end +do + local btn = gui.button(frame, gui.font_size.small) + btn:SetPoint('RIGHT', close_button, 'LEFT' , -5, 0) + gui.set_size(btn, 60, 24) + btn:SetText(color.blizzard'Blizzard UI') + btn:SetScript('OnClick',function() + if AuctionFrame:IsVisible() then HideUIPanel(AuctionFrame) else ShowUIPanel(AuctionFrame) end + end) +end \ No newline at end of file diff --git a/aux-addon/gui/auction_listing.lua b/aux-addon/gui/auction_listing.lua new file mode 100644 index 0000000..6b40ccf --- /dev/null +++ b/aux-addon/gui/auction_listing.lua @@ -0,0 +1,1046 @@ +module 'aux.gui.auction_listing' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local sort_util = require 'aux.util.sort' +local money = require 'aux.util.money' +local history = require 'aux.core.history' +local gui = require 'aux.gui' +local tooltip = require 'aux.core.tooltip' + +local price_per_unit = false + +local HEAD_HEIGHT = 27 +local HEAD_SPACE = 2 + +local TIME_LEFT_STRINGS = { + aux.color.red'30m', -- Short + aux.color.orange'2h', -- Medium + aux.color.yellow'8h', -- Long + aux.color.blue'24h', -- Very Long +} + +function item_column_init(rt, cell) + local spacer = CreateFrame('Frame', nil, cell) + spacer:SetPoint('TOPLEFT', 0, 0) + spacer:SetHeight(rt.ROW_HEIGHT) + spacer:SetWidth(1) + cell.spacer = spacer + + local iconBtn = CreateFrame('Button', nil, cell) + iconBtn:SetPoint('TOPLEFT', spacer, 'TOPRIGHT') + iconBtn:SetHeight(rt.ROW_HEIGHT) + iconBtn:SetWidth(rt.ROW_HEIGHT) + iconBtn:SetScript('OnEnter', rt.OnIconEnter) + iconBtn:SetScript('OnLeave', rt.OnIconLeave) + iconBtn:SetScript('OnClick', rt.OnIconClick) + iconBtn:SetScript('OnDoubleClick', rt.OnIconDoubleClick) + local icon = iconBtn:CreateTexture(nil, 'ARTWORK') + icon:SetPoint('TOPLEFT', 2, -2) + icon:SetPoint('BOTTOMRIGHT', -2, 2) + icon:SetTexCoord(.08, .92, .08, .92) + + cell.iconBtn = iconBtn + cell.icon = icon + + cell.text:ClearAllPoints() + cell.text:SetPoint('TOPLEFT', iconBtn, 'TOPRIGHT', 2, 0) + cell.text:SetPoint('BOTTOMRIGHT', 0, 0) +end + +function item_column_fill(cell, record, _, _, _, indented) + cell.icon:SetTexture(record.texture) + if indented then + cell.spacer:SetWidth(10) + cell.icon:SetAlpha(.5) + cell.text:SetAlpha(.7) + else + cell.spacer:SetWidth(1) + cell.icon:SetAlpha(1) + cell.text:SetAlpha(1) + end + cell.text:SetText(gsub(record.link, '[%[%]]', '')) +end + +M.search_columns = { + { + title = 'Item', + width = .35, + init = item_column_init, + fill = item_column_fill, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.name, record_b.name, desc) + end, + }, + { + title = 'Lvl', + width = .035, + align = 'CENTER', + fill = function(cell, record) + local display_level = max(record.level, 1) + display_level = UnitLevel'player' < record.level and aux.color.red(display_level) or display_level + cell.text:SetText(display_level) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.level, record_b.level, desc) + end, + }, + { + title = 'Auctions', + width = .06, + align = 'CENTER', + fill = function(cell, record, count, own, expandable) + local numAuctionsText = expandable and aux.color.link(count) or count + if own > 0 then + numAuctionsText = numAuctionsText .. (' ' .. aux.color.yellow('(' .. own .. ')')) + end + cell.text:SetText(numAuctionsText) + end, + cmp = function(record_a, record_b, desc) + return sort_util.EQ +-- if sortKey == 'numAuctions' then +-- if a.children then +-- aVal = a.totalAuctions +-- bVal = b.totalAuctions +-- else +-- aVal = a.numAuctions +-- bVal = b.numAuctions +-- end +-- end + end, + }, + { + title = 'Stack\nSize', + width = .055, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(record.aux_quantity) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) + end, + }, + { + title = 'Time\nLeft', + width = .04, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.duration, record_b.duration, desc) + end, + }, + { + title = 'Seller', + width = .13, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(info.is_player(record.owner) and (aux.color.yellow(record.owner)) or (record.owner or '?')) + end, + cmp = function(record_a, record_b, desc) + if not record_a.owner and not record_b.owner then + return sort_util.EQ + elseif not record_a.owner then + return sort_util.GT + elseif not record_b.owner then + return sort_util.LT + else + return sort_util.compare(record_a.owner, record_b.owner, desc) + end + end, + }, + { + title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price_color + if record.high_bidder then + price_color = aux.color.green + elseif record.high_bid ~= 0 then + price_color = aux.color.orange + end + local price + if record.high_bidder then + price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid + else + price = price_per_unit and ceil(record.unit_bid_price) or record.bid_price + end + cell.text:SetText(money.to_string(price, true, false, price_color)) + end, + cmp = function(record_a, record_b, desc) + local price_a + if record_a.high_bidder then + price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid + else + price_a = price_per_unit and record_a.unit_bid_price or record_a.bid_price + end + local price_b + if record_b.high_bidder then + price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid + else + price_b = price_per_unit and record_b.unit_bid_price or record_b.bid_price + end + if record_a.high_bidder and not record_b.high_bidder then + return sort_util.GT + elseif record_b.high_bidder and not record_a.high_bidder then + return sort_util.LT + end + if price_a == price_b then + if record_a.high_bid == 0 and record_b.high_bid ~= 0 then + return sort_util.GT + elseif record_b.high_bid == 0 and record_a.high_bid ~= 0 then + return sort_util.LT + end + end + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price + cell.text:SetText(price > 0 and money.to_string(price, true) or '---') + end, + cmp = function(record_a, record_b, desc) + local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price + local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price + price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) + price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) + + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = '% Hist.\nValue', + width = .08, + align = 'CENTER', + fill = function(cell, record) + local pct, bidPct = record_percentage(record) + cell.text:SetText((pct or bidPct) and gui.percentage_historical(pct or bidPct, not pct) or '?') + end, + cmp = function(record_a, record_b, desc) + local pct_a = record_percentage(record_a) or (desc and -aux.huge or aux.huge) + local pct_b = record_percentage(record_b) or (desc and -aux.huge or aux.huge) + return sort_util.compare(pct_a, pct_b, desc) + end, + }, +} + +M.auctions_columns = { + { + title = 'Item', + width = .35, + init = item_column_init, + fill = item_column_fill, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.name, record_b.name, desc) + end, + }, + { + title = 'Lvl', + width = .035, + align = 'CENTER', + fill = function(cell, record) + local display_level = max(record.level, 1) + display_level = UnitLevel('player') < record.level and aux.color.red(display_level) or display_level + cell.text:SetText(display_level) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.level, record_b.level, desc) + end, + }, + { + title = 'Auctions', + width = .06, + align = 'CENTER', + fill = function(cell, record, count, own, expandable) + local numAuctionsText = expandable and aux.color.link(count) or count + cell.text:SetText(numAuctionsText) + end, + cmp = function(record_a, record_b, desc) + return sort_util.EQ + -- if sortKey == 'numAuctions' then + -- if a.children then + -- aVal = a.totalAuctions + -- bVal = b.totalAuctions + -- else + -- aVal = a.numAuctions + -- bVal = b.numAuctions + -- end + -- end + end, + }, + { + title = 'Stack\nSize', + width = .055, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(record.aux_quantity) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) + end, + }, + { + title = 'Time\nLeft', + width = .04, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.duration, record_b.duration, desc) + end, + }, + { + title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price + if record.high_bidder then + price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid + else + price = price_per_unit and ceil(record.start_price / record.aux_quantity) or record.start_price + end + cell.text:SetText(money.to_string(price, true)) + end, + cmp = function(record_a, record_b, desc) + local price_a + if record_a.high_bidder then + price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid + else + price_a = price_per_unit and record_a.start_price / record_b.aux_quantity or record_a.start_price + end + local price_b + if record_b.high_bidder then + price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid + else + price_b = price_per_unit and record_b.start_price / record_b.aux_quantity or record_b.start_price + end + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price + cell.text:SetText(price > 0 and money.to_string(price, true) or '---') + end, + cmp = function(record_a, record_b, desc) + local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price + local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price + price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) + price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) + + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = 'High Bidder', + width = .21, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(record.high_bidder or aux.color.red 'No Bids') + end, + cmp = function(record_a, record_b, desc) + if not record_a.high_bidder and not record_b.high_bidder then + return sort_util.EQ + elseif not record_a.high_bidder then + return sort_util.GT + elseif not record_b.high_bidder then + return sort_util.LT + else + return sort_util.compare(record_a.high_bidder, record_b.high_bidder, desc) + end + end, + }, +} + +M.bids_columns = { + { + title = 'Item', + width = .35, + init = item_column_init, + fill = item_column_fill, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.name, record_b.name, desc) + end, + }, + { + title = 'Auctions', + width = .06, + align = 'CENTER', + fill = function(cell, record, count, own, expandable) + local numAuctionsText = expandable and aux.color.link(count) or count + cell.text:SetText(numAuctionsText) + end, + cmp = function(record_a, record_b, desc) + return sort_util.EQ + -- if sortKey == 'numAuctions' then + -- if a.children then + -- aVal = a.totalAuctions + -- bVal = b.totalAuctions + -- else + -- aVal = a.numAuctions + -- bVal = b.numAuctions + -- end + -- end + end, + }, + { + title = 'Stack\nSize', + width = .055, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(record.aux_quantity) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) + end, + }, + { + title = 'Time\nLeft', + width = .04, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.duration, record_b.duration, desc) + end, + }, + { + title = 'Seller', + width = .13, + align = 'CENTER', + fill = function(cell, record) + cell.text:SetText(info.is_player(record.owner) and (aux.color.yellow(record.owner)) or (record.owner or '?')) + end, + cmp = function(record_a, record_b, desc) + if not record_a.owner and not record_b.owner then + return sort_util.EQ + elseif not record_a.owner then + return sort_util.GT + elseif not record_b.owner then + return sort_util.LT + else + return sort_util.compare(record_a.owner, record_b.owner, desc) + end + end, + }, + { + title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price + if record.high_bidder then + price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid + else + price = price_per_unit and ceil(record.unit_bid_price) or record.bid_price + end + cell.text:SetText(money.to_string(price)) + end, + cmp = function(record_a, record_b, desc) + local price_a + if record_a.high_bidder then + price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid + else + price_a = price_per_unit and record_a.unit_bid_price or record_a.bid_price + end + local price_b + if record_b.high_bidder then + price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid + else + price_b = price_per_unit and record_b.unit_bid_price or record_b.bid_price + end + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, + width = .125, + align = 'RIGHT', + isPrice = true, + fill = function(cell, record) + local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price + cell.text:SetText(price > 0 and money.to_string(price, true) or '---') + end, + cmp = function(record_a, record_b, desc) + local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price + local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price + price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) + price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) + + return sort_util.compare(price_a, price_b, desc) + end, + }, + { + title = 'Status', + width = .115, + align = 'CENTER', + fill = function(cell, record) + local status + if record.high_bidder then + status = aux.color.yellow'High Bidder' + else + status = aux.color.red'Outbid' + end + cell.text:SetText(status) + end, + cmp = function(record_a, record_b, desc) + return sort_util.compare(record_a.high_bidder and 1 or 0, record_b.high_bidder and 1 or 0, desc) + end, + }, +} + +function record_percentage(record) + if not record then return end + + local historical_value = history.value(record.item_key) or 0 + if historical_value > 0 then + if record.unit_buyout_price > 0 then + return aux.round(100 * record.unit_buyout_price / historical_value) + end + return nil, aux.round(100 * record.unit_bid_price / historical_value) + end +end + +function M.time_left(code) + return TIME_LEFT_STRINGS[code] +end + +local methods = { + + ResizeColumns = function(self) + local weight = 0 + for _, cell in self.headCells do + weight = weight + cell.info.width + end + weight = (self.contentFrame:GetRight() - self.contentFrame:GetLeft()) / weight + for i, cell in self.headCells do + local width = cell.info.width * weight + cell:SetWidth(width) + for _, row in self.rows do + row.cells[i]:SetWidth(width) + end + end + end, + + OnHeadColumnClick = function() + local button = arg1 + local rt = this.rt + + if button == 'RightButton' and rt.headCells[this.columnIndex].info.isPrice then + price_per_unit = not price_per_unit + for _, cell in rt.headCells do + if cell.info.isPrice then + cell:SetText(cell.info.title[price_per_unit and 1 or 2]) + end + end + rt:SetSort() + return + end + + local descending = false + if getn(rt.sorts) > 0 and rt.sorts[1].index == this.columnIndex then + descending = not rt.sorts[1].descending + end + rt:SetSort((descending and -1 or 1) * this.columnIndex) + end, + + OnIconEnter = function() + local rt = this:GetParent().row.rt + local row = this:GetParent().row + if row.record then + GameTooltip:SetOwner(this, 'ANCHOR_RIGHT') + info.load_tooltip(GameTooltip, row.record.tooltip) + tooltip.extend_tooltip(GameTooltip, row.record.link, row.record.aux_quantity) + info.set_shopping_tooltip(row.record.slot) + end + end, + + OnIconLeave = function() + GameTooltip:Hide() + end, + + OnEnter = function() + local rt = this.rt + if rt.expanded[this.expandKey] then + GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) + GameTooltip:AddLine('Double-click to collapse this item and show only the cheapest auction.', 1, 1, 1, true) + GameTooltip:Show() + elseif this.expandable then + GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) + GameTooltip:AddLine('Double-click to expand this item and show all the auctions.', 1, 1, 1, true) + GameTooltip:Show() + end + + this.highlight:Show() + end, + + OnLeave = function() + GameTooltip:Hide() + if not this.rt.selected or this.rt.selected.search_signature ~= this.record.search_signature then + this.highlight:Hide() + end + end, + + OnClick = function() + local button = arg1 + if IsControlKeyDown() then + DressUpItemLink(this.record.link) + elseif IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then + ChatFrameEditBox:Insert(this.record.link) + else + local selection = this.rt:GetSelection() + if not selection or selection.record ~= this.record then + this.rt:SetSelectedRecord(this.record) + end + do (this.rt.handlers.OnClick or pass)(this, button) end + end + end, + + OnDoubleClick = function() + local rt = this.rt + local expand = not rt.expanded[this.expandKey] + + rt.expanded[this.expandKey] = expand + rt:UpdateRowInfo() + rt:UpdateRows() + if not this.indented then + rt:SetSelectedRecord(this.record) + end + end, + + UpdateRowInfo = function(self) + for _, v in ipairs(self.rowInfo) do + if type(v) == 'table' then + for _, child in v.children do + T.release(child) + end + T.release(v.children) + T.release(v) + end + end + T.wipe(self.rowInfo) + self.rowInfo.numDisplayRows = 0 + self.isSorted = nil + self:SetSelectedRecord(nil, true) + + local records = self.records + + local single_item = aux.all(records, function(record) return record.item_key == records[1].item_key end) + + sort(records, function(a, b) return a.search_signature < b.search_signature or a.search_signature == b.search_signature and tostring(a) < tostring(b) end) + + for i = 1, getn(records) do + local record = records[i] + local prevRecord = records[i - 1] + if prevRecord and record.search_signature == prevRecord.search_signature then + -- it's an identical auction to the previous row so increment the number of auctions + self.rowInfo[getn(self.rowInfo)].children[getn(self.rowInfo[getn(self.rowInfo)].children)].count = self.rowInfo[getn(self.rowInfo)].children[getn(self.rowInfo[getn(self.rowInfo)].children)].count + 1 + elseif not single_item and prevRecord and record.item_key == prevRecord.item_key then + -- it's the same base item as the previous row so insert a new auction + tinsert(self.rowInfo[getn(self.rowInfo)].children, T.map('count', 1, 'record', record)) + if self.expanded[self.rowInfo[getn(self.rowInfo)].expandKey] then + self.rowInfo.numDisplayRows = self.rowInfo.numDisplayRows + 1 + end + else + -- it's a different base item from the previous row + tinsert(self.rowInfo, T.map('item_key', record.item_key, 'expandKey', record.item_key, 'children', T.list(T.map('count', 1, 'record', record)))) + self.rowInfo.numDisplayRows = self.rowInfo.numDisplayRows + 1 + end + end + + for _, v in ipairs(self.rowInfo) do + local totalAuctions, totalPlayerAuctions = 0, 0 + for _, childInfo in v.children do + totalAuctions = totalAuctions + childInfo.count + if info.is_player(childInfo.record.owner) then + totalPlayerAuctions = totalPlayerAuctions + childInfo.count + end + end + v.totalAuctions = totalAuctions + v.totalPlayerAuctions = totalPlayerAuctions + end + end, + + UpdateRows = function(self) + if self.rowInfo.numDisplayRows > getn(self.rows) then + self.contentFrame:SetPoint('BOTTOMRIGHT', -15, 0) + else + self.contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) + end + self:ResizeColumns() + + FauxScrollFrame_Update(self.scrollFrame, self.rowInfo.numDisplayRows, getn(self.rows), self.ROW_HEIGHT) + + local maxOffset = max(self.rowInfo.numDisplayRows - getn(self.rows), 0) + if FauxScrollFrame_GetOffset(self.scrollFrame) > maxOffset then + FauxScrollFrame_SetOffset(self.scrollFrame, maxOffset) + end + + for _, cell in self.headCells do + local tex = cell:GetNormalTexture() + tex:SetTexture[[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]] + tex:SetTexCoord(.017, 1, .083, .909) + tex:SetAlpha(.5) + end + + if getn(self.sorts) > 0 then + local last_sort = self.sorts[1] + if last_sort.descending then + self.headCells[last_sort.index]:GetNormalTexture():SetTexture(.8, .6, 1, .8) + else + self.headCells[last_sort.index]:GetNormalTexture():SetTexture(.6, .8, 1, .8) + end + end + + if not self.isSorted then + local function sort_helper(a, b) + + local record_a, record_b + if a.children then + record_a = a.children[1].record + record_b = b.children[1].record + else + record_a = a.record + record_b = b.record + end + + for _, sort in self.sorts do + local ordering = self.columns[sort.index].cmp and self.columns[sort.index].cmp(record_a, record_b, sort.descending) or sort_util.EQ + + if ordering == sort_util.LT then + return true + elseif ordering == sort_util.GT then + return false + end + end + + return tostring(a) < tostring(b) + end + + for _, v in ipairs(self.rowInfo) do + sort(v.children, sort_helper) + end + sort(self.rowInfo, sort_helper) + self.isSorted = true + end + + for _, row in self.rows do + row:Hide() + end + local rowIndex = 1 - FauxScrollFrame_GetOffset(self.scrollFrame) + for _, v in ipairs(self.rowInfo) do + if self.expanded[v.expandKey] then + for j, childInfo in ipairs(v.children) do + self:SetRowInfo(rowIndex, childInfo.record, childInfo.count, 0, j > 1, false, v.expandKey) + rowIndex = rowIndex + 1 + end + else + self:SetRowInfo(rowIndex, v.children[1].record, v.totalAuctions, getn(v.children) > 1 and v.totalPlayerAuctions or 0, false, getn(v.children) > 1, v.expandKey) + rowIndex = rowIndex + 1 + end + end + end, + + SetRowInfo = function(self, rowIndex, record, totalAuctions, totalPlayerAuctions, indented, expandable, expandKey) + if rowIndex <= 0 or rowIndex > getn(self.rows) then return end + local row = self.rows[rowIndex] + row:Show() + if self.selected and record.search_signature == self.selected.search_signature then + row.highlight:Show() + else + row.highlight:Hide() + end + + row.record = record + row.expandable = expandable + row.indented = indented + row.expandKey = expandKey + + for i, column in self.columns do + column.fill(row.cells[i], record, totalAuctions, totalPlayerAuctions, expandable, indented) + end + end, + + SetSelectedRecord = function(self, record, silent) + self.selected = record + local selectedData = self:GetSelection() + self.selected = selectedData and self.selected or nil + + for _, row in self.rows do + if self.selected and row.record and row.record.search_signature == self.selected.search_signature then + row.highlight:Show() + else + row.highlight:Hide() + end + end + + if not silent and self.handlers.OnSelectionChanged then + self.handlers.OnSelectionChanged(self, selectedData or nil) + end + end, + + Reset = function(self) + T.wipe(self.expanded) + self:UpdateRowInfo() + self:UpdateRows() + self:SetSelectedRecord() + end, + + SetDatabase = function(self, database) + if database and database ~= self.records then + self.records = database + end + + local prevSelectedIndex + if self.selected then + for i, row in self.rows do + if row:IsVisible() and row.record == self.selected then + prevSelectedIndex = i + end + end + end + + self:UpdateRowInfo() + self:UpdateRows() + + if not self.selected and prevSelectedIndex then + -- try to select the same row + local row = self.rows[prevSelectedIndex] + if row and row:IsVisible() and row.record then + self:SetSelectedRecord(row.record) + end + if not self.selected then + -- select the first row + row = self.rows[1] + if row and row:IsVisible() and row.record then + self:SetSelectedRecord(row.record) + end + end + end + end, + + RemoveAuctionRecord = function(self, record) + local index = aux.key(self.records, record) + if index then + tremove(self.records, index) + end + self:SetDatabase() + end, + + ContainsRecord = function(self, record) + if aux.key(self.records, record) then + return true + end + end, + + SetSort = T.vararg-function(arg) + local self = tremove(arg, 1) + for _, v in ipairs(arg) do + for i, sort in self.sorts do + if sort.index == abs(v) then + tremove(self.sorts, i) + break + end + end + tinsert(self.sorts, 1, {index=abs(v), descending=v < 0}) + end + + self.isSorted = nil + self:UpdateRows() + end, + + SetHandler = function(self, event, handler) + self.handlers[event] = handler + end, + + GetSelection = function(self) + if not self.selected then return end + local selectedData + for _, v in ipairs(self.rowInfo) do + for _, childInfo in v.children do + if childInfo.record.search_signature == self.selected.search_signature then + selectedData = childInfo + break + end + end + end + return selectedData + end, +} + +function M.new(parent, rows, columns) + local rt = CreateFrame('Frame', nil, parent) + rt.columns = columns + rt.ROW_HEIGHT = (parent:GetHeight() - HEAD_HEIGHT - HEAD_SPACE) / rows + rt.expanded = {} + rt.handlers = {} + rt.sorts = {} + rt.records = {} + rt.rowInfo = {numDisplayRows=0} + + for name, func in methods do + rt[name] = func + end + + rt:SetScript('OnShow', function() + for _, cell in this.headCells do + if cell.info.isPrice then + cell:SetText(cell.info.title[price_per_unit and 1 or 2]) + end + end + end) + + local contentFrame = CreateFrame('Frame', nil, rt) + contentFrame:SetPoint('TOPLEFT', 0, 0) + contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) + rt.contentFrame = contentFrame + + local scrollFrame = CreateFrame('ScrollFrame', gui.unique_name(), rt, 'FauxScrollFrameTemplate') + scrollFrame:SetScript('OnVerticalScroll', function() + FauxScrollFrame_OnVerticalScroll(rt.ROW_HEIGHT, function() rt:UpdateRows() end) + end) + scrollFrame:SetAllPoints(contentFrame) + rt.scrollFrame = scrollFrame + FauxScrollFrame_Update(rt.scrollFrame, 0, rows, rt.ROW_HEIGHT) + + local scrollBar = _G[scrollFrame:GetName() .. 'ScrollBar'] + scrollBar:ClearAllPoints() + scrollBar:SetPoint('TOPRIGHT', rt, -4, -HEAD_HEIGHT) + scrollBar:SetPoint('BOTTOMRIGHT', rt, -4, 4) + scrollBar:SetWidth(10) + local thumbTex = scrollBar:GetThumbTexture() + thumbTex:SetPoint('CENTER', 0, 0) + thumbTex:SetTexture(aux.color.content.background()) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scrollBar:GetWidth()) + _G[scrollBar:GetName() .. 'ScrollUpButton']:Hide() + _G[scrollBar:GetName() .. 'ScrollDownButton']:Hide() + + rt.headCells = {} + for i, column in ipairs(rt.columns) do + local cell = CreateFrame('Button', nil, rt.contentFrame) + cell:SetHeight(HEAD_HEIGHT) + if i == 1 then + cell:SetPoint('TOPLEFT', 0, 0) + else + cell:SetPoint('TOPLEFT', rt.headCells[i - 1], 'TOPRIGHT') + end + cell.info = column + cell.rt = rt + cell.columnIndex = i + cell:RegisterForClicks('LeftButtonUp', 'RightButtonUp') + + cell:SetScript('OnClick', rt.OnHeadColumnClick) + + local text = cell:CreateFontString() + text:SetJustifyH('CENTER') + text:SetFont(gui.font, 12) + text:SetTextColor(aux.color.label.enabled()) + cell:SetFontString(text) + if not column.isPrice then cell:SetText(column.title or '') end -- TODO + text:SetAllPoints() + + local tex = cell:CreateTexture() + tex:SetAllPoints() + tex:SetTexture([[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]]) + tex:SetTexCoord(.017, 1, .083, .909) + tex:SetAlpha(.5) + cell:SetNormalTexture(tex) + + local tex = cell:CreateTexture() + tex:SetAllPoints() + tex:SetTexture([[Interface\Buttons\UI-Listbox-Highlight]]) + tex:SetTexCoord(.025, .957, .087, .931) + tex:SetAlpha(.2) + cell:SetHighlightTexture(tex) + + tinsert(rt.headCells, cell) + end + + rt.rows = {} + for i = 1, rows do + local row = CreateFrame('Button', nil, rt.contentFrame) + row.rt = rt + row:SetHeight(rt.ROW_HEIGHT) + row:RegisterForClicks('LeftButtonUp', 'RightButtonUp') + row:SetScript('OnEnter', rt.OnEnter) + row:SetScript('OnLeave', rt.OnLeave) + row:SetScript('OnClick', rt.OnClick) + row:SetScript('OnDoubleClick', rt.OnDoubleClick) + if i == 1 then + row:SetPoint('TOPLEFT', 0, -(HEAD_HEIGHT + HEAD_SPACE)) + row:SetPoint('TOPRIGHT', 0, -(HEAD_HEIGHT + HEAD_SPACE)) + else + row:SetPoint('TOPLEFT', 0, -(HEAD_HEIGHT + HEAD_SPACE + (i - 1) * rt.ROW_HEIGHT)) + row:SetPoint('TOPRIGHT', 0, -(HEAD_HEIGHT + HEAD_SPACE + (i - 1) * rt.ROW_HEIGHT)) + end + local highlight = row:CreateTexture() + highlight:SetAllPoints() + highlight:SetTexture(1, .9, 0, .5) + highlight:Hide() + row.highlight = highlight + + row.cells = {} + for j, column in ipairs(rt.columns) do + local cell = CreateFrame('Frame', nil, row) + local text = cell:CreateFontString() + cell.text = text + text:SetFont(gui.font, min(14, rt.ROW_HEIGHT)) + text:SetJustifyH(column.align or 'LEFT') + text:SetJustifyV('CENTER') + text:SetPoint('TOPLEFT', 1, -1) + text:SetPoint('BOTTOMRIGHT', -1, 1) + cell:SetHeight(rt.ROW_HEIGHT) + cell.rt = rt + cell.row = row + + if j == 1 then + cell:SetPoint('TOPLEFT', 0, 0) + else + cell:SetPoint('TOPLEFT', row.cells[j - 1], 'TOPRIGHT') + end + + if mod(j, 2) == 1 then + local tex = cell:CreateTexture() + tex:SetAllPoints() + tex:SetTexture(.3, .3, .3, .2) + end + + if column.init then + column.init(rt, cell) + end + + tinsert(row.cells, cell) + end + + if mod(i, 2) == 0 then + local tex = row:CreateTexture() + tex:SetAllPoints() + tex:SetTexture(.3, .3, .3, .3) + end + + tinsert(rt.rows, row) + end + + rt:SetAllPoints() + rt:ResizeColumns() + return rt +end \ No newline at end of file diff --git a/aux-addon/gui/core.lua b/aux-addon/gui/core.lua new file mode 100644 index 0000000..fdc0964 --- /dev/null +++ b/aux-addon/gui/core.lua @@ -0,0 +1,569 @@ +module 'aux.gui' + +local T = require 'T' +local aux = require 'aux' + +M.font = [[Fonts\ARIALN.TTF]] + +M.font_size = aux.immutable-{ + small = 13, + medium = 15, + large = 18, +} + +--function aux.handle.LOAD() +-- do +-- local blizzard_backdrop, aux_background, aux_border +-- +-- aux_border = DropDownList1:CreateTexture() +-- aux_border:SetTexture(1, 1, 1, .02) +-- aux_border:SetPoint('TOPLEFT', DropDownList1Backdrop, 'TOPLEFT', -2, 2) +-- aux_border:SetPoint('BOTTOMRIGHT', DropDownList1Backdrop, 'BOTTOMRIGHT', 1.5, -1.5) +-- aux_border:SetBlendMode('ADD') +-- aux_background = DropDownList1:CreateTexture(nil, 'OVERLAY') +-- aux_background:SetTexture(aux.color.content.background()) +-- aux_background:SetAllPoints(DropDownList1Backdrop) +-- blizzard_backdrop = DropDownList1Backdrop:GetBackdrop() +-- aux.hook('ToggleDropDownMenu', function(...) +-- T.temp(arg) +-- local ret = T.temp-T.list(aux.orig.ToggleDropDownMenu(unpack(arg))) +-- local dropdown = _G[arg[4] or ''] or this:GetParent() +-- if strfind(dropdown:GetName() or '', '^aux.frame%d+$') then +-- set_aux_dropdown_style(dropdown) +-- else +-- set_blizzard_dropdown_style() +-- end +-- return unpack(ret) +-- end) +-- +-- function set_aux_dropdown_style(dropdown) +-- DropDownList1Backdrop:SetBackdrop(T.empty) +-- aux_border:Show() +-- aux_background:Show() +-- DropDownList1:SetWidth(dropdown:GetWidth() * .9) +-- DropDownList1:SetHeight(DropDownList1:GetHeight() - 10) +-- DropDownList1:ClearAllPoints() +-- DropDownList1:SetPoint('TOPLEFT', dropdown, 'BOTTOMLEFT', -2, -2) +-- for i = 1, UIDROPDOWNMENU_MAXBUTTONS do +-- local button = _G['DropDownList1Button' .. i] +-- button:SetPoint('TOPLEFT', 0, -((button:GetID() - 1) * UIDROPDOWNMENU_BUTTON_HEIGHT) - 7) +-- button:SetPoint('TOPRIGHT', 0, -((button:GetID() - 1) * UIDROPDOWNMENU_BUTTON_HEIGHT) - 7) +-- local text = button:GetFontString() +-- text:SetFont(font, 14) +-- text:SetPoint('TOPLEFT', 18, 0) +-- text:SetPoint('BOTTOMRIGHT', -8, 0) +-- local highlight = _G['DropDownList1Button' .. i .. 'Highlight'] +-- highlight:ClearAllPoints() +-- highlight:SetDrawLayer('OVERLAY') +-- highlight:SetHeight(14) +-- highlight:SetPoint('LEFT', 5, 0) +-- highlight:SetPoint('RIGHT', -3, 0) +-- local check = _G['DropDownList1Button' .. i .. 'Check'] +-- check:SetWidth(16) +-- check:SetHeight(16) +-- check:SetPoint('LEFT', 3, -1) +-- end +-- end +-- +-- function set_blizzard_dropdown_style() +-- DropDownList1Backdrop:SetBackdrop(blizzard_backdrop) +-- aux_border:Hide() +-- aux_background:Hide() +-- for i = 1, UIDROPDOWNMENU_MAXBUTTONS do +-- local button = _G['DropDownList1Button' .. i] +-- local text = button:GetFontString() +-- text:SetFont([[Fonts\FRIZQT__.ttf]], 10) +-- text:SetShadowOffset(1, -1) +-- local highlight = _G['DropDownList1Button' .. i .. 'Highlight'] +-- highlight:SetAllPoints() +-- highlight:SetDrawLayer('BACKGROUND') +-- local check = _G['DropDownList1Button' .. i .. 'Check'] +-- check:SetWidth(24) +-- check:SetHeight(24) +-- check:SetPoint('LEFT', 0, 0) +-- end +-- end +-- end +--end + +do + local id = 1 + function M.unique_name() + id = id + 1 + return 'aux.frame' .. id + end +end + +do + local menu = CreateFrame('Frame', unique_name(), UIParent, 'UIDropDownMenuTemplate') + M.menu = T.vararg-function(arg) + HideDropDownMenu(1) + UIDropDownMenu_Initialize(menu, function() + for i = 1, getn(arg), 2 do + UIDropDownMenu_AddButton(T.map('text', arg[i], 'notCheckable', true, 'func', arg[i + 1])) + end + end, 'MENU') + ToggleDropDownMenu(1, nil, menu, 'cursor') + end +end + +function M.set_size(frame, width, height) + frame:SetWidth(width) + frame:SetHeight(height or width) +end + +function M.set_frame_style(frame, backdrop_color, border_color, left, right, top, bottom) + frame:SetBackdrop{bgFile=[[Interface\Buttons\WHITE8X8]], edgeFile=[[Interface\Buttons\WHITE8X8]], edgeSize=1.5, tile=true, insets={left=left, right=right, top=top, bottom=bottom}} + frame:SetBackdropColor(backdrop_color()) + frame:SetBackdropBorderColor(border_color()) +end + +function M.set_window_style(frame, left, right, top, bottom) + set_frame_style(frame, aux.color.window.background, aux.color.window.border, left, right, top, bottom) +end + +function M.set_panel_style(frame, left, right, top, bottom) + set_frame_style(frame, aux.color.panel.background, aux.color.panel.border, left, right, top, bottom) +end + +function M.set_content_style(frame, left, right, top, bottom) + set_frame_style(frame, aux.color.content.background, aux.color.content.border, left, right, top, bottom) +end + +function M.panel(parent) + local panel = CreateFrame('Frame', nil, parent) + set_panel_style(panel) + return panel +end + +function M.checkbutton(parent, text_height) + local button = button(parent, text_height) + button.state = false + button:SetBackdropColor(aux.color.state.disabled()) + function button:SetChecked(state) + if state then + self:SetBackdropColor(aux.color.state.enabled()) + self.state = true + else + self:SetBackdropColor(aux.color.state.disabled()) + self.state = false + end + end + function button:GetChecked() + return self.state + end + return button +end + +function M.button(parent, text_height) + text_height = text_height or font_size.large + local button = CreateFrame('Button', nil, parent) + set_size(button, 80, 24) + set_content_style(button) + local highlight = button:CreateTexture(nil, 'HIGHLIGHT') + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + button.highlight = highlight + do + local label = button:CreateFontString() + label:SetFont(font, text_height) + label:SetAllPoints(button) + label:SetJustifyH('CENTER') + label:SetJustifyV('CENTER') + label:SetTextColor(aux.color.text.enabled()) + button:SetFontString(label) + end + button.default_Enable = button.Enable + function button:Enable() + if self:IsEnabled() == 1 then return end + self:GetFontString():SetTextColor(aux.color.text.enabled()) + return self:default_Enable() + end + button.default_Disable = button.Disable + function button:Disable() + if self:IsEnabled() == 0 then return end + self:GetFontString():SetTextColor(aux.color.text.disabled()) + return self:default_Disable() + end + + return button +end + +do + local mt = {__index=T.acquire()} + function mt.__index:create_tab(text) + local id = getn(self._tabs) + 1 + + local tab = CreateFrame('Button', unique_name(), self._frame) + tab.id = id + tab.group = self + tab:SetHeight(24) + set_panel_style(tab) + local dock = tab:CreateTexture(nil, 'OVERLAY') + dock:SetHeight(3) + if self._orientation == 'UP' then + dock:SetPoint('BOTTOMLEFT', 1, -1) + dock:SetPoint('BOTTOMRIGHT', -1, -1) + elseif self._orientation == 'DOWN' then + dock:SetPoint('TOPLEFT', 1, 1) + dock:SetPoint('TOPRIGHT', -1, 1) + end + dock:SetTexture(aux.color.panel.background()) + tab.dock = dock + local highlight = tab:CreateTexture(nil, 'HIGHLIGHT') + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + tab.highlight = highlight + + tab.text = tab:CreateFontString() + tab.text:SetAllPoints() + tab.text:SetJustifyH('CENTER') + tab.text:SetJustifyV('CENTER') + tab.text:SetFont(font, font_size.large) + tab:SetFontString(tab.text) + + tab:SetText(text) + + tab:SetScript('OnClick', function() + if this.id ~= this.group.selected then + PlaySound('igCharacterInfoTab') + this.group:select(this.id) + end + end) + + if getn(self._tabs) == 0 then + if self._orientation == 'UP' then + tab:SetPoint('BOTTOMLEFT', self._frame, 'TOPLEFT', 4, -1) + elseif self._orientation == 'DOWN' then + tab:SetPoint('TOPLEFT', self._frame, 'BOTTOMLEFT', 4, 1) + end + else + if self._orientation == 'UP' then + tab:SetPoint('BOTTOMLEFT', self._tabs[getn(self._tabs)], 'BOTTOMRIGHT', 4, 0) + elseif self._orientation == 'DOWN' then + tab:SetPoint('TOPLEFT', self._tabs[getn(self._tabs)], 'TOPRIGHT', 4, 0) + end + end + + tab:SetWidth(tab:GetFontString():GetStringWidth() + 14) + + tinsert(self._tabs, tab) + end + function mt.__index:select(id) + self._selected = id + self:update() + do (self._on_select or pass)(id) end + end + function mt.__index:update() + for _, tab in self._tabs do + if tab.group._selected == tab.id then + tab.text:SetTextColor(aux.color.label.enabled()) + tab:Disable() + tab:SetBackdropColor(aux.color.panel.background()) + tab.dock:Show() + tab:SetHeight(29) + else + tab.text:SetTextColor(aux.color.text.enabled()) + tab:Enable() + tab:SetBackdropColor(aux.color.content.background()) + tab.dock:Hide() + tab:SetHeight(24) + end + end + end + function M.tabs(parent, orientation) + local self = { + _frame = parent, + _orientation = orientation, + _tabs = T.acquire(), + } + return setmetatable(self, mt) + end +end + +function M.editbox(parent) + local editbox = CreateFrame('EditBox', nil, parent) + editbox:SetAutoFocus(false) + editbox:SetTextInsets(1.5, 1.5, 3, 3) + editbox:SetMaxLetters(nil) + editbox:SetHeight(24) + editbox:SetTextColor(0, 0, 0, 0) + set_content_style(editbox) + editbox:SetScript('OnEscapePressed', function() + this:ClearFocus() + do (this.escape or pass)() end + end) + editbox:SetScript('OnEnterPressed', function() (this.enter or pass)() end) + editbox:SetScript('OnEditFocusGained', function() + if this.block_focus then + this.block_focus = false + this:ClearFocus() + return + end + this.overlay:Hide() + this:SetTextColor(aux.color.text.enabled()) + this.focused = true + this:HighlightText() + do (this.focus_gain or pass)() end + end) + editbox:SetScript('OnEditFocusLost', function() + this.overlay:Show() + this:SetTextColor(0, 0, 0, 0) + this.focused = false + this:HighlightText(0, 0) + this:SetScript('OnUpdate', nil) + do (this.focus_loss or pass)() end + end) + editbox:SetScript('OnTextChanged', function() + this.overlay:SetText(this.formatter and this.formatter(this:GetText()) or this:GetText()) + do (this.change or pass)() end + end) + editbox:SetScript('OnChar', function() (this.char or pass)() end) + do + local last_click = T.map('t', 0) + editbox:SetScript('OnMouseDown', function() + if arg1 == 'RightButton' then + this:SetText('') + this:ClearFocus() + this.block_focus = true + else + local x, y = GetCursorPosition() + -- local offset = x - editbox:GetLeft()*editbox:GetEffectiveScale() TODO use a fontstring to measure getstringwidth for structural highlighting + -- or use an overlay with itemlinks + if GetTime() - last_click.t < .5 and x == last_click.x and y == last_click.y then + aux.thread(function() editbox:HighlightText() end) + end + T.wipe(last_click) + last_click.t = GetTime() + last_click.x = x + last_click.y = y + end + end) + end + function editbox:SetAlignment(alignment) + self:SetJustifyH(alignment) + self.overlay:SetJustifyH(alignment) + end + function editbox:SetFontSize(size) + self:SetFont(font, size) + self.overlay:SetFont(font, size) + end + local overlay = label(editbox) + overlay:SetPoint('LEFT', 1.5, 0) + overlay:SetPoint('RIGHT', -1.5, 0) + overlay:SetTextColor(aux.color.text.enabled()) + editbox.overlay = overlay + editbox:SetAlignment('LEFT') + editbox:SetFontSize(font_size.medium) + return editbox +end + +do + local function update_bar() + if this:GetValue() < 1 then + this:SetAlpha(1 - (sin(GetTime() * 180) + 1) / 4) + else + this:SetAlpha(1) + end + end + function M.status_bar(parent) + local self = CreateFrame('Frame', nil, parent) + local level = parent:GetFrameLevel() + self:SetFrameLevel(level + 1) + do + local status_bar = CreateFrame('StatusBar', nil, self, 'TextStatusBar') + status_bar:SetOrientation('HORIZONTAL') + status_bar:SetMinMaxValues(0, 1) + status_bar:SetAllPoints() + status_bar:SetStatusBarTexture([[Interface\Buttons\WHITE8X8]]) + status_bar:SetStatusBarColor(.42, .42, .42, .7) + status_bar:SetFrameLevel(level + 2) + status_bar:SetScript('OnUpdate', update_bar) + self.secondary_status_bar = status_bar + end + do + local status_bar = CreateFrame('StatusBar', nil, self, 'TextStatusBar') + status_bar:SetOrientation('HORIZONTAL') + status_bar:SetMinMaxValues(0, 1) + status_bar:SetAllPoints() + status_bar:SetStatusBarTexture([[Interface\Buttons\WHITE8X8]]) + status_bar:SetStatusBarColor(.19, .22, .33, .9) + status_bar:SetFrameLevel(level + 3) + status_bar:SetScript('OnUpdate', update_bar) + self.primary_status_bar = status_bar + end + do + local text_frame = CreateFrame('Frame', nil, self) + text_frame:SetFrameLevel(level + 4) + text_frame:SetAllPoints(self) + local text = label(text_frame, font_size.medium) + text:SetTextColor(aux.color.text.enabled()) + text:SetPoint('CENTER', 0, 0) + self.text = text + end + function self:update_status(primary_status, secondary_status) + if primary_status then + self.primary_status_bar:SetValue(primary_status) + end + if secondary_status then + self.secondary_status_bar:SetValue(secondary_status) + end + end + function self:set_text(text) + self.text:SetText(text) + end + return self + end +end + +function M.item(parent) + local item = CreateFrame('Frame', nil, parent) + set_size(item, 260, 40) + local btn = CreateFrame('CheckButton', unique_name(), item, 'ActionButtonTemplate') + item.button = btn + btn:SetPoint('LEFT', 2, .5) + btn:SetHighlightTexture(nil) + btn:RegisterForClicks() + item.texture = _G[btn:GetName() .. 'Icon'] + item.texture:SetTexCoord(.06, .94, .06, .94) + item.name = label(btn, font_size.medium) + item.name:SetJustifyH('LEFT') + item.name:SetPoint('LEFT', btn, 'RIGHT', 10, 0) + item.name:SetPoint('RIGHT', item, 'RIGHT', -10, .5) + item.count = _G[btn:GetName() .. 'Count'] + item.count:SetTextHeight(17) + return item +end + +function M.label(parent, size) + local label = parent:CreateFontString() + label:SetFont(font, size or font_size.small) + label:SetTextColor(aux.color.label.enabled()) + return label +end + +function M.horizontal_line(parent, y_offset, inverted_color) + local texture = parent:CreateTexture() + texture:SetPoint('TOPLEFT', parent, 'TOPLEFT', 2, y_offset) + texture:SetPoint('TOPRIGHT', parent, 'TOPRIGHT', -2, y_offset) + texture:SetHeight(2) + if inverted_color then + texture:SetTexture(aux.color.panel.background()) + else + texture:SetTexture(aux.color.content.background()) + end + return texture +end + +function M.vertical_line(parent, x_offset, top_offset, bottom_offset, inverted_color) + local texture = parent:CreateTexture() + texture:SetPoint('TOPLEFT', parent, 'TOPLEFT', x_offset, top_offset or -2) + texture:SetPoint('BOTTOMLEFT', parent, 'BOTTOMLEFT', x_offset, bottom_offset or 2) + texture:SetWidth(2) + if inverted_color then + texture:SetTexture(aux.color.panel.background()) + else + texture:SetTexture(aux.color.content.background()) + end + return texture +end + +function M.dropdown(parent) + local dropdown = CreateFrame('Frame', unique_name(), parent, 'UIDropDownMenuTemplate') + set_content_style(dropdown, 0, 0, 4, 4) + + _G[dropdown:GetName() .. 'Left']:Hide() + _G[dropdown:GetName() .. 'Middle']:Hide() + _G[dropdown:GetName() .. 'Right']:Hide() + + local button = _G[dropdown:GetName() .. 'Button'] + button:ClearAllPoints() + button:SetScale(.9) + button:SetPoint('RIGHT', dropdown, 0, 0) + dropdown.button = button + + local text = _G[dropdown:GetName() .. 'Text'] + text:ClearAllPoints() + text:SetPoint('RIGHT', button, 'LEFT', -2, 0) + text:SetPoint('LEFT', 8, 0) + text:SetFont(font, font_size.medium) + text:SetShadowColor(0, 0, 0, 0) + + return dropdown +end + +function M.slider(parent) + + local slider = CreateFrame('Slider', nil, parent) + slider:SetOrientation('HORIZONTAL') + slider:SetHeight(6) + slider:SetHitRectInsets(0, 0, -12, -12) + slider:SetValue(0) + + set_panel_style(slider) + local thumb_texture = slider:CreateTexture(nil, 'ARTWORK') + thumb_texture:SetPoint('CENTER', 0, 0) + thumb_texture:SetTexture(aux.color.content.background()) + thumb_texture:SetHeight(18) + thumb_texture:SetWidth(8) + set_size(thumb_texture, 8, 18) + slider:SetThumbTexture(thumb_texture) + + local label = slider:CreateFontString(nil, 'OVERLAY') + label:SetPoint('BOTTOMLEFT', slider, 'TOPLEFT', -3, 8) + label:SetPoint('BOTTOMRIGHT', slider, 'TOPRIGHT', 6, 8) + label:SetJustifyH('LEFT') + label:SetHeight(13) + label:SetFont(font, font_size.small) + label:SetTextColor(aux.color.label.enabled()) + + local editbox = editbox(slider) + editbox:SetPoint('LEFT', slider, 'RIGHT', 5, 0) + set_size(editbox, 45, 18) + editbox:SetAlignment('CENTER') + editbox:SetFontSize(17) + + slider.label = label + slider.editbox = editbox + return slider +end + +function M.checkbox(parent) + local checkbox = CreateFrame('CheckButton', nil, parent, 'UICheckButtonTemplate') + checkbox:SetWidth(16) + checkbox:SetHeight(16) + set_content_style(checkbox) + checkbox:SetNormalTexture(nil) + checkbox:SetPushedTexture(nil) + checkbox:GetHighlightTexture():SetAllPoints() + checkbox:GetHighlightTexture():SetTexture(1, 1, 1, .2) + checkbox:GetCheckedTexture():SetTexCoord(.12, .88, .12, .88) + checkbox:GetHighlightTexture('BLEND') + return checkbox +end + +do + local editbox = CreateFrame'EditBox' + editbox:SetAutoFocus(false) + function M.clear_focus() + editbox:SetFocus() + editbox:ClearFocus() + end +end + +function M.percentage_historical(pct, bid) + local text = (pct > 10000 and '>10000' or pct) .. '%' + if bid then + return aux.color.gray(text) + elseif pct < 50 then + return aux.color.blue(text) + elseif pct < 80 then + return aux.color.green(text) + elseif pct < 110 then + return aux.color.yellow(text) + elseif pct < 135 then + return aux.color.orange(text) + else + return aux.color.red(text) + end +end \ No newline at end of file diff --git a/aux-addon/gui/item_listing.lua b/aux-addon/gui/item_listing.lua new file mode 100644 index 0000000..4b22728 --- /dev/null +++ b/aux-addon/gui/item_listing.lua @@ -0,0 +1,129 @@ +module 'aux.gui.item_listing' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local gui = require 'aux.gui' + +local ROW_HEIGHT = 39 + +function M:render() + + if getn(self.item_records or T.empty) > getn(self.rows) then + self.content_frame:SetPoint('BOTTOMRIGHT', -15, 0) + else + self.content_frame:SetPoint('BOTTOMRIGHT', 0, 0) + end + + FauxScrollFrame_Update(self.scroll_frame, getn(self.item_records), getn(self.rows), ROW_HEIGHT) + local offset = FauxScrollFrame_GetOffset(self.scroll_frame) + + local rows = self.rows + + for i, row in rows do + local item_record = self.item_records[i + offset] + + if item_record then + row.item_record = item_record + if self.selected and self.selected(item_record) or row.mouseover then + row.highlight:Show() + elseif not row.mouse_over then + row.highlight:Hide() + end + row.item.texture:SetTexture(item_record.texture) + row.item.name:SetText('[' .. item_record.name .. ']') + local color = ITEM_QUALITY_COLORS[item_record.quality] + row.item.name:SetTextColor(color.r, color.g, color.b) + if item_record.aux_quantity > 1 then + row.item.count:SetText(item_record.aux_quantity) + else + row.item.count:SetText() + end + row:Show() + else + row:Hide() + end + end +end + +function M.new(parent, on_click, selected) + local content_frame = CreateFrame('Frame', nil, parent) + content_frame:SetAllPoints() + + local scroll_frame = CreateFrame('ScrollFrame', gui.unique_name(), parent, 'FauxScrollFrameTemplate') + scroll_frame:SetScript('OnVerticalScroll', function() + FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, function() render(this.item_listing) end) + end) + scroll_frame:SetPoint('TOPLEFT', content_frame, 'TOPLEFT', 0, 29) + scroll_frame:SetPoint('BOTTOMRIGHT', content_frame, 'BOTTOMRIGHT', 0, 0) + + local scroll_bar = _G[scroll_frame:GetName() .. 'ScrollBar'] + scroll_bar:ClearAllPoints() + scroll_bar:SetPoint('TOPRIGHT', parent, -4, 2) + scroll_bar:SetPoint('BOTTOMRIGHT', parent, -4, 4) + scroll_bar:SetWidth(10) + local thumbTex = scroll_bar:GetThumbTexture() + thumbTex:SetPoint('CENTER', 0, 0) + thumbTex:SetTexture(aux.color.content.background()) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scroll_bar:GetWidth()) + _G[scroll_bar:GetName() .. 'ScrollUpButton']:Hide() + _G[scroll_bar:GetName() .. 'ScrollDownButton']:Hide() + + local rows = T.acquire() + local row_index = 1 + local max_height = content_frame:GetHeight() + local total_height = 0 + while total_height + ROW_HEIGHT < max_height do + local row = CreateFrame('Frame', nil, content_frame) + row:SetHeight(ROW_HEIGHT) + row:SetPoint('TOPLEFT', content_frame, 0, -((row_index - 1) * ROW_HEIGHT)) + row:SetPoint('TOPRIGHT', content_frame, 0, -((row_index - 1) * ROW_HEIGHT)) + row:EnableMouse(true) + row:SetScript('OnMouseUp', on_click) + row:SetScript('OnEnter', function() + row.mouseover = true + row.highlight:Show() + end) + row:SetScript('OnLeave', function() + row.mouseover = false + if not selected(row.item_record) then + row.highlight:Hide() + end + end) + + row.item = gui.item(row) + row.item:SetScale(.9) + row.item:SetPoint('LEFT', 2.5, 0) + row.item:SetPoint('RIGHT', -2.5, 0) + row.item.button:SetScript('OnEnter', function() + info.set_tooltip(row.item_record.itemstring, this, 'ANCHOR_RIGHT') + end) + row.item.button:SetScript('OnLeave', function() GameTooltip:Hide() end) + + local highlight = row:CreateTexture() + highlight:SetAllPoints(row) + highlight:Hide() + highlight:SetTexture(1, .9, 0, .4) + row.highlight = highlight + + rows[row_index] = row + row_index = row_index + 1 + total_height = total_height + ROW_HEIGHT + end + + local item_listing = { + selected = selected, + content_frame = content_frame, + scroll_frame = scroll_frame, + rows = rows, + } + scroll_frame.item_listing = item_listing + + return item_listing +end + +function M.populate(item_listing, item_records) + item_listing.item_records = item_records + render(item_listing) +end diff --git a/aux-addon/gui/listing.lua b/aux-addon/gui/listing.lua new file mode 100644 index 0000000..544b234 --- /dev/null +++ b/aux-addon/gui/listing.lua @@ -0,0 +1,300 @@ +module 'aux.gui.listing' + +local T = require 'T' +local aux = require 'aux' +local gui = require 'aux.gui' + +local ROW_HEIGHT = 15 +local ROW_TEXT_SIZE = 14 +local HEAD_HEIGHT = 27 +local HEAD_SPACE = 2 +local DEFAULT_COL_INFO = {{width=1}} + +local handlers = { + OnEnter = function() + this.mouseover = true + if not this.data then return end + if not this.st.highlightDisabled then + this.highlight:Show() + end + + local handler = this.st.handlers.OnEnter + if handler then + handler(this.st, this.data, this) + end + end, + + OnLeave = function() + this.mouseover = false + if not this.data then return end + if not (this.st.selected and this.st.selected(this.data)) then + this.highlight:Hide() + end + + local handler = this.st.handlers.OnLeave + if handler then + handler(this.st, this.data, this) + end + end, + + OnClick = function() + if not this.data then return end + local handler = this.st.handlers.OnClick + if handler then + handler(this.st, this.data, this, arg1) + end + end, + + OnDoubleClick = function() + if not this.data then return end + + local handler = this.st.handlers.OnDoubleClick + if handler then + handler(this.st, this.data, this, arg1) + end + end, +} + +local methods = { + Update = function(self) + if getn(self.colInfo) > 1 or self.colInfo[1].name then + self.headHeight = HEAD_HEIGHT + else + self.headHeight = 0 + end + + if getn(self.rowData or T.empty) > self.numRows then + self.contentFrame:SetPoint('BOTTOMRIGHT', -15, 0) + else + self.contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) + end + + local width = self.contentFrame:GetRight() - self.contentFrame:GetLeft() + + while getn(self.headCols) < getn(self.colInfo) do + self:AddColumn() + end + + for i, col in self.headCols do + if self.colInfo[i] then + col:Show() + col:SetWidth(self.colInfo[i].width * width) + col:SetHeight(self.headHeight) + col.text:SetText(self.colInfo[i].name or '') + col.text:SetJustifyH(self.colInfo[i].headAlign or 'CENTER') + else + col:Hide() + end + end + + while getn(self.rows) < self.numRows do + self:AddRow() + end + + for i, row in self.rows do + if i > self.numRows then + row.data = nil + row:Hide() + else + row:Show() + while getn(row.cols) < getn(self.colInfo) do + self:AddCell(i) + end + for j, col in row.cols do + if self.headCols[j] and self.colInfo[j] then + col:Show() + col:SetWidth(self.colInfo[j].width * width) + col.text:SetJustifyH(self.colInfo[j].align or 'LEFT') + else + col:Hide() + end + end + end + end + + if not self.rowData then return end + FauxScrollFrame_Update(self.scrollFrame, getn(self.rowData), self.numRows, ROW_HEIGHT) + local offset = FauxScrollFrame_GetOffset(self.scrollFrame) + self.offset = offset + + for i = 1, self.numRows do + local row = self.rows[i] + row.data = nil + if i > getn(self.rowData) then + row:Hide() + else + row:Show() + local data = self.rowData[i + offset] + if not data then break end + row.data = data + + if row.mouseover or self.selected and self.selected(data) then + row.highlight:Show() + else + row.highlight:Hide() + end + + for j, col in row.cols do + if self.colInfo[j] then + local colData = data.cols[j] + if type(colData.value) == 'function' then + col.text:SetText(colData.value(unpack(colData.args))) + else + col.text:SetText(colData.value) + end + end + end + end + end + end, + + SetData = function(self, rowData) + for _, row in self.rowData or T.empty do + for _, col in row.cols do T.release(col) end + T.release(row.cols) + T.release(row) + end + self.rowData = rowData + self.updateSort = true + self:Update() + end, + + AddColumn = function(self) + local colNum = getn(self.headCols) + 1 + local col = CreateFrame('Frame', nil, self.contentFrame) + if colNum == 1 then + col:SetPoint('TOPLEFT', 0, 0) + else + col:SetPoint('TOPLEFT', self.headCols[colNum - 1], 'TOPRIGHT') + end + col.st = self + col.colNum = colNum + + local text = col:CreateFontString() + text:SetAllPoints() + text:SetFont(gui.font, 12) + text:SetTextColor(aux.color.label.enabled()) + col.text = text + + local tex = col:CreateTexture() + tex:SetAllPoints() + tex:SetTexture([[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]]) + tex:SetTexCoord(.017, 1, .083, .909) + tex:SetAlpha(.5) + + tinsert(self.headCols, col) + + for i, row in self.rows do + while getn(row.cols) < getn(self.headCols) do + self:AddCell(i) + end + end + end, + + AddCell = function(self, rowNum) + local row = self.rows[rowNum] + local colNum = getn(row.cols) + 1 + local cell = CreateFrame('Frame', nil, row) + local text = cell:CreateFontString() + cell.text = text + text:SetFont(gui.font, ROW_TEXT_SIZE) + text:SetJustifyV('CENTER') + text:SetPoint('TOPLEFT', 1, -1) + text:SetPoint('BOTTOMRIGHT', -1, 1) + cell:SetHeight(ROW_HEIGHT) + cell.st = self + + if colNum == 1 then + cell:SetPoint('TOPLEFT', 0, 0) + else + cell:SetPoint('TOPLEFT', row.cols[colNum - 1], 'TOPRIGHT') + end + tinsert(row.cols, cell) + end, + + AddRow = function(self) + local row = CreateFrame('Button', nil, self.contentFrame) + row:SetHeight(ROW_HEIGHT) + row:RegisterForClicks('LeftButtonUp', 'RightButtonUp') + for name, func in handlers do + row:SetScript(name, func) + end + local rowNum = getn(self.rows) + 1 + if rowNum == 1 then + row:SetPoint('TOPLEFT', 0, -(self.headHeight + HEAD_SPACE)) + row:SetPoint('TOPRIGHT', 0, -(self.headHeight + HEAD_SPACE)) + else + row:SetPoint('TOPLEFT', 0, -(self.headHeight + HEAD_SPACE + (rowNum - 1) * ROW_HEIGHT)) + row:SetPoint('TOPRIGHT', 0, -(self.headHeight + HEAD_SPACE + (rowNum - 1) * ROW_HEIGHT)) + end + local highlight = row:CreateTexture() + highlight:SetAllPoints() + highlight:SetTexture(1, .9, 0, .4) + highlight:Hide() + row.highlight = highlight + row.st = self + + row.cols = T.acquire() + self.rows[rowNum] = row + for _ = 1, getn(self.colInfo) do + self:AddCell(rowNum) + end + end, + + SetSelection = function(self, f) + self.selected = f + end, + + SetHandler = function(self, event, handler) + self.handlers[event] = handler + end, + + SetColInfo = function(self, colInfo) + colInfo = colInfo or DEFAULT_COL_INFO + self.colInfo = colInfo + self:Update() + end, +} + +function M.new(parent) + local st = CreateFrame('Frame', gui.unique_name(), parent) + st:SetAllPoints() + + st.numRows = max(floor((parent:GetHeight() - HEAD_HEIGHT - HEAD_SPACE) / ROW_HEIGHT), 0) + + local contentFrame = CreateFrame('Frame', nil, st) + contentFrame:SetPoint('TOPLEFT', 0, 0) + contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) + st.contentFrame = contentFrame + + local scrollFrame = CreateFrame('ScrollFrame', st:GetName() .. 'ScrollFrame', st, 'FauxScrollFrameTemplate') + scrollFrame:SetScript('OnVerticalScroll', function() + FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, function() st:Update() end) + end) + scrollFrame:SetAllPoints(contentFrame) + st.scrollFrame = scrollFrame + + local scroll_bar = _G[scrollFrame:GetName() .. 'ScrollBar'] + scroll_bar:ClearAllPoints() + scroll_bar:SetPoint('TOPRIGHT', st, -4, -HEAD_HEIGHT) + scroll_bar:SetPoint('BOTTOMRIGHT', st, -4, 4) + scroll_bar:SetWidth(10) + local thumbTex = scroll_bar:GetThumbTexture() + thumbTex:SetPoint('CENTER', 0, 0) + thumbTex:SetTexture(aux.color.content.background()) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scroll_bar:GetWidth()) + _G[scroll_bar:GetName() .. 'ScrollUpButton']:Hide() + _G[scroll_bar:GetName() .. 'ScrollDownButton']:Hide() + + for name, func in methods do + st[name] = func + end + + st.headCols = T.acquire() + st.rows = T.acquire() + st.handlers = T.acquire() + st.colInfo = DEFAULT_COL_INFO + + return st +end \ No newline at end of file diff --git a/aux-addon/libs/T.lua b/aux-addon/libs/T.lua new file mode 100644 index 0000000..738b613 --- /dev/null +++ b/aux-addon/libs/T.lua @@ -0,0 +1,149 @@ +if module 'T' then return end + +local next, getn, setn, tremove, setmetatable = next, getn, table.setn, tremove, setmetatable + +local wipe, acquire, release +local pool, pool_size, overflow_pool, auto_release = {}, 0, setmetatable({}, {__mode='k'}), {} + +function wipe(t) + setmetatable(t, nil) + for k in t do + t[k] = nil + end + t.reset, t.reset = nil, 1 + setn(t, 0) +end +M.wipe = wipe + +CreateFrame'Frame':SetScript('OnUpdate', function() + for t in auto_release do + release(t) + end + wipe(auto_release) +end) + +function acquire() + if pool_size > 0 then + pool_size = pool_size - 1 + return pool[pool_size + 1] + end + local t = next(overflow_pool) + if t then + overflow_pool[t] = nil + return t + end + return {} +end +M.acquire = acquire + +function release(t) + wipe(t) + auto_release[t] = nil + if pool_size < 50 then + pool_size = pool_size + 1 + pool[pool_size] = t + else + overflow_pool[t] = true + end +end +M.release = release + +do + local function f(_, v) + if v then + auto_release[v] = true + return v + end + end + M.temp = setmetatable({}, {__metatable=false, __newindex=pass, __call=f, __sub=f}) +end +do + local function f(_, v) + if v then + auto_release[v] = nil + return v + end + end + M.static = setmetatable({}, {__metatable=false, __newindex=pass, __call=f, __sub=f}) +end + +do + local function unpack(t) + if getn(t) > 0 then + return tremove(t, 1), unpack(t) + else + release(t) + end + end + M.unpack = unpack +end + +M.empty = setmetatable({}, {__metatable=false, __newindex=pass}) + +local vararg +do + local MAXPARAMS = 100 + + local code = [[ + local f, setn, acquire, auto_release = f, setn, acquire, auto_release + return function( + ]] + for i = 1, MAXPARAMS - 1 do + code = code .. format('a%d,', i) + end + code = code .. [[ + overflow) + if overflow ~= nil then error("T-vararg overflow.", 2) end + local n = 0 + repeat + ]] + for i = MAXPARAMS - 1, 1, -1 do + code = code .. format('if a%1$d ~= nil then n = %1$d; break end;', i) + end + code = code .. [[ + until true + local t = acquire() + auto_release[t] = true + setn(t, n) + repeat + ]] + for i = 1, MAXPARAMS - 1 do + code = code .. format('if %1$d > n then break end; t[%1$d] = a%1$d;', i) + end + code = code .. [[ + until true + return f(t) + end + ]] + + function vararg(f) + local chunk = loadstring(code) + setfenv(chunk, {f=f, setn=setn, acquire=acquire, auto_release=auto_release}) + return chunk() + end + M.vararg = setmetatable({}, { + __metatable = false, + __sub = function(_, v) + return vararg(v) + end, + }) +end + +M.list = vararg(function(arg) + auto_release[arg] = nil + return arg +end) +M.set = vararg(function(arg) + local t = acquire() + for _, v in arg do + t[v] = true + end + return t +end) +M.map = vararg(function(arg) + local t = acquire() + for i = 1, getn(arg), 2 do + t[arg[i]] = arg[i + 1] + end + return t +end) \ No newline at end of file diff --git a/aux-addon/libs/package.lua b/aux-addon/libs/package.lua new file mode 100644 index 0000000..1339a82 --- /dev/null +++ b/aux-addon/libs/package.lua @@ -0,0 +1,37 @@ +if module then return end +local _G, setfenv, setmetatable = getfenv(0), setfenv, setmetatable +local environments, interfaces = {}, {} + +local function pass() end + +local environment_mt = {__index=_G} + +local function create_module(name) + local environment = setmetatable({_G=_G, pass=pass}, environment_mt) + local exports = {} + environment.M = setmetatable({}, { + __metatable=false, + __newindex=function(_, k, v) + environment[k], exports[k] = v, v + end, + }) + environment._M = environment + environments[name] = environment + interfaces[name] = setmetatable({}, {__metatable=false, __index=exports, __newindex=pass}) +end + +function module(name) + local defined = not not environments[name] + if not defined then + create_module(name) + end + setfenv(2, environments[name]) + return defined +end + +function require(name) + if not interfaces[name] then + create_module(name) + end + return interfaces[name] +end \ No newline at end of file diff --git a/aux-addon/tabs/auctions/core.lua b/aux-addon/tabs/auctions/core.lua new file mode 100644 index 0000000..ec54346 --- /dev/null +++ b/aux-addon/tabs/auctions/core.lua @@ -0,0 +1,101 @@ +module 'aux.tabs.auctions' + +local T = require 'T' +local aux = require 'aux' +local scan_util = require 'aux.util.scan' +local scan = require 'aux.core.scan' + +local tab = aux.tab 'Auctions' + +auction_records = T.acquire() + +function tab.OPEN() + frame:Show() + scan_auctions() +end + +function tab.CLOSE() + frame:Hide() +end + +function update_listing() + listing:SetDatabase(auction_records) +end + +function M.scan_auctions() + + status_bar:update_status(0, 0) + status_bar:set_text('Scanning auctions...') + + T.wipe(auction_records) + update_listing() + scan.start{ + type = 'owner', + queries = {{blizzard_query = T.acquire()}}, + on_page_loaded = function(page, total_pages) + status_bar:update_status(page / total_pages, 0) + status_bar:set_text(format('Scanning (Page %d / %d)', page, total_pages)) + end, + on_auction = function(auction_record) + tinsert(auction_records, auction_record) + end, + on_complete = function() + status_bar:update_status(1, 1) + status_bar:set_text('Scan complete') + update_listing() + end, + on_abort = function() + status_bar:update_status(1, 1) + status_bar:set_text('Scan aborted') + end, + } +end + +do + local scan_id = 0 + local IDLE, SEARCHING, FOUND = aux.enum(3) + local state = IDLE + local found_index + + function find_auction(record) + if not listing:ContainsRecord(record) then return end + + scan.abort(scan_id) + state = SEARCHING + scan_id = scan_util.find( + record, + status_bar, + function() state = IDLE end, + function() state = IDLE; listing:RemoveAuctionRecord(record) end, + function(index) + state = FOUND + found_index = index + + cancel_button:SetScript('OnClick', function() + if scan_util.test(record, index) and listing:ContainsRecord(record) then + aux.cancel_auction(index, function() listing:RemoveAuctionRecord(record) end) + end + end) + cancel_button:Enable() + end + ) + end + + function on_update() + if state == IDLE or state == SEARCHING then + cancel_button:Disable() + end + + if state == SEARCHING then return end + + local selection = listing:GetSelection() + if not selection then + state = IDLE + elseif selection and state == IDLE then + find_auction(selection.record) + elseif state == FOUND and not scan_util.test(selection.record, found_index) then + cancel_button:Disable() + if not aux.cancel_in_progress() then state = IDLE end + end + end +end \ No newline at end of file diff --git a/aux-addon/tabs/auctions/frame.lua b/aux-addon/tabs/auctions/frame.lua new file mode 100644 index 0000000..689663e --- /dev/null +++ b/aux-addon/tabs/auctions/frame.lua @@ -0,0 +1,62 @@ +module 'aux.tabs.auctions' + +local aux = require 'aux' +local info = require 'aux.util.info' +local gui = require 'aux.gui' +local auction_listing = require 'aux.gui.auction_listing' +local search_tab = require 'aux.tabs.search' + +frame = CreateFrame('Frame', nil, aux.frame) +frame:SetAllPoints() +frame:SetScript('OnUpdate', on_update) +frame:Hide() + +frame.listing = gui.panel(frame) +frame.listing:SetPoint('TOP', frame, 'TOP', 0, -8) +frame.listing:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) +frame.listing:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) + +listing = auction_listing.new(frame.listing, 20, auction_listing.auctions_columns) +listing:SetSort(1, 2, 3, 4, 5, 6, 7, 8) +listing:Reset() +listing:SetHandler('OnClick', function(row, button) + if IsAltKeyDown() then + if listing:GetSelection().record == row.record then + if IsAltKeyDown() and listing:GetSelection().record == row.record then + cancel_button:Click() + end + end + elseif button == 'RightButton' then + aux.set_tab(1) + search_tab.set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') + search_tab.execute(nil, false) + end +end) +listing:SetHandler('OnSelectionChanged', function(rt, datum) + if not datum then return end + find_auction(datum.record) +end) + +do + status_bar = gui.status_bar(frame) + status_bar:SetWidth(265) + status_bar:SetHeight(25) + status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) + status_bar:update_status(1, 1) + status_bar:set_text('') +end +do + local btn = gui.button(frame) + btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) + btn:SetText('Cancel') + btn:Disable() + cancel_button = btn +end +do + local btn = gui.button(frame) + btn:SetPoint('TOPLEFT', cancel_button, 'TOPRIGHT', 5, 0) + btn:SetText('Refresh') + btn:SetScript('OnClick', function() + scan_auctions() + end) +end \ No newline at end of file diff --git a/aux-addon/tabs/bids/core.lua b/aux-addon/tabs/bids/core.lua new file mode 100644 index 0000000..b52cc1b --- /dev/null +++ b/aux-addon/tabs/bids/core.lua @@ -0,0 +1,125 @@ +module 'aux.tabs.bids' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local scan_util = require 'aux.util.scan' +local scan = require 'aux.core.scan' + +local tab = aux.tab 'Bids' + +auction_records = {} + +function tab.OPEN() + frame:Show() + scan_bids() +end + +function tab.CLOSE() + frame:Hide() +end + +function update_listing() + listing:SetDatabase(auction_records) +end + +function M.scan_bids() + + status_bar:update_status(0, 0) + status_bar:set_text('Scanning auctions...') + + T.wipe(auction_records) + update_listing() + scan.start{ + type = 'bidder', + queries = T.list(T.map('blizzard_query', T.acquire())), + on_page_loaded = function(page, total_pages) + status_bar:update_status(page / total_pages, 0) + status_bar:set_text(format('Scanning (Page %d / %d)', page, total_pages)) + end, + on_auction = function(auction_record) + tinsert(auction_records, auction_record) + end, + on_complete = function() + status_bar:update_status(1, 1) + status_bar:set_text('Scan complete') + update_listing() + end, + on_abort = function() + status_bar:update_status(1, 1) + status_bar:set_text('Scan aborted') + end, + } +end + +do + local scan_id = 0 + local IDLE, SEARCHING, FOUND = aux.enum(3) + local state = IDLE + local found_index + + function find_auction(record) + if not listing:ContainsRecord(record) then return end + + scan.abort(scan_id) + state = SEARCHING + scan_id = scan_util.find( + record, + status_bar, + function() state = IDLE end, + function() + state = IDLE + listing:RemoveAuctionRecord(record) + end, + function(index) + state = FOUND + found_index = index + + if not record.high_bidder then + bid_button:SetScript('OnClick', function() + if scan_util.test(record, index) and listing:ContainsRecord(record) then + aux.place_bid('bidder', index, record.bid_price, record.bid_price < record.buyout_price and function() + info.bid_update(record) + listing:SetDatabase() + end or function() listing:RemoveAuctionRecord(record) end) + end + end) + bid_button:Enable() + else + bid_button:Disable() + end + + if record.buyout_price > 0 then + buyout_button:SetScript('OnClick', function() + if scan_util.test(record, index) and listing:ContainsRecord(record) then + aux.place_bid('bidder', index, record.buyout_price, function() listing:RemoveAuctionRecord(record) end) + end + end) + buyout_button:Enable() + else + buyout_button:Disable() + end + end + ) + end + + function on_update() + if state == IDLE or state == SEARCHING then + buyout_button:Disable() + bid_button:Disable() + end + + if state == SEARCHING then return end + + local selection = listing:GetSelection() + if not selection then + state = IDLE + elseif selection and state == IDLE then + find_auction(selection.record) + elseif state == FOUND and not scan_util.test(selection.record, found_index) then + buyout_button:Disable() + bid_button:Disable() + if not aux.bid_in_progress() then state = IDLE end + end + end +end \ No newline at end of file diff --git a/aux-addon/tabs/bids/frame.lua b/aux-addon/tabs/bids/frame.lua new file mode 100644 index 0000000..c7f3b47 --- /dev/null +++ b/aux-addon/tabs/bids/frame.lua @@ -0,0 +1,71 @@ +module 'aux.tabs.bids' + +local aux = require 'aux' +local info = require 'aux.util.info' +local gui = require 'aux.gui' +local auction_listing = require 'aux.gui.auction_listing' +local search_tab = require 'aux.tabs.search' + +frame = CreateFrame('Frame', nil, aux.frame) +frame:SetAllPoints() +frame:SetScript('OnUpdate', on_update) +frame:Hide() + +frame.listing = gui.panel(frame) +frame.listing:SetPoint('TOP', frame, 'TOP', 0, -8) +frame.listing:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) +frame.listing:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) + +listing = auction_listing.new(frame.listing, 20, auction_listing.bids_columns) +listing:SetSort(1, 2, 3, 4, 5, 6, 7, 8) +listing:Reset() +listing:SetHandler('OnClick', function(row, button) + if IsAltKeyDown() then + if listing:GetSelection().record == row.record then + if button == 'LeftButton' then + buyout_button:Click() + elseif button == 'RightButton' then + bid_button:Click() + end + end + elseif button == 'RightButton' then + aux.set_tab(1) + search_tab.set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') + search_tab.execute(nil, false) + end +end) +listing:SetHandler('OnSelectionChanged', function(rt, datum) + if not datum then return end + find_auction(datum.record) +end) + +do + status_bar = gui.status_bar(frame) + status_bar:SetWidth(265) + status_bar:SetHeight(25) + status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) + status_bar:update_status(1, 0) + status_bar:set_text('') +end +do + local btn = gui.button(frame) + btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) + btn:SetText('Bid') + btn:Disable() + bid_button = btn +end +do + local btn = gui.button(frame) + btn:SetPoint('TOPLEFT', bid_button, 'TOPRIGHT', 5, 0) + btn:SetText('Buyout') + btn:Disable() + buyout_button = btn +end +do + local btn = gui.button(frame) + btn:SetPoint('TOPLEFT', buyout_button, 'TOPRIGHT', 5, 0) + btn:SetText('Refresh') + btn:SetScript('OnClick', function() + scan_bids() + end) +end \ No newline at end of file diff --git a/aux-addon/tabs/post/core.lua b/aux-addon/tabs/post/core.lua new file mode 100644 index 0000000..41ca1da --- /dev/null +++ b/aux-addon/tabs/post/core.lua @@ -0,0 +1,560 @@ +module 'aux.tabs.post' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local sort_util = require 'aux.util.sort' +local persistence = require 'aux.util.persistence' +local money = require 'aux.util.money' +local scan_util = require 'aux.util.scan' +local post = require 'aux.core.post' +local scan = require 'aux.core.scan' +local history = require 'aux.core.history' +local item_listing = require 'aux.gui.item_listing' +local al = require 'aux.gui.auction_listing' +local gui = require 'aux.gui' + +local tab = aux.tab 'Post' + +local settings_schema = {'tuple', '#', {duration='number'}, {start_price='number'}, {buyout_price='number'}, {hidden='boolean'}} + +local scan_id, inventory_records, bid_records, buyout_records = 0, {}, {}, {} + +M.DURATION_2, M.DURATION_8, M.DURATION_24 = 120, 480, 1440 + +refresh = true + +selected_item = nil + +function get_default_settings() + return T.map('duration', aux.account_data.post_duration, 'start_price', 0, 'buyout_price', 0, 'hidden', false) +end + +function aux.handle.LOAD2() + data = aux.faction_data.post +end + +function read_settings(item_key) + item_key = item_key or selected_item.key + return data[item_key] and persistence.read(settings_schema, data[item_key]) or get_default_settings() +end +function write_settings(settings, item_key) + item_key = item_key or selected_item.key + data[item_key] = persistence.write(settings_schema, settings) +end + +do + local bid_selections, buyout_selections = {}, {} + function get_bid_selection() + return bid_selections[selected_item.key] + end + function set_bid_selection(record) + bid_selections[selected_item.key] = record + end + function get_buyout_selection() + return buyout_selections[selected_item.key] + end + function set_buyout_selection(record) + buyout_selections[selected_item.key] = record + end +end + +function refresh_button_click() + scan.abort(scan_id) + refresh_entries() + refresh = true +end + +function tab.OPEN() + frame:Show() + update_inventory_records() + refresh = true +end + +function tab.CLOSE() + selected_item = nil + frame:Hide() +end + +function tab.USE_ITEM(item_info) + select_item(item_info.item_key) +end + +function get_unit_start_price() + return selected_item and read_settings().start_price or 0 +end + +function set_unit_start_price(amount) + local settings = read_settings() + settings.start_price = amount + write_settings(settings) +end + +function get_unit_buyout_price() + return selected_item and read_settings().buyout_price or 0 +end + +function set_unit_buyout_price(amount) + local settings = read_settings() + settings.buyout_price = amount + write_settings(settings) +end + +function update_inventory_listing() + local records = aux.values(aux.filter(aux.copy(inventory_records), function(record) + local settings = read_settings(record.key) + return record.aux_quantity > 0 and (not settings.hidden or show_hidden_checkbox:GetChecked()) + end)) + sort(records, function(a, b) return a.name < b.name end) + item_listing.populate(inventory_listing, records) +end + +function update_auction_listing(listing, records, reference) + local rows = T.acquire() + if selected_item then + local historical_value = history.value(selected_item.key) + local stack_size = stack_size_slider:GetValue() + for _, record in records[selected_item.key] or T.empty do + local price_color = undercut(record, stack_size_slider:GetValue(), listing == 'bid') < reference and aux.color.red + local price = record.unit_price * (listing == 'bid' and record.stack_size / stack_size_slider:GetValue() or 1) + tinsert(rows, T.map( + 'cols', T.list( + T.map('value', record.own and aux.color.green(record.count) or record.count), + T.map('value', al.time_left(record.duration)), + T.map('value', record.stack_size == stack_size and aux.color.green(record.stack_size) or record.stack_size), + T.map('value', money.to_string(price, true, nil, price_color)), + T.map('value', historical_value and gui.percentage_historical(aux.round(price / historical_value * 100)) or '---') + ), + 'record', record + )) + end + if historical_value then + tinsert(rows, T.map( + 'cols', T.list( + T.map('value', '---'), + T.map('value', '---'), + T.map('value', '---'), + T.map('value', money.to_string(historical_value, true, nil, aux.color.green)), + T.map('value', historical_value and gui.percentage_historical(100) or '---') + ), + 'record', T.map('historical_value', true, 'stack_size', stack_size, 'unit_price', historical_value, 'own', true) + )) + end + sort(rows, function(a, b) + return sort_util.multi_lt( + a.record.unit_price * (listing == 'bid' and a.record.stack_size or 1), + b.record.unit_price * (listing == 'bid' and b.record.stack_size or 1), + + a.record.historical_value and 1 or 0, + b.record.historical_value and 1 or 0, + + b.record.own and 0 or 1, + a.record.own and 0 or 1, + + a.record.stack_size, + b.record.stack_size, + + a.record.duration, + b.record.duration + ) + end) + end + if listing == 'bid' then + bid_listing:SetData(rows) + elseif listing == 'buyout' then + buyout_listing:SetData(rows) + end +end + +function update_auction_listings() + update_auction_listing('bid', bid_records, get_unit_start_price()) + update_auction_listing('buyout', buyout_records, get_unit_buyout_price()) +end + +function M.select_item(item_key) + for _, inventory_record in aux.filter(aux.copy(inventory_records), function(record) return record.aux_quantity > 0 end) do + if inventory_record.key == item_key then + update_item(inventory_record) + return + end + end +end + +function price_update() + if selected_item then + local historical_value = history.value(selected_item.key) + if get_bid_selection() or get_buyout_selection() then + set_unit_start_price(undercut(get_bid_selection() or get_buyout_selection(), stack_size_slider:GetValue(), get_bid_selection())) + unit_start_price_input:SetText(money.to_string(get_unit_start_price(), true, nil, nil, true)) + end + if get_buyout_selection() then + set_unit_buyout_price(undercut(get_buyout_selection(), stack_size_slider:GetValue())) + unit_buyout_price_input:SetText(money.to_string(get_unit_buyout_price(), true, nil, nil, true)) + end + start_price_percentage:SetText(historical_value and gui.percentage_historical(aux.round(get_unit_start_price() / historical_value * 100)) or '---') + buyout_price_percentage:SetText(historical_value and gui.percentage_historical(aux.round(get_unit_buyout_price() / historical_value * 100)) or '---') + end +end + +function post_auctions() + if selected_item then + local unit_start_price = get_unit_start_price() + local unit_buyout_price = get_unit_buyout_price() + local stack_size = stack_size_slider:GetValue() + local stack_count + stack_count = stack_count_slider:GetValue() + local duration = UIDropDownMenu_GetSelectedValue(duration_dropdown) + local key = selected_item.key + + local duration_code + if duration == DURATION_2 then + duration_code = 2 + elseif duration == DURATION_8 then + duration_code = 3 + elseif duration == DURATION_24 then + duration_code = 4 + end + + post.start( + key, + stack_size, + duration, + unit_start_price, + unit_buyout_price, + stack_count, + function(posted) + if not frame:IsShown() then + return + end + for i = 1, posted do + record_auction(key, stack_size, unit_start_price * stack_size, unit_buyout_price, duration_code, UnitName'player') + end + update_inventory_records() + local same + for _, record in inventory_records do + if record.key == key then + same = record + break + end + end + if same then + update_item(same) + else + selected_item = nil + end + refresh = true + end + ) + end +end + +function validate_parameters() + if not selected_item then + post_button:Disable() + return + end + if get_unit_buyout_price() > 0 and get_unit_start_price() > get_unit_buyout_price() then + post_button:Disable() + return + end + if get_unit_start_price() == 0 then + post_button:Enable() + return + end + if stack_count_slider:GetValue() == 0 then + post_button:Disable() + return + end + post_button:Enable() +end + +function update_item_configuration() + if not selected_item then + refresh_button:Disable() + + item.texture:SetTexture(nil) + item.count:SetText() + item.name:SetTextColor(aux.color.label.enabled()) + item.name:SetText('No item selected') + + unit_start_price_input:Hide() + unit_buyout_price_input:Hide() + stack_size_slider:Hide() + stack_count_slider:Hide() + deposit:Hide() + duration_dropdown:Hide() + hide_checkbox:Hide() + else + unit_start_price_input:Show() + unit_buyout_price_input:Show() + stack_size_slider:Show() + stack_count_slider:Show() + deposit:Show() + duration_dropdown:Show() + hide_checkbox:Show() + + item.texture:SetTexture(selected_item.texture) + item.name:SetText('[' .. selected_item.name .. ']') + do + local color = ITEM_QUALITY_COLORS[selected_item.quality] + item.name:SetTextColor(color.r, color.g, color.b) + end + if selected_item.aux_quantity > 1 then + item.count:SetText(selected_item.aux_quantity) + else + item.count:SetText() + end + + stack_size_slider.editbox:SetNumber(stack_size_slider:GetValue()) + stack_count_slider.editbox:SetNumber(stack_count_slider:GetValue()) + + do + local deposit_factor = UnitFactionGroup'npc' and .05 or .25 + local duration_factor = UIDropDownMenu_GetSelectedValue(duration_dropdown) / 120 + local stack_size, stack_count = selected_item.max_charges and 1 or stack_size_slider:GetValue(), stack_count_slider:GetValue() + local amount = floor(selected_item.unit_vendor_price * deposit_factor * stack_size) * stack_count * duration_factor + deposit:SetText('Deposit: ' .. money.to_string(amount, nil, nil, aux.color.text.enabled)) + end + + refresh_button:Enable() + end +end + +function undercut(record, stack_size, stack) + local price = ceil(record.unit_price * (stack and record.stack_size or stack_size)) + if not record.own then + price = price - 1 + end + return price / stack_size +end + +function quantity_update(maximize_count) + if selected_item then + local max_stack_count = selected_item.max_charges and selected_item.availability[stack_size_slider:GetValue()] or floor(selected_item.availability[0] / stack_size_slider:GetValue()) + stack_count_slider:SetMinMaxValues(1, max_stack_count) + if maximize_count then + stack_count_slider:SetValue(max_stack_count) + end + end + refresh = true +end + +function unit_vendor_price(item_key) + for slot in info.inventory() do + T.temp(slot) + local item_info = T.temp-info.container_item(unpack(slot)) + if item_info and item_info.item_key == item_key then + if info.auctionable(item_info.tooltip, nil, true) and not item_info.lootable then + ClearCursor() + PickupContainerItem(unpack(slot)) + ClickAuctionSellItemButton() + local auction_sell_item = T.temp-info.auction_sell_item() + ClearCursor() + ClickAuctionSellItemButton() + ClearCursor() + if auction_sell_item then + return auction_sell_item.vendor_price / auction_sell_item.count + end + end + end + end +end + +function update_item(item) + local settings = read_settings(item.key) + + item.unit_vendor_price = unit_vendor_price(item.key) + if not item.unit_vendor_price then + settings.hidden = true + write_settings(settings, item.key) + refresh = true + return + end + + scan.abort(scan_id) + + selected_item = item + + UIDropDownMenu_Initialize(duration_dropdown, initialize_duration_dropdown) + UIDropDownMenu_SetSelectedValue(duration_dropdown, settings.duration) + + hide_checkbox:SetChecked(settings.hidden) + + if selected_item.max_charges then + for i = selected_item.max_charges, 1, -1 do + if selected_item.availability[i] > 0 then + stack_size_slider:SetMinMaxValues(1, i) + break + end + end + else + stack_size_slider:SetMinMaxValues(1, min(selected_item.max_stack, selected_item.aux_quantity)) + end + stack_size_slider:SetValue(aux.huge) + quantity_update(true) + + unit_start_price_input:SetText(money.to_string(settings.start_price, true, nil, nil, true)) + unit_buyout_price_input:SetText(money.to_string(settings.buyout_price, true, nil, nil, true)) + + if not bid_records[selected_item.key] then + refresh_entries() + end + + write_settings(settings, item.key) + + refresh = true +end + +function update_inventory_records() + local auctionable_map = T.temp-T.acquire() + for slot in info.inventory() do + T.temp(slot) + local item_info = T.temp-info.container_item(unpack(slot)) + if item_info then + local charge_class = item_info.charges or 0 + if info.auctionable(item_info.tooltip, nil, true) and not item_info.lootable then + if not auctionable_map[item_info.item_key] then + local availability = T.acquire() + for i = 0, 10 do + availability[i] = 0 + end + availability[charge_class] = item_info.count + auctionable_map[item_info.item_key] = T.map( + 'item_id', item_info.item_id, + 'suffix_id', item_info.suffix_id, + 'key', item_info.item_key, + 'itemstring', item_info.itemstring, + 'name', item_info.name, + 'texture', item_info.texture, + 'quality', item_info.quality, + 'aux_quantity', item_info.charges or item_info.count, + 'max_stack', item_info.max_stack, + 'max_charges', item_info.max_charges, + 'availability', availability + ) + else + local auctionable = auctionable_map[item_info.item_key] + auctionable.availability[charge_class] = (auctionable.availability[charge_class] or 0) + item_info.count + auctionable.aux_quantity = auctionable.aux_quantity + (item_info.charges or item_info.count) + end + end + end + end + T.release(inventory_records) + inventory_records = aux.values(auctionable_map) + refresh = true +end + +function refresh_entries() + if selected_item then + local item_key = selected_item.key + set_bid_selection() + set_buyout_selection() + bid_records[item_key], buyout_records[item_key] = nil, nil + local query = scan_util.item_query(selected_item.item_id) + status_bar:update_status(0, 0) + status_bar:set_text('Scanning auctions...') + + scan_id = scan.start{ + type = 'list', + ignore_owner = true, + queries = T.list(query), + on_page_loaded = function(page, total_pages) + status_bar:update_status(page / total_pages, 0) -- TODO + status_bar:set_text(format('Scanning Page %d / %d', page, total_pages)) + end, + on_auction = function(auction_record) + if auction_record.item_key == item_key then + record_auction( + auction_record.item_key, + auction_record.aux_quantity, + auction_record.unit_blizzard_bid, + auction_record.unit_buyout_price, + auction_record.duration, + auction_record.owner + ) + end + end, + on_abort = function() + bid_records[item_key], buyout_records[item_key] = nil, nil + status_bar:update_status(1, 1) + status_bar:set_text('Scan aborted') + end, + on_complete = function() + bid_records[item_key] = bid_records[item_key] or T.acquire() + buyout_records[item_key] = buyout_records[item_key] or T.acquire() + refresh = true + status_bar:update_status(1, 1) + status_bar:set_text('Scan complete') + end, + } + end +end + +function record_auction(key, aux_quantity, unit_blizzard_bid, unit_buyout_price, duration, owner) + bid_records[key] = bid_records[key] or T.acquire() + do + local entry + for _, record in bid_records[key] do + if unit_blizzard_bid == record.unit_price and aux_quantity == record.stack_size and duration == record.duration and info.is_player(owner) == record.own then + entry = record + end + end + if not entry then + entry = T.map('stack_size', aux_quantity, 'unit_price', unit_blizzard_bid, 'duration', duration, 'own', info.is_player(owner), 'count', 0) + tinsert(bid_records[key], entry) + end + entry.count = entry.count + 1 + end + buyout_records[key] = buyout_records[key] or T.acquire() + if unit_buyout_price == 0 then return end + do + local entry + for _, record in buyout_records[key] do + if unit_buyout_price == record.unit_price and aux_quantity == record.stack_size and duration == record.duration and info.is_player(owner) == record.own then + entry = record + end + end + if not entry then + entry = T.map('stack_size', aux_quantity, 'unit_price', unit_buyout_price, 'duration', duration, 'own', info.is_player(owner), 'count', 0) + tinsert(buyout_records[key], entry) + end + entry.count = entry.count + 1 + end +end + +function on_update() + if refresh then + refresh = false + price_update() + update_item_configuration() + update_inventory_listing() + update_auction_listings() + end + validate_parameters() +end + +function initialize_duration_dropdown() + local function on_click() + UIDropDownMenu_SetSelectedValue(duration_dropdown, this.value) + local settings = read_settings() + settings.duration = this.value + write_settings(settings) + refresh = true + end + UIDropDownMenu_AddButton{ + text = '2 Hours', + value = DURATION_2, + func = on_click, + } + UIDropDownMenu_AddButton{ + text = '8 Hours', + value = DURATION_8, + func = on_click, + } + UIDropDownMenu_AddButton{ + text = '24 Hours', + value = DURATION_24, + func = on_click, + } +end diff --git a/aux-addon/tabs/post/frame.lua b/aux-addon/tabs/post/frame.lua new file mode 100644 index 0000000..60bebbf --- /dev/null +++ b/aux-addon/tabs/post/frame.lua @@ -0,0 +1,326 @@ +module 'aux.tabs.post' + +local aux = require 'aux' +local info = require 'aux.util.info' +local money = require 'aux.util.money' +local gui = require 'aux.gui' +local listing = require 'aux.gui.listing' +local item_listing = require 'aux.gui.item_listing' +local search_tab = require 'aux.tabs.search' + +frame = CreateFrame('Frame', nil, aux.frame) +frame:SetAllPoints() +frame:SetScript('OnUpdate', on_update) +frame:Hide() + +frame.content = CreateFrame('Frame', nil, frame) +frame.content:SetPoint('TOP', frame, 'TOP', 0, -8) +frame.content:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) +frame.content:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) + +frame.inventory = gui.panel(frame.content) +frame.inventory:SetWidth(212) +frame.inventory:SetPoint('TOPLEFT', 0, 0) +frame.inventory:SetPoint('BOTTOMLEFT', 0, 0) + +frame.parameters = gui.panel(frame.content) +frame.parameters:SetHeight(173) +frame.parameters:SetPoint('TOPLEFT', frame.inventory, 'TOPRIGHT', 2.5, 0) +frame.parameters:SetPoint('TOPRIGHT', 0, 0) + +frame.bid_listing = gui.panel(frame.content) +frame.bid_listing:SetHeight(228) +frame.bid_listing:SetWidth(271.5) +frame.bid_listing:SetPoint('BOTTOMLEFT', frame.inventory, 'BOTTOMRIGHT', 2.5, 0) + +frame.buyout_listing = gui.panel(frame.content) +frame.buyout_listing:SetHeight(228) +frame.buyout_listing:SetWidth(271.5) +frame.buyout_listing:SetPoint('BOTTOMRIGHT', 0, 0) + +do + local checkbox = gui.checkbox(frame.inventory) + checkbox:SetPoint('TOPLEFT', 49, -15) + checkbox:SetScript('OnClick', function() + refresh = true + end) + local label = gui.label(checkbox, gui.font_size.small) + label:SetPoint('LEFT', checkbox, 'RIGHT', 4, 1) + label:SetText('Show hidden items') + show_hidden_checkbox = checkbox +end + +gui.horizontal_line(frame.inventory, -45) + +do + local f = CreateFrame('Frame', nil, frame.inventory) + f:SetPoint('TOPLEFT', 0, -51) + f:SetPoint('BOTTOMRIGHT', 0, 0) + inventory_listing = item_listing.new( + f, + function() + if arg1 == 'LeftButton' then + update_item(this.item_record) + elseif arg1 == 'RightButton' then + aux.set_tab(1) + search_tab.set_filter(strlower(info.item(this.item_record.item_id).name) .. '/exact') + search_tab.execute(nil, false) + end + end, + function(item_record) + return item_record == selected_item + end + ) +end + +bid_listing = listing.new(frame.bid_listing) +bid_listing:SetColInfo{ + {name='Auctions', width=.17, align='CENTER'}, + {name='Time\nLeft', width=.11, align='CENTER'}, + {name='Stack\nSize', width=.11, align='CENTER'}, + {name='Auction Bid\n(per item)', width=.4, align='RIGHT'}, + {name='% Hist.\nValue', width=.21, align='CENTER'}, +} +bid_listing:SetSelection(function(data) + return data.record == get_bid_selection() or data.record.historical_value and get_bid_selection() and get_bid_selection().historical_value +end) +bid_listing:SetHandler('OnClick', function(table, row_data, column, button) + if button == 'RightButton' and row_data.record == get_bid_selection() or row_data.record.historical_value and get_bid_selection() and get_bid_selection().historical_value then + set_bid_selection() + else + set_bid_selection(row_data.record) + end + refresh = true +end) +bid_listing:SetHandler('OnDoubleClick', function(table, row_data, column, button) + stack_size_slider:SetValue(row_data.record.stack_size) + refresh = true +end) + +buyout_listing = listing.new(frame.buyout_listing) +buyout_listing:SetColInfo{ + {name='Auctions', width=.17, align='CENTER'}, + {name='Time\nLeft', width=.11, align='CENTER'}, + {name='Stack\nSize', width=.12, align='CENTER'}, + {name='Auction Buyout\n(per item)', width=.4, align='RIGHT'}, + {name='% Hist.\nValue', width=.20, align='CENTER'}, +} +buyout_listing:SetSelection(function(data) + return data.record == get_buyout_selection() or data.record.historical_value and get_buyout_selection() and get_buyout_selection().historical_value +end) +buyout_listing:SetHandler('OnClick', function(table, row_data, column, button) + if button == 'RightButton' and row_data.record == get_buyout_selection() or row_data.record.historical_value and get_buyout_selection() and get_buyout_selection().historical_value then + set_buyout_selection() + else + set_buyout_selection(row_data.record) + end + refresh = true +end) +buyout_listing:SetHandler('OnDoubleClick', function(table, row_data, column, button) + stack_size_slider:SetValue(row_data.record.stack_size) + refresh = true +end) + +do + status_bar = gui.status_bar(frame) + status_bar:SetWidth(265) + status_bar:SetHeight(25) + status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) + status_bar:update_status(1, 1) + status_bar:set_text('') +end +do + local btn = gui.button(frame.parameters) + btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) + btn:SetText('Post') + btn:SetScript('OnClick', post_auctions) + post_button = btn +end +do + local btn = gui.button(frame.parameters) + btn:SetPoint('TOPLEFT', post_button, 'TOPRIGHT', 5, 0) + btn:SetText('Refresh') + btn:SetScript('OnClick', refresh_button_click) + refresh_button = btn +end +do + item = gui.item(frame.parameters) + item:SetPoint('TOPLEFT', 10, -6) + item.button:SetScript('OnEnter', function() + if selected_item then + info.set_tooltip(selected_item.itemstring, this, 'ANCHOR_RIGHT') + end + end) + item.button:SetScript('OnLeave', function() + GameTooltip:Hide() + end) +end +do + local slider = gui.slider(frame.parameters) + slider:SetValueStep(1) + slider:SetPoint('TOPLEFT', 13, -73) + slider:SetWidth(190) + slider:SetScript('OnValueChanged', function() + quantity_update(true) + end) + slider.editbox.change = function() + slider:SetValue(this:GetNumber()) + quantity_update(true) + if selected_item then + local settings = read_settings() + write_settings(settings) + end + end + slider.editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + unit_buyout_price_input:SetFocus() + elseif stack_count_slider.editbox:IsVisible() then + stack_count_slider.editbox:SetFocus() + else + unit_start_price_input:SetFocus() + end + end) + slider.editbox:SetNumeric(true) + slider.editbox:SetMaxLetters(3) + slider.label:SetText('Stack Size') + stack_size_slider = slider +end +do + local slider = gui.slider(frame.parameters) + slider:SetValueStep(1) + slider:SetPoint('TOPLEFT', stack_size_slider, 'BOTTOMLEFT', 0, -32) + slider:SetWidth(190) + slider:SetScript('OnValueChanged', function() + quantity_update() + end) + slider.editbox.change = function() + slider:SetValue(this:GetNumber()) + quantity_update() + end + slider.editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + stack_size_slider.editbox:SetFocus() + else + unit_start_price_input:SetFocus() + end + end) + slider.editbox:SetNumeric(true) + slider.label:SetText('Stack Count') + stack_count_slider = slider +end +do + local dropdown = gui.dropdown(frame.parameters) + dropdown:SetPoint('TOPLEFT', stack_count_slider, 'BOTTOMLEFT', 0, -22) + dropdown:SetWidth(90) + local label = gui.label(dropdown, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) + label:SetText('Duration') + UIDropDownMenu_Initialize(dropdown, initialize_duration_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_duration_dropdown) + end) + duration_dropdown = dropdown +end +do + local checkbox = gui.checkbox(frame.parameters) + checkbox:SetPoint('TOPRIGHT', -83, -6) + checkbox:SetScript('OnClick', function() + local settings = read_settings() + settings.hidden = this:GetChecked() + write_settings(settings) + refresh = true + end) + local label = gui.label(checkbox, gui.font_size.small) + label:SetPoint('LEFT', checkbox, 'RIGHT', 4, 1) + label:SetText('Hide this item') + hide_checkbox = checkbox +end +do + local editbox = gui.editbox(frame.parameters) + editbox:SetPoint('TOPRIGHT', -71, -60) + editbox:SetWidth(180) + editbox:SetHeight(22) + editbox:SetAlignment('RIGHT') + editbox:SetFontSize(17) + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + stack_count_slider.editbox:SetFocus() + else + unit_buyout_price_input:SetFocus() + end + end) + editbox.formatter = function() return money.to_string(get_unit_start_price(), true) end + editbox.char = function() set_bid_selection(); set_buyout_selection(); set_unit_start_price(money.from_string(this:GetText())) end + editbox.change = function() refresh = true end + editbox.enter = function() this:ClearFocus() end + editbox.focus_loss = function() + this:SetText(money.to_string(get_unit_start_price(), true, nil, nil, true)) + end + do + local label = gui.label(editbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) + label:SetText('Unit Starting Price') + end + do + local label = gui.label(editbox, 14) + label:SetPoint('LEFT', editbox, 'RIGHT', 8, 0) + label:SetWidth(50) + label:SetJustifyH('CENTER') + start_price_percentage = label + end + unit_start_price_input = editbox +end +do + local editbox = gui.editbox(frame.parameters) + editbox:SetPoint('TOPRIGHT', unit_start_price_input, 'BOTTOMRIGHT', 0, -19) + editbox:SetWidth(180) + editbox:SetHeight(22) + editbox:SetAlignment('RIGHT') + editbox:SetFontSize(17) + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + unit_start_price_input:SetFocus() + else + stack_size_slider.editbox:SetFocus() + end + end) + editbox.formatter = function() return money.to_string(get_unit_buyout_price(), true) end + editbox.char = function() set_buyout_selection(); set_unit_buyout_price(money.from_string(this:GetText())) end + editbox.change = function() refresh = true end + editbox.enter = function() this:ClearFocus() end + editbox.focus_loss = function() + this:SetText(money.to_string(get_unit_buyout_price(), true, nil, nil, true)) + end + do + local label = gui.label(editbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) + label:SetText('Unit Buyout Price') + end + do + local label = gui.label(editbox, 14) + label:SetPoint('LEFT', editbox, 'RIGHT', 8, 0) + label:SetWidth(50) + label:SetJustifyH('CENTER') + buyout_price_percentage = label + end + unit_buyout_price_input = editbox +end +do + local label = gui.label(frame.parameters, gui.font_size.medium) + label:SetPoint('TOPLEFT', unit_buyout_price_input, 'BOTTOMLEFT', 0, -24) + deposit = label +end + +function aux.handle.LOAD() + if not aux.account_data.post_bid then + frame.bid_listing:Hide() + frame.buyout_listing:SetPoint('BOTTOMLEFT', frame.inventory, 'BOTTOMRIGHT', 2.5, 0) + buyout_listing:SetColInfo{ + {name='Auctions', width=.15, align='CENTER'}, + {name='Time Left', width=.15, align='CENTER'}, + {name='Stack Size', width=.15, align='CENTER'}, + {name='Auction Buyout (per item)', width=.4, align='RIGHT'}, + {name='% Hist. Value', width=.15, align='CENTER'}, + } + end +end \ No newline at end of file diff --git a/aux-addon/tabs/search/core.lua b/aux-addon/tabs/search/core.lua new file mode 100644 index 0000000..fcf9650 --- /dev/null +++ b/aux-addon/tabs/search/core.lua @@ -0,0 +1,85 @@ +module 'aux.tabs.search' + +local aux = require 'aux' +local info = require 'aux.util.info' + +local tab = aux.tab 'Search' + +StaticPopupDialogs.AUX_SEARCH_TABLE_FULL = { + text = 'Table full!\nFurther results from this search will still be processed but no longer displayed in the table.', + button1 = 'Ok', + showAlert = 1, + timeout = 0, + hideOnEscape = 1, +} + +RESULTS, SAVED, FILTER = aux.enum(3) + +function aux.handle.LOAD() + set_subtab(SAVED) +end + +function tab.OPEN() + frame:Show() + update_search_listings() + update_filter_display() +end + +function tab.CLOSE() + current_search().table:SetSelectedRecord() + frame:Hide() +end + +function tab.CLICK_LINK(item_info) + set_filter(strlower(item_info.name) .. '/exact') + execute(nil, false) +end + +function tab.USE_ITEM(item_info) + set_filter(strlower(info.item(item_info.item_id).name) .. '/exact') + execute(nil, false) +end + +function set_subtab(tab) + CloseDropDownMenus() + search_results_button:UnlockHighlight() + saved_searches_button:UnlockHighlight() + new_filter_button:UnlockHighlight() + frame.results:Hide() + frame.saved:Hide() + frame.filter:Hide() + + if tab == RESULTS then + frame.results:Show() + search_results_button:LockHighlight() + elseif tab == SAVED then + frame.saved:Show() + saved_searches_button:LockHighlight() + elseif tab == FILTER then + frame.filter:Show() + new_filter_button:LockHighlight() + end +end + +function M.set_filter(filter_string) + search_box:SetFocus() + search_box:SetText(filter_string) +end + +function add_filter(filter_string) + local old_filter_string = search_box:GetText() + old_filter_string = aux.trim(old_filter_string) + + if old_filter_string ~= '' then + old_filter_string = old_filter_string .. ';' + end + + set_filter(old_filter_string .. filter_string) +end + +function blizzard_page_index(str) + if tonumber(str) then + return max(0, tonumber(str) - 1) + end +end + diff --git a/aux-addon/tabs/search/filter.lua b/aux-addon/tabs/search/filter.lua new file mode 100644 index 0000000..44618bf --- /dev/null +++ b/aux-addon/tabs/search/filter.lua @@ -0,0 +1,395 @@ +module 'aux.tabs.search' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local money = require 'aux.util.money' +local filter_util = require 'aux.util.filter' + +local post_filter = {} +local post_filter_index = 0 + +function valid_level(str) + local level = tonumber(str) + return level and aux.bounded(1, 60, level) +end + +blizzard_query = setmetatable(T.acquire(), { + __index = function(_, key) + if key == 'name' then + return name_input:GetText() + elseif key == 'exact' then + return exact_checkbox:GetChecked() + elseif key == 'min_level' then + return tonumber(min_level_input:GetText()) + elseif key == 'max_level' then + return tonumber(max_level_input:GetText()) + elseif key == 'usable' then + return usable_checkbox:GetChecked() + elseif key == 'class' then + local class_index = UIDropDownMenu_GetSelectedValue(class_dropdown) + return class_index ~= 0 and class_index or nil + elseif key == 'subclass' then + local subclass_index = UIDropDownMenu_GetSelectedValue(subclass_dropdown) + return subclass_index ~= 0 and subclass_index or nil + elseif key == 'slot' then + local slot_index = UIDropDownMenu_GetSelectedValue(slot_dropdown) + return (slot_index or 0) > 0 and slot_index or nil + elseif key == 'quality' then + local quality_code = UIDropDownMenu_GetSelectedValue(quality_dropdown) + return (quality_code or -1) >= 0 and quality_code or nil + end + end, + __newindex = function(_, key, value) + if key == 'name' then + name_input:SetText(value) + elseif key == 'exact' then + exact_checkbox:SetChecked(value) + elseif key == 'min_level' then + min_level_input:SetText(value) + elseif key == 'max_level' then + max_level_input:SetText(value) + elseif key == 'usable' then + usable_checkbox:SetChecked(value) + elseif key == 'class' then + UIDropDownMenu_Initialize(class_dropdown, initialize_class_dropdown) + UIDropDownMenu_SetSelectedValue(class_dropdown, value) + elseif key == 'subclass' then + UIDropDownMenu_Initialize(subclass_dropdown, initialize_subclass_dropdown) + UIDropDownMenu_SetSelectedValue(subclass_dropdown, value) + elseif key == 'slot' then + UIDropDownMenu_Initialize(slot_dropdown, initialize_slot_dropdown) + UIDropDownMenu_SetSelectedValue(slot_dropdown, value) + elseif key == 'quality' then + UIDropDownMenu_Initialize(quality_dropdown, initialize_quality_dropdown) + UIDropDownMenu_SetSelectedValue(quality_dropdown, value) + end + end, +}) + +function update_form() + if GetAuctionItemSubClasses(blizzard_query.class or 0) then + subclass_dropdown.button:Enable() + else + subclass_dropdown.button:Disable() + end + + if blizzard_query.class == 2 then + slot_dropdown.button:Enable() + else + slot_dropdown.button:Disable() + end + + if blizzard_query.exact then + usable_checkbox:Disable() + for key in T.temp-T.set('min_level', 'max_level') do + _M[key .. '_input']:EnableMouse(false) + _M[key .. '_input']:ClearFocus() + end + for key in T.temp-T.set('class', 'subclass', 'slot', 'quality') do + _M[key .. '_dropdown'].button:Disable() + end + CloseDropDownMenus() + else + usable_checkbox:Enable() + for key in T.temp-T.set('min_level', 'max_level') do + _M[key .. '_input']:EnableMouse(true) + end + class_dropdown.button:Enable() + quality_dropdown.button:Enable() + end + + if aux.any(T.temp-T.list('min_level', 'max_level', 'usable', 'class', 'subclass', 'slot', 'quality'), function(key) return blizzard_query[key] end) then + exact_checkbox:Disable() + else + exact_checkbox:Enable() + end +end + +function get_filter_builder_query() + local filter_string + + local function add(part) + if part then + filter_string = filter_string and filter_string .. '/' .. part or part + end + end + + local name = blizzard_query.name + if not aux.index(filter_util.parse_filter_string(name), 'blizzard', 'name') then + name = filter_util.quote(name) + end + add((name ~= '' or blizzard_query.exact) and name) + + add(blizzard_query.exact and 'exact') + add(blizzard_query.min_level or blizzard_query.max_level and 1) + add(blizzard_query.max_level) + add(blizzard_query.usable and 'usable') + + local classes = T.temp-T.list(GetAuctionItemClasses()) + if blizzard_query.class and blizzard_query.class > 0 then + add(strlower(classes[blizzard_query.class])) + end + local subclasses = T.temp-T.list(GetAuctionItemSubClasses(blizzard_query.class or 0)) + if blizzard_query.subclass and blizzard_query.subclass > 0 then + add(strlower(subclasses[blizzard_query.subclass])) + end + local slot_index = blizzard_query.slot + if slot_index then + local slots = T.temp-T.list(GetAuctionInvTypes(blizzard_query.class or 0, blizzard_query.subclass or 0)) + add(strlower(_G[slots[slot_index]])) + end + + local quality = blizzard_query.quality + if quality and quality >= 0 then + add(strlower(_G['ITEM_QUALITY' .. quality .. '_DESC'])) + end + + local post_filter_string = filter_util.filter_string(post_filter) + add(post_filter_string ~= '' and post_filter_string) + + return filter_string or '' +end + +function set_form(filter) + clear_form() + for _, component in ipairs(filter.components) do + if component[1] == 'blizzard' then + blizzard_query[component[2]] = component[4] + else + add_component(component) + end + end + update_filter_display() +end + +function clear_form() + blizzard_query.name = '' + name_input:ClearFocus() + blizzard_query.exact = false + blizzard_query.min_level = '' + min_level_input:ClearFocus() + blizzard_query.max_level = '' + max_level_input:ClearFocus() + blizzard_query.usable = false + UIDropDownMenu_ClearAll(class_dropdown) + UIDropDownMenu_ClearAll(subclass_dropdown) + UIDropDownMenu_ClearAll(slot_dropdown) + UIDropDownMenu_ClearAll(quality_dropdown) + filter_parameter_input:ClearFocus() + T.wipe(post_filter) + post_filter_index = 0 + update_filter_display() +end + +function import_filter_string() + local filter, error = filter_util.parse_filter_string(aux.select(3, strfind(search_box:GetText(), '^([^;]*)'))) + if filter or aux.print(error) then + set_form(filter) + end + update_form() +end + +function export_filter_string() + set_filter(get_filter_builder_query()) +end + +function formatted_post_filter(components) + local no_line_break + local stack = T.temp-T.acquire() + local str = '' + + for i, component in ipairs(components) do + local component = components[i] + if no_line_break then + str = str .. ' ' + end + str = str .. '

' + for _ = 1, getn(stack) + 1 do + str = str .. aux.color.content.background'----' + end + no_line_break = component[1] == 'operator' and component[2] == 'not' + + local filter_color = (post_filter_index == i and aux.color.gold or aux.color.orange) + local component_text = filter_color(component[2]) + if component[1] == 'operator' and component[2] ~= 'not' then + component_text = component_text .. filter_color(tonumber(component[3]) or '') + tinsert(stack, component[3]) + elseif component[1] == 'filter' then + local parameter = component[3] + if parameter then + if component[2] == 'item' then + parameter = info.display_name(info.item_id(parameter)) or '[' .. parameter .. ']' + elseif filter_util.filters[component[2]].input_type == 'money' then + parameter = money.to_string(money.from_string(parameter), nil, true) + end + component_text = component_text .. filter_color(': ') .. parameter + end + while getn(stack) > 0 and stack[getn(stack)] do + local top = tremove(stack) + if tonumber(top) and top > 1 then + tinsert(stack, top - 1) + break + end + end + end + str = str .. data_link(i, component_text) + end + + return '

' .. data_link(0, 'Post Filter:') .. '

' .. str .. '

' +end + +function data_link(id, str) + return format('%s', id, str) +end + +function data_link_click() + local button = arg3 + local index = tonumber(arg1) + if button == 'LeftButton' then + post_filter_index = index + elseif button == 'RightButton' and index > 0 then + remove_component(index) + end + update_filter_display() +end + +function remove_component(index) + tremove(post_filter, index) + if post_filter_index >= index then + post_filter_index = max(post_filter_index - 1, min(1, getn(post_filter))) + end +end + +function add_component(component) + post_filter_index = post_filter_index + 1 + tinsert(post_filter, post_filter_index, component) +end + +function add_form_component() + local str = filter_input:GetText() + if str then + local filter = filter_util.filters[str] + if filter then + if filter.input_type ~= '' then + str = str .. '/' .. filter_parameter_input:GetText() + end + end + local components, error = filter_util.parse_filter_string(str) + if components and getn(components.blizzard) == 0 and getn(components.post) == 1 then + add_component(components.post[1]) + update_filter_display() + filter_parameter_input:SetText('') + filter_input:HighlightText() + filter_input:SetFocus() + elseif error then + aux.print(error) + end + end +end + +do + local text = '' + function update_filter_display() + text = formatted_post_filter(post_filter) + filter_display:SetWidth(filter_display_size()) + set_filter_display_offset() + filter_display:SetText(text) + end + function filter_display_size() + local font, font_size = filter_display:GetFont() + filter_display.measure:SetFont(font, font_size) + local lines = 0 + local width = 0 + for line in string.gfind(text, '

(.-)

') do + lines = lines + 1 + filter_display.measure:SetText(line) + width = max(width, filter_display.measure:GetStringWidth()) + end + return width, lines * (font_size + .5) + end +end + +function set_filter_display_offset(x_offset, y_offset) + local scroll_frame = filter_display:GetParent() + x_offset, y_offset = x_offset or scroll_frame:GetHorizontalScroll(), y_offset or scroll_frame:GetVerticalScroll() + local width, height = filter_display_size() + local x_lower_bound = min(0, scroll_frame:GetWidth() - width - 10) + local x_upper_bound = 0 + local y_lower_bound = 0 + local y_upper_bound = max(0, height - scroll_frame:GetHeight()) + scroll_frame:SetHorizontalScroll(aux.bounded(x_lower_bound, x_upper_bound, x_offset)) + scroll_frame:SetVerticalScroll(aux.bounded(y_lower_bound, y_upper_bound, y_offset)) +end + +function initialize_filter_dropdown() + for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do + UIDropDownMenu_AddButton(T.map( + 'text', filter, + 'value', filter, + 'func', function() + filter_input:SetText(this.value) + if aux.index(filter_util.filters[this.value], 'input_type') == '' or this.value == 'not' then + add_form_component() + elseif filter_util.filters[this.value] then + filter_parameter_input:Show() + filter_parameter_input:SetFocus() + else + filter_input:SetFocus() + end + end + )) + end +end + +function initialize_class_dropdown() + local function on_click() + if this.value ~= blizzard_query.class then + UIDropDownMenu_SetSelectedValue(class_dropdown, this.value) + UIDropDownMenu_ClearAll(subclass_dropdown) + UIDropDownMenu_Initialize(subclass_dropdown, initialize_subclass_dropdown) + UIDropDownMenu_ClearAll(slot_dropdown) + UIDropDownMenu_Initialize(slot_dropdown, initialize_slot_dropdown) + update_form() + end + end + UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) + for i, class in ipairs(T.temp-T.list(GetAuctionItemClasses())) do + UIDropDownMenu_AddButton(T.map('text', class, 'value', i, 'func', on_click)) + end +end + +function initialize_subclass_dropdown() + local function on_click() + if this.value ~= blizzard_query.subclass then + UIDropDownMenu_SetSelectedValue(subclass_dropdown, this.value) + update_form() + end + end + UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) + for i, subclass in ipairs(T.temp-T.list(GetAuctionItemSubClasses(blizzard_query.class or 0))) do + UIDropDownMenu_AddButton(T.map('text', subclass, 'value', i, 'func', on_click)) + end +end + +function initialize_slot_dropdown() + local function on_click() + UIDropDownMenu_SetSelectedValue(slot_dropdown, this.value) + update_form() + end + UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) + for i, slot in ipairs(T.temp-T.list(GetAuctionInvTypes(blizzard_query.class == 2 and 2 or 0, blizzard_query.subclass or 0))) do + UIDropDownMenu_AddButton(T.map('text', _G[slot], 'value', i, 'func', on_click)) + end +end + +function initialize_quality_dropdown() + local function on_click() + UIDropDownMenu_SetSelectedValue(quality_dropdown, this.value) + update_form() + end + UIDropDownMenu_AddButton(T.map('text', ALL, 'value', -1, 'func', on_click)) + for i = 0, 4 do + UIDropDownMenu_AddButton(T.map('text', _G['ITEM_QUALITY' .. i .. '_DESC'], 'value', i, 'func', on_click)) + end +end diff --git a/aux-addon/tabs/search/frame.lua b/aux-addon/tabs/search/frame.lua new file mode 100644 index 0000000..24a5d3a --- /dev/null +++ b/aux-addon/tabs/search/frame.lua @@ -0,0 +1,559 @@ +module 'aux.tabs.search' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local completion = require 'aux.util.completion' +local filter_util = require 'aux.util.filter' +local scan = require 'aux.core.scan' +local gui = require 'aux.gui' +local listing = require 'aux.gui.listing' +local auction_listing = require 'aux.gui.auction_listing' + +local FILTER_SPACING = 28.5 + +frame = CreateFrame('Frame', nil, aux.frame) +frame:SetAllPoints() +frame:SetScript('OnUpdate', on_update) +frame:Hide() + +frame.filter = gui.panel(frame) +frame.filter:SetAllPoints(aux.frame.content) + +frame.results = gui.panel(frame) +frame.results:SetAllPoints(aux.frame.content) + +frame.saved = CreateFrame('Frame', nil, frame) +frame.saved:SetAllPoints(aux.frame.content) + +frame.saved.favorite = gui.panel(frame.saved) +frame.saved.favorite:SetWidth(393) +frame.saved.favorite:SetPoint('TOPLEFT', 0, 0) +frame.saved.favorite:SetPoint('BOTTOMLEFT', 0, 0) + +frame.saved.recent = gui.panel(frame.saved) +frame.saved.recent:SetWidth(364.5) +frame.saved.recent:SetPoint('TOPRIGHT', 0, 0) +frame.saved.recent:SetPoint('BOTTOMRIGHT', 0, 0) +do + local btn = gui.button(frame, 25) + btn:SetPoint('TOPLEFT', 5, -8) + btn:SetWidth(30) + btn:SetHeight(25) + btn:SetText('<') + btn:SetScript('OnClick', previous_search) + previous_button = btn +end +do + local btn = gui.button(frame, 25) + btn:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) + btn:SetWidth(30) + btn:SetHeight(25) + btn:SetText('>') + btn:SetScript('OnClick', next_search) + next_button = btn +end +do + local btn = gui.button(frame, gui.font_size.small) + btn:SetHeight(25) + btn:SetWidth(60) + btn:SetText(aux.color.label.enabled'Range:') + btn:SetScript('OnClick', function() + update_real_time(true) + end) + range_button = btn +end +do + local btn = gui.button(frame, gui.font_size.small) + btn:SetHeight(25) + btn:SetWidth(60) + btn:Hide() + btn:SetText(aux.color.label.enabled'Real Time') + btn:SetScript('OnClick', function() + update_real_time(false) + end) + real_time_button = btn +end +do + local function change() + local page = tonumber(this:GetText()) + local valid_input = page and tostring(max(1, page)) or '' + if this:GetText() ~= valid_input then + this:SetText(valid_input) + end + end + do + local editbox = gui.editbox(range_button) + editbox:SetPoint('LEFT', range_button, 'RIGHT', 4, 0) + editbox:SetWidth(40) + editbox:SetHeight(25) + editbox:SetAlignment('CENTER') + editbox:SetNumeric(true) + editbox:SetScript('OnTabPressed', function() + if not IsShiftKeyDown() then + last_page_input:SetFocus() + end + end) + editbox.enter = execute + editbox.change = change + local label = gui.label(editbox, gui.font_size.medium) + label:SetPoint('LEFT', editbox, 'RIGHT', 0, 0) + label:SetTextColor(aux.color.label.enabled()) + label:SetText('-') + first_page_input = editbox + end + do + local editbox = gui.editbox(range_button) + editbox:SetPoint('LEFT', first_page_input, 'RIGHT', 5.8, 0) + editbox:SetWidth(40) + editbox:SetHeight(25) + editbox:SetAlignment('CENTER') + editbox:SetNumeric(true) + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + first_page_input:SetFocus() + else + search_box:SetFocus() + end + end) + editbox.enter = execute + editbox.change = change + last_page_input = editbox + end +end +do + local btn = gui.button(frame) + btn:SetHeight(25) + btn:SetPoint('TOPRIGHT', -5, -8) + btn:SetText('Search') + btn:RegisterForClicks('LeftButtonUp', 'RightButtonUp') + btn:SetScript('OnClick', function() + if arg1 == 'RightButton' then + set_filter(current_search().filter_string) + end + execute() + end) + start_button = btn +end +do + local btn = gui.button(frame) + btn:SetHeight(25) + btn:SetPoint('TOPRIGHT', -5, -8) + btn:SetText('Pause') + btn:SetScript('OnClick', function() + scan.abort(search_scan_id) + end) + stop_button = btn +end +do + local btn = gui.button(frame) + btn:SetHeight(25) + btn:SetPoint('RIGHT', start_button, 'LEFT', -4, 0) + btn:SetBackdropColor(aux.color.state.enabled()) + btn:SetText('Resume') + btn:SetScript('OnClick', function() + execute(true) + end) + resume_button = btn +end +do + local editbox = gui.editbox(frame) + editbox:EnableMouse(1) + editbox.formatter = function(str) + local queries = filter_util.queries(str) + return queries and aux.join(aux.map(aux.copy(queries), function(query) return query.prettified end), ';') or aux.color.red(str) + end + editbox.complete = completion.complete_filter + editbox.escape = function() this:SetText(current_search().filter_string or '') end + editbox:SetHeight(25) + editbox.char = function() + this:complete() + end + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + last_page_input:SetFocus() + else + this:HighlightText(0, 0) + end + end) + editbox.enter = execute + search_box = editbox +end +do + gui.horizontal_line(frame, -40) +end +do + local btn = gui.button(frame, gui.font_size.large) + btn:SetPoint('BOTTOMLEFT', aux.frame.content, 'TOPLEFT', 10, 8) + btn:SetWidth(243) + btn:SetHeight(22) + btn:SetText('Search Results') + btn:SetScript('OnClick', function() set_subtab(RESULTS) end) + search_results_button = btn +end +do + local btn = gui.button(frame, gui.font_size.large) + btn:SetPoint('TOPLEFT', search_results_button, 'TOPRIGHT', 5, 0) + btn:SetWidth(243) + btn:SetHeight(22) + btn:SetText('Saved Searches') + btn:SetScript('OnClick', function() set_subtab(SAVED) end) + saved_searches_button = btn +end +do + local btn = gui.button(frame, gui.font_size.large) + btn:SetPoint('TOPLEFT', saved_searches_button, 'TOPRIGHT', 5, 0) + btn:SetWidth(243) + btn:SetHeight(22) + btn:SetText('Filter Builder') + btn:SetScript('OnClick', function() set_subtab(FILTER) end) + new_filter_button = btn +end +do + local frame = CreateFrame('Frame', nil, frame) + frame:SetWidth(265) + frame:SetHeight(25) + frame:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) + status_bar_frame = frame +end +do + local btn = gui.button(frame.results) + btn:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) + btn:SetText('Bid') + btn:Disable() + bid_button = btn +end +do + local btn = gui.button(frame.results) + btn:SetPoint('TOPLEFT', bid_button, 'TOPRIGHT', 5, 0) + btn:SetText('Buyout') + btn:Disable() + buyout_button = btn +end +do + local btn = gui.button(frame.results) + btn:SetPoint('TOPLEFT', buyout_button, 'TOPRIGHT', 5, 0) + btn:SetText('Clear') + btn:SetScript('OnClick', function() + while tremove(current_search().records) do end + current_search().table:SetDatabase() + end) +end +do + local btn = gui.button(frame.saved) + btn:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) + btn:SetText('Favorite') + btn:SetScript('OnClick', function() + add_favorite(search_box:GetText()) + end) +end +do + local btn1 = gui.button(frame.filter) + btn1:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) + btn1:SetText('Search') + btn1:SetScript('OnClick', function() + export_filter_string() + execute() + end) + + local btn2 = gui.button(frame.filter) + btn2:SetPoint('LEFT', btn1, 'RIGHT', 5, 0) + btn2:SetText('Export') + btn2:SetScript('OnClick', export_filter_string) + + local btn3 = gui.button(frame.filter) + btn3:SetPoint('LEFT', btn2, 'RIGHT', 5, 0) + btn3:SetText('Import') + btn3:SetScript('OnClick', import_filter_string) +end +do + local editbox = gui.editbox(frame.filter) + editbox.complete_item = completion.complete(function() return aux.account_data.auctionable_items end) + editbox:SetPoint('TOPLEFT', 14, -FILTER_SPACING) + editbox:SetWidth(260) + editbox.char = function() + if blizzard_query.exact then + this:complete_item() + end + end + editbox:SetScript('OnTabPressed', function() + if blizzard_query.exact then + return + end + if IsShiftKeyDown() then + max_level_input:SetFocus() + else + min_level_input:SetFocus() + end + end) + editbox.change = update_form + editbox.enter = function() editbox:ClearFocus() end + local label = gui.label(editbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) + label:SetText('Name') + name_input = editbox +end +do + local checkbox = gui.checkbox(frame.filter) + checkbox:SetPoint('TOPLEFT', name_input, 'TOPRIGHT', 16, 0) + checkbox:SetScript('OnClick', update_form) + local label = gui.label(checkbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', checkbox, 'TOPLEFT', -2, 1) + label:SetText('Exact') + exact_checkbox = checkbox +end +do + local editbox = gui.editbox(frame.filter) + editbox:SetPoint('TOPLEFT', name_input, 'BOTTOMLEFT', 0, -FILTER_SPACING) + editbox:SetWidth(125) + editbox:SetAlignment('CENTER') + editbox:SetNumeric(true) + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + name_input:SetFocus() + else + max_level_input:SetFocus() + end + end) + editbox.enter = function() editbox:ClearFocus() end + editbox.change = function() + local valid_level = valid_level(this:GetText()) + if tostring(valid_level) ~= this:GetText() then + this:SetText(valid_level or '') + end + update_form() + end + local label = gui.label(editbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) + label:SetText('Level Range') + min_level_input = editbox +end +do + local editbox = gui.editbox(frame.filter) + editbox:SetPoint('TOPLEFT', min_level_input, 'TOPRIGHT', 10, 0) + editbox:SetWidth(125) + editbox:SetAlignment('CENTER') + editbox:SetNumeric(true) + editbox:SetScript('OnTabPressed', function() + if IsShiftKeyDown() then + min_level_input:SetFocus() + else + name_input:SetFocus() + end + end) + editbox.enter = function() editbox:ClearFocus() end + editbox.change = function() + local valid_level = valid_level(this:GetText()) + if tostring(valid_level) ~= this:GetText() then + this:SetText(valid_level or '') + end + update_form() + end + local label = gui.label(editbox, gui.font_size.medium) + label:SetPoint('RIGHT', editbox, 'LEFT', -3, 0) + label:SetText('-') + max_level_input = editbox +end +do + local checkbox = gui.checkbox(frame.filter) + checkbox:SetPoint('TOPLEFT', max_level_input, 'TOPRIGHT', 16, 0) + checkbox:SetScript('OnClick', update_form) + local label = gui.label(checkbox, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', checkbox, 'TOPLEFT', -2, 1) + label:SetText('Usable') + usable_checkbox = checkbox +end +do + local dropdown = gui.dropdown(frame.filter) + class_dropdown = dropdown + dropdown:SetPoint('TOPLEFT', min_level_input, 'BOTTOMLEFT', 0, 5 - FILTER_SPACING) + dropdown:SetWidth(300) + local label = gui.label(dropdown, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) + label:SetText('Item Class') + UIDropDownMenu_Initialize(dropdown, initialize_class_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_class_dropdown) + end) +end +do + local dropdown = gui.dropdown(frame.filter) + subclass_dropdown = dropdown + dropdown:SetPoint('TOPLEFT', class_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) + dropdown:SetWidth(300) + local label = gui.label(dropdown, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) + label:SetText('Item Subclass') + UIDropDownMenu_Initialize(dropdown, initialize_subclass_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_subclass_dropdown) + end) +end +do + local dropdown = gui.dropdown(frame.filter) + slot_dropdown = dropdown + dropdown:SetPoint('TOPLEFT', subclass_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) + dropdown:SetWidth(300) + local label = gui.label(dropdown, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) + label:SetText('Item Slot') + UIDropDownMenu_Initialize(dropdown, initialize_slot_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_slot_dropdown) + end) +end +do + local dropdown = gui.dropdown(frame.filter) + quality_dropdown = dropdown + dropdown:SetPoint('TOPLEFT', slot_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) + dropdown:SetWidth(300) + local label = gui.label(dropdown, gui.font_size.small) + label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) + label:SetText('Min Quality') + UIDropDownMenu_Initialize(dropdown, initialize_quality_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_quality_dropdown) + end) +end +gui.vertical_line(frame.filter, 332) +do + local dropdown = gui.dropdown(frame.filter) + dropdown:SetPoint('TOPRIGHT', -174.5, -10) + dropdown:SetWidth(150) + UIDropDownMenu_Initialize(dropdown, initialize_filter_dropdown) + dropdown:SetScript('OnShow', function() + UIDropDownMenu_Initialize(this, initialize_filter_dropdown) + end) + _G[dropdown:GetName() .. 'Text']:Hide() + local label = gui.label(dropdown, gui.font_size.medium) + label:SetPoint('RIGHT', dropdown, 'LEFT', -15, 0) + label:SetText('Component') + filter_dropdown = dropdown +end +do + local input = gui.editbox(frame.filter) + input:SetPoint('CENTER', filter_dropdown, 'CENTER', 0, 0) + input:SetWidth(150) + input:SetScript('OnTabPressed', function() filter_parameter_input:SetFocus() end) + input.complete = completion.complete(function() return T.temp-T.list('and', 'or', 'not', unpack(aux.keys(filter_util.filters))) end) + input.char = function() this:complete() end + input.change = function() + local text = this:GetText() + if filter_util.filters[text] and filter_util.filters[text].input_type ~= '' then + local _, _, suggestions = filter_util.parse_filter_string(text .. '/') + filter_parameter_input:SetNumeric(filter_util.filters[text].input_type == 'number') + filter_parameter_input.complete = completion.complete(function() return suggestions or T.empty end) + filter_parameter_input:Show() + else + filter_parameter_input:Hide() + end + end + input.enter = function() + if filter_parameter_input:IsVisible() then + filter_parameter_input:SetFocus() + else + add_form_component() + end + end + filter_input = input +end +do + local input = gui.editbox(frame.filter) + input:SetPoint('LEFT', filter_dropdown, 'RIGHT', 10, 0) + input:SetWidth(150) + input:SetScript('OnTabPressed', function() + filter_input:SetFocus() + end) + input.char = function() this:complete() end + input.enter = add_form_component + input:Hide() + filter_parameter_input = input +end +do + local scroll_frame = CreateFrame('ScrollFrame', nil, frame.filter) + scroll_frame:SetWidth(395) + scroll_frame:SetHeight(270) + scroll_frame:SetPoint('TOPLEFT', 348.5, -50) + scroll_frame:EnableMouse(true) + scroll_frame:EnableMouseWheel(true) + scroll_frame:SetScript('OnMouseWheel', function() + local child = this:GetScrollChild() + child:SetFont('p', [[Fonts\ARIALN.TTF]], aux.bounded(gui.font_size.small, gui.font_size.large, aux.select(2, child:GetFont()) + arg1*2)) + update_filter_display() + end) + scroll_frame:RegisterForDrag('LeftButton') + scroll_frame:SetScript('OnDragStart', function() + this.x, this.y = GetCursorPosition() + this.x_offset, this.y_offset = this:GetHorizontalScroll(), this:GetVerticalScroll() + this.x_extra, this.y_extra = 0, 0 + this:SetScript('OnUpdate', function() + local x, y = GetCursorPosition() + local new_x_offset = this.x_offset + x - this.x + local new_y_offset = this.y_offset + y - this.y + + set_filter_display_offset(new_x_offset - this.x_extra, new_y_offset - this.y_extra) + + this.x_extra = max(this.x_extra, new_x_offset) + this.y_extra = min(this.y_extra, new_y_offset) + end) + end) + scroll_frame:SetScript('OnDragStop', function() + this:SetScript('OnUpdate', nil) + end) + gui.set_content_style(scroll_frame, -2, -2, -2, -2) + local scroll_child = CreateFrame('SimpleHTML', nil, scroll_frame) + scroll_frame:SetScrollChild(scroll_child) + scroll_child:SetFont('p', [[Fonts\ARIALN.TTF]], gui.font_size.large) + scroll_child:SetTextColor('p', aux.color.label.enabled()) + scroll_child:SetWidth(1) + scroll_child:SetHeight(1) + scroll_child:SetScript('OnHyperlinkClick', data_link_click) + scroll_child.measure = scroll_child:CreateFontString() + filter_display = scroll_child +end + +status_bars = {} +tables = {} +for _ = 1, 5 do + local status_bar = gui.status_bar(frame) + status_bar:SetAllPoints(status_bar_frame) + status_bar:Hide() + tinsert(status_bars, status_bar) + + local table = auction_listing.new(frame.results, 16, auction_listing.search_columns) + table:SetHandler('OnClick', function(row, button) + if IsAltKeyDown() then + if current_search().table:GetSelection().record == row.record then + if button == 'LeftButton' then + buyout_button:Click() + elseif button == 'RightButton' then + bid_button:Click() + end + end + elseif button == 'RightButton' then + aux.set_tab(1) + set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') + execute(nil, false) + end + end) + table:SetHandler('OnSelectionChanged', function(rt, datum) + bid_button:Disable() + buyout_button:Disable() + if not datum then return end + find_auction(datum.record) + end) + table:Hide() + tinsert(tables, table) +end + +favorite_searches_listing = listing.new(frame.saved.favorite) +favorite_searches_listing:SetColInfo{{name='Auto', width=.07, align='CENTER'}, {name='Favorite Searches', width=.93}} + +recent_searches_listing = listing.new(frame.saved.recent) +recent_searches_listing:SetColInfo{{name='Recent Searches', width=1}} + +for listing in T.temp-T.set(favorite_searches_listing, recent_searches_listing) do + for k, v in handlers do + listing:SetHandler(k, v) + end +end \ No newline at end of file diff --git a/aux-addon/tabs/search/results.lua b/aux-addon/tabs/search/results.lua new file mode 100644 index 0000000..6f9e863 --- /dev/null +++ b/aux-addon/tabs/search/results.lua @@ -0,0 +1,443 @@ +module 'aux.tabs.search' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local filter_util = require 'aux.util.filter' +local scan_util = require 'aux.util.scan' +local scan = require 'aux.core.scan' + +search_scan_id = 0 + +function aux.handle.LOAD() + new_search() +end + +function update_real_time(enable) + if enable then + range_button:Hide() + real_time_button:Show() + search_box:SetPoint('LEFT', real_time_button, 'RIGHT', 4, 0) + else + real_time_button:Hide() + range_button:Show() + search_box:SetPoint('LEFT', last_page_input, 'RIGHT', 4, 0) + end +end + +do + local searches = {} + local search_index = 1 + + function current_search() + return searches[search_index] + end + + function update_search(index) + searches[search_index].status_bar:Hide() + searches[search_index].table:Hide() + searches[search_index].table:SetSelectedRecord() + + search_index = index + + searches[search_index].status_bar:Show() + searches[search_index].table:Show() + + search_box:SetText(searches[search_index].filter_string or '') + first_page_input:SetText(searches[search_index].first_page and searches[search_index].first_page + 1 or '') + last_page_input:SetText(searches[search_index].last_page and searches[search_index].last_page + 1 or '') + if search_index == 1 then + previous_button:Disable() + else + previous_button:Enable() + end + if search_index == getn(searches) then + next_button:Hide() + range_button:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) + real_time_button:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) + else + next_button:Show() + range_button:SetPoint('LEFT', next_button, 'RIGHT', 4, 0) + real_time_button:SetPoint('LEFT', next_button, 'RIGHT', 4, 0) + end + update_real_time(searches[search_index].real_time) + update_start_stop() + update_continuation() + end + + function new_search(filter_string, first_page, last_page, real_time) + while getn(searches) > search_index do + tremove(searches) + end + local search = T.map('records', T.acquire(), 'filter_string', filter_string, 'first_page', first_page, 'last_page', last_page, 'real_time', real_time) + tinsert(searches, search) + if getn(searches) > 5 then + tremove(searches, 1) + tinsert(status_bars, tremove(status_bars, 1)) + tinsert(tables, tremove(tables, 1)) + search_index = 4 + end + + search.status_bar = status_bars[getn(searches)] + search.status_bar:update_status(1, 1) + search.status_bar:set_text('') + + search.table = tables[getn(searches)] + search.table:SetSort(1, 2, 3, 4, 5, 6, 7, 8, 9) + search.table:Reset() + search.table:SetDatabase(search.records) + + update_search(getn(searches)) + end + + function clear_control_focus() + search_box:ClearFocus() + first_page_input:ClearFocus() + last_page_input:ClearFocus() + end + + function previous_search() + clear_control_focus() + update_search(search_index - 1) + set_subtab(RESULTS) + end + + function next_search() + clear_control_focus() + update_search(search_index + 1) + set_subtab(RESULTS) + end +end + +function update_continuation() + if current_search().continuation then + resume_button:Show() + search_box:SetPoint('RIGHT', resume_button, 'LEFT', -4, 0) + else + resume_button:Hide() + search_box:SetPoint('RIGHT', start_button, 'LEFT', -4, 0) + end +end + +function discard_continuation() + scan.abort(search_scan_id) + current_search().continuation = nil + update_continuation() +end + +function update_start_stop() + if current_search().active then + stop_button:Show() + start_button:Hide() + else + start_button:Show() + stop_button:Hide() + end +end + +function start_real_time_scan(query, search, continuation) + + local ignore_page + if not search then + search = current_search() + query.blizzard_query.first_page = tonumber(continuation) or 0 + query.blizzard_query.last_page = tonumber(continuation) or 0 + ignore_page = not tonumber(continuation) + end + + local next_page + local new_records = T.acquire() + search_scan_id = scan.start{ + type = 'list', + queries = {query}, + auto_buy_validator = search.auto_buy_validator, + auto_bid_validator = search.auto_bid_validator, + on_scan_start = function() + search.status_bar:update_status(.9999, .9999) + search.status_bar:set_text('Scanning last page ...') + end, + on_page_loaded = function(_, _, last_page) + next_page = last_page + if last_page == 0 then + ignore_page = false + end + end, + on_auction = function(auction_record) + if not ignore_page then + tinsert(new_records, auction_record) + end + end, + on_complete = function() + local map = T.temp-T.acquire() + for _, record in search.records do + map[record.sniping_signature] = record + end + for _, record in new_records do + map[record.sniping_signature] = record + end + T.release(new_records) + new_records = aux.values(map) + + if getn(new_records) > 2000 then + StaticPopup_Show('AUX_SEARCH_TABLE_FULL') + else + search.records = new_records + search.table:SetDatabase(search.records) + end + + query.blizzard_query.first_page = next_page + query.blizzard_query.last_page = next_page + start_real_time_scan(query, search) + end, + on_abort = function() + search.status_bar:update_status(1, 1) + search.status_bar:set_text('Scan paused') + + search.continuation = next_page or not ignore_page and query.blizzard_query.first_page or true + + if current_search() == search then + update_continuation() + end + + search.active = false + update_start_stop() + end, + } +end + +function start_search(queries, continuation) + local current_query, current_page, total_queries, start_query, start_page + + local search = current_search() + + total_queries = getn(queries) + + if continuation then + start_query, start_page = unpack(continuation) + for i = 1, start_query - 1 do + tremove(queries, 1) + end + queries[1].blizzard_query.first_page = (queries[1].blizzard_query.first_page or 0) + start_page - 1 + search.table:SetSelectedRecord() + else + start_query, start_page = 1, 1 + end + + + search_scan_id = scan.start{ + type = 'list', + queries = queries, + auto_buy_validator = search.auto_buy_validator, + auto_bid_validator = search.auto_bid_validator, + on_scan_start = function() + search.status_bar:update_status(0, 0) + if continuation then + search.status_bar:set_text('Resuming scan...') + else + search.status_bar:set_text('Scanning auctions...') + end + end, + on_page_loaded = function(_, total_scan_pages) + current_page = current_page + 1 + total_scan_pages = total_scan_pages + (start_page - 1) + total_scan_pages = max(total_scan_pages, 1) + current_page = min(current_page, total_scan_pages) + search.status_bar:update_status((current_query - 1) / getn(queries), current_page / total_scan_pages) + search.status_bar:set_text(format('Scanning %d / %d (Page %d / %d)', current_query, total_queries, current_page, total_scan_pages)) + end, + on_page_scanned = function() + search.table:SetDatabase() + end, + on_start_query = function(query) + current_query = current_query and current_query + 1 or start_query + current_page = current_page and 0 or start_page - 1 + end, + on_auction = function(auction_record, ctrl) + if getn(search.records) < 2000 then + tinsert(search.records, auction_record) + if getn(search.records) == 2000 then + StaticPopup_Show('AUX_SEARCH_TABLE_FULL') + end + end + end, + on_complete = function() + search.status_bar:update_status(1, 1) + search.status_bar:set_text('Scan complete') + + if current_search() == search and frame.results:IsVisible() and getn(search.records) == 0 then + set_subtab(SAVED) + end + + search.active = false + update_start_stop() + end, + on_abort = function() + search.status_bar:update_status(1, 1) + search.status_bar:set_text('Scan paused') + + if current_query then + search.continuation = {current_query, current_page + 1} + else + search.continuation = {start_query, start_page} + end + if current_search() == search then + update_continuation() + end + + search.active = false + update_start_stop() + end, + } +end + +function M.execute(resume, real_time) + + if resume then + real_time = current_search().real_time + elseif real_time == nil then + real_time = real_time_button:IsShown() + end + + if resume then + search_box:SetText(current_search().filter_string) + end + local filter_string, first_page, last_page = search_box:GetText(), blizzard_page_index(first_page_input:GetText()), blizzard_page_index(last_page_input:GetText()) + + local queries, error = filter_util.queries(filter_string) + if not queries then + aux.print('Invalid filter:', error) + return + elseif real_time then + if getn(queries) > 1 then + aux.print('Error: The real time mode does not support multi-queries') + return + elseif queries[1].blizzard_query.first_page or queries[1].blizzard_query.last_page then + aux.print('Error: The real time mode does not support page ranges') + return + end + end + + if resume then + current_search().table:SetSelectedRecord() + else + if filter_string ~= current_search().filter_string then + if current_search().filter_string then + new_search(filter_string, first_page, last_page, real_time) + else + current_search().filter_string = filter_string + end + new_recent_search(filter_string, aux.join(aux.map(aux.copy(queries), function(filter) return filter.prettified end), ';')) + else + local search = current_search() + search.records = T.acquire() + search.table:Reset() + search.table:SetDatabase(search.records) + end + local search = current_search() + search.first_page = first_page + search.last_page = last_page + search.real_time = real_time + search.auto_buy_validator = get_auto_buy_validator() + search.auto_bid_validator = get_auto_bid_validator() + end + + local continuation = resume and current_search().continuation + discard_continuation() + current_search().active = true + update_start_stop() + clear_control_focus() + set_subtab(RESULTS) + if real_time then + start_real_time_scan(queries[1], nil, continuation) + else + for _, query in queries do + query.blizzard_query.first_page = current_search().first_page + query.blizzard_query.last_page = current_search().last_page + end + start_search(queries, continuation) + end +end + +do + local scan_id = 0 + local IDLE, SEARCHING, FOUND = aux.enum(3) + local state = IDLE + local found_index + + function find_auction(record) + local search = current_search() + + if not search.table:ContainsRecord(record) or info.is_player(record.owner) then + return + end + + scan.abort(scan_id) + state = SEARCHING + scan_id = scan_util.find( + record, + current_search().status_bar, + function() + state = IDLE + end, + function() + state = IDLE + search.table:RemoveAuctionRecord(record) + end, + function(index) + if search.table:GetSelection() and search.table:GetSelection().record ~= record then + return + end + + state = FOUND + found_index = index + + if not record.high_bidder then + bid_button:SetScript('OnClick', function() + if scan_util.test(record, index) and search.table:ContainsRecord(record) then + aux.place_bid('list', index, record.bid_price, record.bid_price < record.buyout_price and function() + info.bid_update(record) + search.table:SetDatabase() + end or function() search.table:RemoveAuctionRecord(record) end) + end + end) + bid_button:Enable() + else + bid_button:Disable() + end + + if record.buyout_price > 0 then + buyout_button:SetScript('OnClick', function() + if scan_util.test(record, index) and search.table:ContainsRecord(record) then + aux.place_bid('list', index, record.buyout_price, function() search.table:RemoveAuctionRecord(record) end) + end + end) + buyout_button:Enable() + else + buyout_button:Disable() + end + end + ) + end + + function on_update() + if state == IDLE or state == SEARCHING then + buyout_button:Disable() + bid_button:Disable() + end + + if state == SEARCHING then return end + + local selection = current_search().table:GetSelection() + if not selection then + state = IDLE + elseif selection and state == IDLE then + find_auction(selection.record) + elseif state == FOUND and not scan_util.test(selection.record, found_index) then + buyout_button:Disable() + bid_button:Disable() + if not aux.bid_in_progress() then + state = IDLE + end + end + end +end \ No newline at end of file diff --git a/aux-addon/tabs/search/saved.lua b/aux-addon/tabs/search/saved.lua new file mode 100644 index 0000000..8089e79 --- /dev/null +++ b/aux-addon/tabs/search/saved.lua @@ -0,0 +1,181 @@ +module 'aux.tabs.search' + +local T = require 'T' +local aux = require 'aux' +local filter_util = require 'aux.util.filter' +local gui = require 'aux.gui' + +function aux.handle.LOAD() + recent_searches, favorite_searches = aux.realm_data.recent_searches, aux.realm_data.favorite_searches +end + +function update_search_listings() + local favorite_search_rows = T.acquire() + for i = 1, getn(favorite_searches) do + local search = favorite_searches[i] + local name = strsub(search.prettified, 1, 250) + tinsert(favorite_search_rows, T.map( + 'cols', T.list(T.map('value', search.auto_buy and aux.color.red'X' or search.auto_bid and aux.color.red'Y' or ''), T.map('value', name)), + 'search', search, + 'index', i + )) + end + favorite_searches_listing:SetData(favorite_search_rows) + + local recent_search_rows = T.acquire() + for i = 1, getn(recent_searches) do + local search = recent_searches[i] + local name = strsub(search.prettified, 1, 250) + tinsert(recent_search_rows, T.map( + 'cols', T.list(T.map('value', name)), + 'search', search, + 'index', i + )) + end + recent_searches_listing:SetData(recent_search_rows) +end + +function new_recent_search(filter_string, prettified) + for i = getn(recent_searches), 1, -1 do + if recent_searches[i].filter_string == filter_string then + tremove(recent_searches, i) + end + end + tinsert(recent_searches, 1, T.map( + 'filter_string', filter_string, + 'prettified', prettified + )) + while getn(recent_searches) > 50 do + tremove(recent_searches) + end + update_search_listings() +end + +handlers = { + OnClick = function(st, data, _, button) + if not data then return end + if button == 'LeftButton' and IsShiftKeyDown() then + set_filter(data.search.filter_string) + elseif button == 'RightButton' and IsShiftKeyDown() then + add_filter(data.search.filter_string) + elseif button == 'LeftButton' then + set_filter(data.search.filter_string) + execute() + elseif button == 'RightButton' then + local u = update_search_listings + if st == recent_searches_listing then + tinsert(favorite_searches, 1, data.search) + u(d) + elseif st == favorite_searches_listing then + local auto_buy = data.search.auto_buy + local auto_bid = data.search.auto_bid + gui.menu( + (auto_buy and 'Disable' or 'Enable') .. ' Auto Buy', function() if auto_buy then data.search.auto_buy = nil else enable_auto_buy(data.search) end u() end, + (auto_bid and 'Disable' or 'Enable') .. ' Auto Bid', function() if auto_bid then data.search.auto_bid = nil else enable_auto_bid(data.search) end u() end, + 'Move Up', function() move_up(favorite_searches, data.index); u() end, + 'Move Down', function() move_down(favorite_searches, data.index); u() end, + 'Delete', function() tremove(favorite_searches, data.index); u() end + ) + end + end + end, + OnEnter = function(st, data, self) + if not data then return end + GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) + GameTooltip:AddLine(gsub(data.search.prettified, ';', '\n\n'), 255/255, 254/255, 250/255, true) + GameTooltip:Show() + end, + OnLeave = function() + GameTooltip:ClearLines() + GameTooltip:Hide() + end +} + +function get_auto_buy_validator() + local validators = T.acquire() + for _, search in favorite_searches do + if search.auto_buy then + local queries, error = filter_util.queries(search.filter_string) + if queries then + tinsert(validators, queries[1].validator) + else + aux.print('Invalid auto buy filter:', error) + end + end + end + return function(record) + return aux.any(validators, function(validator) return validator(record) end) + end +end + +function get_auto_bid_validator() + local validators = T.acquire() + for _, search in favorite_searches do + if search.auto_bid then + local queries, error = filter_util.queries(search.filter_string) + if queries then + tinsert(validators, queries[1].validator) + else + aux.print('Invalid auto bid filter:', error) + end + end + end + return function(record) + return aux.any(validators, function(validator) return validator(record) end) + end +end + +function add_favorite(filter_string) + local queries, error = filter_util.queries(filter_string) + if queries then + tinsert(favorite_searches, 1, T.map( + 'filter_string', filter_string, + 'prettified', aux.join(aux.map(queries, function(query) return query.prettified end), ';') + )) + update_search_listings() + else + aux.print('Invalid filter:', error) + end +end + +function enable_auto_buy(search) + local queries, error = filter_util.queries(search.filter_string) + if queries then + if getn(queries) > 1 then + aux.print('Error: Auto Buy does not support multi-queries') + elseif aux.size(queries[1].blizzard_query) > 0 and not filter_util.parse_filter_string(search.filter_string).blizzard.exact then + aux.print('Error: Auto Buy does not support Blizzard filters') + else + search.auto_buy = true + end + else + aux.print('Invalid filter:', error) + end +end + +function enable_auto_bid(search) + local queries, error = filter_util.queries(search.filter_string) + if queries then + if getn(queries) > 1 then + aux.print('Error: Auto Bid does not support multi-queries') + elseif aux.size(queries[1].blizzard_query) > 0 and not filter_util.parse_filter_string(search.filter_string).blizzard.exact then + aux.print('Error: Auto Bid does not support Blizzard filters') + else + search.auto_bid = true + end + else + aux.print('Invalid filter:', error) + end +end + +function move_up(list, index) + if list[index - 1] then + list[index], list[index - 1] = list[index - 1], list[index] + end +end + +function move_down(list, index) + if list[index + 1] then + list[index], list[index + 1] = list[index + 1], list[index] + end +end \ No newline at end of file diff --git a/aux-addon/util.lua b/aux-addon/util.lua new file mode 100644 index 0000000..9de2094 --- /dev/null +++ b/aux-addon/util.lua @@ -0,0 +1,188 @@ +module 'aux' + +local T = require 'T' + +M.immutable = setmetatable(T.acquire(), { + __metatable = false, + __newindex = pass, + __sub = function(_, t) + return setmetatable(T.acquire(), T.map('__metatable', false, '__newindex', pass, '__index', t)) + end +}) + +function M.assign(t1, t2) + for k, v in t2 do + if t1[k] == nil then + t1[k] = v + end + end + return t1 +end + +function M.enum(n) + if n > 0 then return immutable-{}, enum(n - 1) end +end + +M.select = T.vararg-function(arg) + for _ = 1, arg[1] do + tremove(arg, 1) + end + if getn(arg) == 0 then + return nil + else + return unpack(arg) + end +end + +M.join = table.concat + +M.index = T.vararg-function(arg) + local t = tremove(arg, 1) + for _, v in ipairs(arg) do + t = t and t[v] + end + return t +end + +M.huge = 1/0 + +function M.modified() + return IsShiftKeyDown() or IsControlKeyDown() or IsAltKeyDown() +end + +function M.copy(t) + local copy = T.acquire() + for k, v in t do + copy[k] = v + end + table.setn(copy, getn(t)) + return setmetatable(copy, getmetatable(t)) +end + +function M.size(t) + local size = 0 + for _ in t do + size = size + 1 + end + return size +end + +function M.key(t, value) + for k, v in t do + if v == value then + return k + end + end +end + +function M.keys(t) + local keys = T.acquire() + for k in t do + tinsert(keys, k) + end + return keys +end + +function M.values(t) + local values = T.acquire() + for _, v in t do + tinsert(values, v) + end + return values +end + +function M.eq(t1, t2) + if not t1 or not t2 then return false end + for key, value in t1 do + if t2[key] ~= value then return false end + end + for key, value in t2 do + if t1[key] ~= value then return false end + end + return true +end + +function M.any(t, predicate) + for _, v in t do + if predicate then + if predicate(v) then return true end + elseif v then + return true + end + end + return false +end + +function M.all(t, predicate) + for _, v in t do + if predicate then + if not predicate(v) then return false end + elseif not v then + return false + end + end + return true +end + +function M.filter(t, predicate) + for k, v in t do + if not predicate(v, k) then t[k] = nil end + end + return t +end + +function M.map(t, f) + for k, v in t do + t[k] = f(v, k) + end + return t +end + +function M.trim(str) + return gsub(str, '^%s*(.-)%s*$', '%1') +end + +function M.split(str, separator) + local parts = T.acquire() + while true do + local start_index = strfind(str, separator, 1, true) + if start_index then + local part = strsub(str, 1, start_index - 1) + tinsert(parts, part) + str = strsub(str, start_index + 1) + else + local part = strsub(str, 1) + tinsert(parts, part) + return parts + end + end +end + +function M.tokenize(str) + local tokens = T.acquire() + for token in string.gfind(str, '%S+') do tinsert(tokens, token) end + return tokens +end + +function M.bounded(lower_bound, upper_bound, number) + return max(lower_bound, min(upper_bound, number)) +end + +function M.round(x) + return floor(x + .5) +end + +function M.later(t, t0) + t0 = t0 or GetTime() + return function() return GetTime() - t0 > t end +end + +function M.signal() + local params + return T.vararg-function(arg) + T.static(arg) + params = arg + end, function() + return params + end +end \ No newline at end of file diff --git a/aux-addon/util/completion.lua b/aux-addon/util/completion.lua new file mode 100644 index 0000000..40b9b78 --- /dev/null +++ b/aux-addon/util/completion.lua @@ -0,0 +1,45 @@ +module 'aux.util.completion' + +local aux = require 'aux' +local filter_util = require 'aux.util.filter' + +function M:complete_filter() + if IsControlKeyDown() then -- TODO problem is ctrl-v, maybe find a better solution + return + end + + local filter_string = this:GetText() + + local completed_filter_string = aux.select(3, strfind(filter_string, '([^;]*)/[^/;]*$')) + local _, suggestions = filter_util.query(completed_filter_string) + + local start_index, _, current_modifier = strfind(filter_string, '([^/;]*)$') + current_modifier = current_modifier or '' + + for _, suggestion in ipairs(suggestions) do + if strsub(strupper(suggestion), 1, strlen(current_modifier)) == strupper(current_modifier) then + this:SetText(strlower(strsub(filter_string, 1, start_index - 1) .. suggestion)) + this:HighlightText(strlen(filter_string), -1) + return + end + end +end + +function M.complete(candidates) + return function(self) + if IsControlKeyDown() then -- TODO problem is ctrl-v, maybe find a better solution + return + end + + local text = self:GetText() + + local t = candidates() + for i = 1, getn(t) do + if strsub(strupper(t[i]), 1, strlen(text)) == strupper(text) then + self:SetText(strlower(t[i])) + self:HighlightText(strlen(text), -1) + return + end + end + end +end \ No newline at end of file diff --git a/aux-addon/util/filter.lua b/aux-addon/util/filter.lua new file mode 100644 index 0000000..bb7092e --- /dev/null +++ b/aux-addon/util/filter.lua @@ -0,0 +1,549 @@ +module 'aux.util.filter' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local money = require 'aux.util.money' +local filter_util = require 'aux.util.filter' +local history = require 'aux.core.history' +local disenchant = require 'aux.core.disenchant' + +function default_filter(str) + return { + input_type = '', + validator = function() + return function(auction_record) + return aux.any(auction_record.tooltip, function(entry) + return strfind(strlower(entry.left_text or ''), str, 1, true) or strfind(strlower(entry.right_text or ''), str, 1, true) + end) + end + end, + } +end + +M.filters = { + + ['utilizable'] = { + input_type = '', + validator = function() + return function(auction_record) + return auction_record.usable and not info.tooltip_match(ITEM_SPELL_KNOWN, auction_record.tooltip) + end + end, + }, + + ['tooltip'] = { + input_type = 'string', + validator = function(str) + return default_filter(str).validator() + end, + }, + + ['item'] = { + input_type = 'string', + validator = function(name) + return function(auction_record) + return strlower(info.item(auction_record.item_id).name) == name + end + end + }, + + ['left'] = { + input_type = T.list('30m', '2h', '8h', '24h'), + validator = function(index) + return function(auction_record) + return auction_record.duration == index + end + end + }, + + ['rarity'] = { + input_type = T.list('poor', 'common', 'uncommon', 'rare', 'epic'), + validator = function(index) + return function(auction_record) + return auction_record.quality == index - 1 + end + end + }, + + ['min-level'] = { + input_type = 'number', + validator = function(level) + return function(auction_record) + return auction_record.level >= level + end + end + }, + + ['max-level'] = { + input_type = 'number', + validator = function(level) + return function(auction_record) + return auction_record.level <= level + end + end + }, + + ['bid-price'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.unit_bid_price <= amount + end + end + }, + + ['price'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.buyout_price > 0 and auction_record.unit_buyout_price <= amount + end + end + }, + + ['bid-percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return history.value(auction_record.item_key) and auction_record.unit_bid_price / history.value(auction_record.item_key) * 100 <= pct + end + end + }, + + ['percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return auction_record.unit_buyout_price > 0 + and history.value(auction_record.item_key) + and auction_record.unit_buyout_price / history.value(auction_record.item_key) * 100 <= pct + end + end + }, + + ['bid-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return history.value(auction_record.item_key) and history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.bid_price >= amount + end + end + }, + + ['profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + return auction_record.buyout_price > 0 and history.value(auction_record.item_key) and history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.buyout_price >= amount + end + end + }, + + ['bid-disenchant-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) + return disenchant_value and disenchant_value - auction_record.bid_price >= amount + end + end + }, + + ['disenchant-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) + return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount + end + end + }, + + ['bid-vendor-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local vendor_price = info.merchant_info(auction_record.item_id) + return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount + end + end + }, + + ['vendor-profit'] = { + input_type = 'money', + validator = function(amount) + return function(auction_record) + local vendor_price = info.merchant_info(auction_record.item_id) + return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount + end + end + }, + + ['seller'] = { + input_type = 'string', + validator = function(name) + return function(auction_record) + return auction_record.owner and strupper(name) == strupper(auction_record.owner) + end + end + }, +} + +function operator(str) + local operator = str == 'not' and T.list('operator', 'not', 1) + for name in T.temp-T.set('and', 'or') do + local arity = aux.select(3, strfind(str, '^' .. name .. '(%d*)$')) + if arity then + arity = tonumber(arity) + operator = not (arity and arity < 2) and T.list('operator', name, arity) + end + end + return operator or nil +end + +do + local mt = { + __call = function(self, str, i) + if not str then + self.max_level = self.max_level or self.min_level + return self + end + if self.exact then return end + local number = tonumber(aux.select(3, strfind(str, '^(%d+)$'))) + if number then + if number >= 1 and number <= 60 then + for _, key in ipairs(T.temp-T.list('min_level', 'max_level')) do + if not self[key] then + self[key] = T.list(str, number) + return T.list('blizzard', key, str, number) + end + end + end + end + for _, parser in T.temp-T.list( + T.temp-T.list('class', info.item_class_index), + T.temp-T.list('subclass', T.vararg-function(arg) return info.item_subclass_index(aux.index(self.class, 2) or 0, unpack(arg)) end), + T.temp-T.list('slot', T.vararg-function(arg) return info.item_slot_index(aux.index(self.class, 2) == 2 and 2 or 0, aux.index(self.subclass, 2) or 0, unpack(arg)) end), + T.temp-T.list('quality', info.item_quality_index) + ) do + if not self[parser[1]] then + tinsert(parser, str) + local index, label = parser[2](aux.select(3, unpack(parser))) + if index then + self[parser[1]] = T.list(label, index) + return T.list('blizzard', parser[1], label, index) + end + end + end + if not self[str] and (str == 'usable' or str == 'exact' and self.name and aux.size(self) == 1) then + self[str] = T.list(str, 1) + return T.list('blizzard', str, str, 1) + elseif i == 1 and strlen(str) <= 63 then + self.name = unquote(str) + return T.list('blizzard', 'name', unquote(str), str) +-- return nil, 'The name filter must not be longer than 63 characters' TODO + end + end, + } + function blizzard_filter_parser() + return setmetatable(T.acquire(), mt) + end +end + +function parse_parameter(input_type, str) + if input_type == 'money' then + local money = money.from_string(str) + return money and money > 0 and money or nil + elseif input_type == 'number' then + local number = tonumber(str) + return number and number > 0 and mod(number, 1) == 0 and number or nil + elseif input_type == 'string' then + return str ~= '' and str or nil + elseif type(input_type) == 'table' then + return aux.key(input_type, str) + end +end + +function M.parse_filter_string(str) + local filter, post_filter = T.acquire(), T.acquire() + local blizzard_filter_parser = blizzard_filter_parser() + + local parts = str and aux.map(aux.split(str, '/'), function(part) return strlower(aux.trim(part)) end) or T.acquire() + + local i = 1 + while parts[i] do + local operator = operator(parts[i]) + if operator then + tinsert(post_filter, operator) + tinsert(filter, operator) + elseif filters[parts[i]] then + local input_type = filters[parts[i]].input_type + if input_type ~= '' then + if not parts[i + 1] or not parse_parameter(input_type, parts[i + 1]) then + if parts[i] == 'item' then + return nil, 'Invalid item name', aux.account_data.auctionable_items + elseif type(input_type) == 'table' then + return nil, 'Invalid choice for ' .. parts[i], input_type + else + return nil, 'Invalid input for ' .. parts[i] .. '. Expecting: ' .. input_type + end + end + tinsert(post_filter, T.list('filter', parts[i], parts[i + 1])) + i = i + 1 + else + tinsert(post_filter, T.list('filter', parts[i])) + end + tinsert(filter, post_filter[getn(post_filter)]) + else + local part = blizzard_filter_parser(parts[i], i) + if part then + tinsert(filter, part) + elseif parts[i] ~= '' then + tinsert(post_filter, T.list('filter', 'tooltip', parts[i])) + tinsert(filter, post_filter[getn(post_filter)]) + else + return nil, 'Empty modifier' + end + end + i = i + 1 + end + + return T.map('components', filter, 'blizzard', blizzard_filter_parser(), 'post', post_filter) +end + +function M.query(filter_string) + local filter, error, suggestions = parse_filter_string(filter_string) + + if not filter then + return nil, suggestions or T.acquire(), error + end + + local polish_notation_counter = 0 + for _, component in ipairs(filter.post) do + if component[1] == 'operator' then + polish_notation_counter = max(polish_notation_counter, 1) + polish_notation_counter = polish_notation_counter + (tonumber(component[2]) or 1) - 1 + elseif component[1] == 'filter' then + polish_notation_counter = polish_notation_counter - 1 + end + end + + if polish_notation_counter > 0 then + local suggestions = T.acquire() + for key in filters do + tinsert(suggestions, strlower(key)) + end + tinsert(suggestions, 'and') + tinsert(suggestions, 'or') + tinsert(suggestions, 'not') + return nil, suggestions, 'Malformed expression' + end + + return { + blizzard_query = blizzard_query(filter), + validator = validator(filter), + prettified = prettified_filter_string(filter), + }, _M.suggestions(filter) +end + +function M.queries(filter_string) + local parts = aux.split(filter_string, ';') + local queries = T.acquire() + for _, str in ipairs(parts) do + local query, _, error = query(str) + if not query then + return nil, error + else + tinsert(queries, query) + end + end + return queries +end + +function suggestions(filter) + local suggestions = T.acquire() + + if filter.blizzard.name and aux.size(filter.blizzard) == 1 then tinsert(suggestions, 'exact') end + + tinsert(suggestions, 'and'); tinsert(suggestions, 'or'); tinsert(suggestions, 'not'); tinsert(suggestions, 'tooltip') + + for key in filters do tinsert(suggestions, key) end + + -- classes + if not filter.blizzard.class then + for _, class in ipairs(T.temp-T.list(GetAuctionItemClasses())) do tinsert(suggestions, class) end + end + + -- subclasses + if not filter.blizzard.subclass then + for _, subclass in ipairs(T.temp-T.list(GetAuctionItemSubClasses(aux.index(filter.blizzard.class, 2) or 0))) do + tinsert(suggestions, subclass) + end + end + + -- slots + if not filter.blizzard.slot then + for _, invtype in ipairs(T.temp-T.list(GetAuctionInvTypes(aux.index(filter.blizzard.class, 2) == 2 and 2 or 0, aux.index(filter.blizzard.subclass, 2) or 0))) do + tinsert(suggestions, _G[invtype]) + end + end + + -- usable + if not filter.blizzard.usable then tinsert(suggestions, 'usable') end + + -- rarities + if not filter.blizzard.quality then + for i = 0, 4 do tinsert(suggestions, _G['ITEM_QUALITY' .. i .. '_DESC']) end + end + + -- item names + if getn(filter.components) == 0 then + for _, name in ipairs(aux.account_data.auctionable_items) do + tinsert(suggestions, name .. '/exact') + end + end + + return suggestions +end + +function M.filter_string(components) + local query_builder = query_builder() + for _, component in ipairs(components) do + if component[1] == 'blizzard' then + query_builder.append(component[4] or component[3]) + elseif component[1] == 'operator' then + query_builder.append(component[2] .. (component[2] ~= 'not' and tonumber(component[3]) or '')) + elseif component[1] == 'filter' then + query_builder.append(component[2]) + local parameter = component[3] + if parameter then + if filter_util.filters[component[2]].input_type == 'money' then + parameter = money.to_string(money.from_string(parameter), nil, true, nil, true) + end + query_builder.append(parameter) + end + end + end + return query_builder.get() +end + +function prettified_filter_string(filter) + local prettified = query_builder() + for i, component in ipairs(filter.components) do + if component[1] == 'blizzard' then + if component[2] == 'name' then + if filter.blizzard.exact then + prettified.append(info.display_name(info.item_id(component[3])) or aux.color.orange('[' .. component[3] .. ']')) + elseif component[3] ~= '' then + prettified.append(aux.color.label.enabled(component[3])) + end + elseif component[2] ~= 'exact' then + prettified.append(aux.color.orange(component[3])) + end + elseif component[1] == 'operator' then + prettified.append(aux.color.orange(component[2] .. (component[2] ~= 'not' and tonumber(component[3]) or ''))) + elseif component[1] == 'filter' then + if i == 1 or component[2] ~= 'tooltip' then + prettified.append(aux.color.orange(component[2])) + end + local parameter = component[3] + if parameter then + if component[2] == 'item' then + prettified.append(info.display_name(info.item_id(parameter)) or aux.color.label.enabled('[' .. parameter .. ']')) + else + if filters[component[2]].input_type == 'money' then + prettified.append(money.to_string(money.from_string(parameter), nil, true, aux.color.label.enabled)) + else + prettified.append(aux.color.label.enabled(parameter)) + end + end + end + end + end + if prettified.get() == '' then + return aux.color.orange'<>' + else + return prettified.get() + end +end + +function M.quote(name) + return '<' .. name .. '>' +end + +function M.unquote(name) + return aux.select(3, strfind(name, '^<(.*)>$')) or name +end + +function blizzard_query(filter) + local filters = filter.blizzard + local query = T.map('name', filters.name) + local item_id = filters.name and info.item_id(filters.name) + local item_info = item_id and info.item(item_id) + if filters.exact and item_info then + local class_index = info.item_class_index(item_info.class) + local subclass_index = info.item_subclass_index(class_index or 0, item_info.subclass) + local slot_index = info.item_slot_index(class_index or 0, subclass_index or 0, item_info.slot) + query.min_level = item_info.level + query.max_level = item_info.level + query.usable = item_info.usable + query.class = class_index + query.subclass = subclass_index + query.slot = slot_index + query.quality = item_info.quality + else + for key in T.temp-T.set('min_level', 'max_level', 'class', 'subclass', 'slot', 'usable', 'quality') do + query[key] = aux.index(filters[key], 2) + end + end + return query +end + +function validator(filter) + local validators = T.acquire() + for i, component in filter.post do + local type, name, param = unpack(component) + if type == 'filter' then + validators[i] = filters[name].validator(parse_parameter(filters[name].input_type, param)) + end + end + return function(record) + if filter.blizzard.exact and strlower(info.item(record.item_id).name) ~= filter.blizzard.name then + return false + end + local stack = T.temp-T.acquire() + for i = getn(filter.post), 1, -1 do + local type, name, param = unpack(filter.post[i]) + if type == 'operator' then + local args = T.temp-T.acquire() + while (not param or param > 0) and getn(stack) > 0 do + tinsert(args, tremove(stack)) + param = param and param - 1 + end + if name == 'not' then + tinsert(stack, not args[1]) + elseif name == 'and' then + tinsert(stack, aux.all(args)) + elseif name == 'or' then + tinsert(stack, aux.any(args)) + end + elseif type == 'filter' then + tinsert(stack, not not validators[i](record)) + end + end + return aux.all(stack) + end +end + +function M.query_builder() + local filter + return T.map( + 'append', function(part) + filter = not filter and part or filter .. '/' .. part + end, + 'get', function() + return filter or '' + end + ) +end \ No newline at end of file diff --git a/aux-addon/util/info.lua b/aux-addon/util/info.lua new file mode 100644 index 0000000..76693dc --- /dev/null +++ b/aux-addon/util/info.lua @@ -0,0 +1,443 @@ +module 'aux.util.info' + +local T = require 'T' +local aux = require 'aux' + +CreateFrame('GameTooltip', 'AuxTooltip', nil, 'GameTooltipTemplate') +AuxTooltip:SetScript('OnTooltipAddMoney', function() + this.money = arg1 +end) + +do + local inventory_index_map = { + INVTYPE_AMMO = {0}, + INVTYPE_HEAD = {1}, + INVTYPE_NECK = {2}, + INVTYPE_SHOULDER = {3}, + INVTYPE_BODY = {4}, + INVTYPE_CHEST = {5}, + INVTYPE_ROBE = {5}, + INVTYPE_WAIST = {6}, + INVTYPE_LEGS = {7}, + INVTYPE_FEET = {8}, + INVTYPE_WRIST = {9}, + INVTYPE_HAND = {10}, + INVTYPE_FINGER = {11, 12}, + INVTYPE_TRINKET = {13, 14}, + INVTYPE_CLOAK = {15}, + INVTYPE_2HWEAPON = {16, 17}, + INVTYPE_WEAPONMAINHAND = {16, 17}, + INVTYPE_WEAPON = {16, 17}, + INVTYPE_WEAPONOFFHAND = {16, 17}, + INVTYPE_HOLDABLE = {16, 17}, + INVTYPE_SHIELD = {16, 17}, + INVTYPE_RANGED = {18}, + INVTYPE_RANGEDRIGHT = {18}, + INVTYPE_TABARD = {19}, + } + function M.inventory_index(slot) + return unpack(inventory_index_map[slot] or T.temp-T.acquire()) + end +end + +function M.container_item(bag, slot) + local link = GetContainerItemLink(bag, slot) + if link then + local item_id, suffix_id, unique_id, enchant_id = parse_link(link) + local item_info = T.temp-item(item_id, suffix_id, unique_id, enchant_id) + + local texture, count, locked, quality, readable, lootable = GetContainerItemInfo(bag, slot) -- quality not working? + local tooltip, tooltip_money = tooltip('bag', bag, slot) + local max_charges = max_item_charges(item_id) + local charges = max_charges and item_charges(tooltip) + local aux_quantity = charges or count + return T.map( + 'item_id', item_id, + 'suffix_id', suffix_id, + 'unique_id', unique_id, + 'enchant_id', enchant_id, + + 'link', link, + 'itemstring', item_info.itemstring, + 'item_key', item_id .. ':' .. suffix_id, + + 'name', item_info.name, + 'texture', texture, + 'level', item_info.level, + 'type', item_info.type, + 'subtype', item_info.subtype, + 'slot', item_info.slot, + 'quality', item_info.quality, + 'max_stack', item_info.max_stack, + + 'count', count, + 'locked', locked, + 'readable', readable, + 'lootable', lootable, + + 'tooltip', tooltip, + 'tooltip_money', tooltip_money, + 'max_charges', max_charges, + 'charges', charges, + 'aux_quantity', aux_quantity + ) + end +end + +function M.auction_sell_item() + for name, texture, count, quality, usable, vendor_price in GetAuctionSellItemInfo do + return T.map( + 'name', name, + 'texture', texture, + 'quality', quality, + 'count', count, + 'usable', usable, + 'vendor_price', vendor_price -- it seems for charge items this is always the price for full charges + ) + end +end + +function M.auction(index, query_type) + query_type = query_type or 'list' + + local link = GetAuctionItemLink(query_type, index) + if link then + local item_id, suffix_id, unique_id, enchant_id = parse_link(link) + local item_info = T.temp-item(item_id, suffix_id, unique_id, enchant_id) + + local name, texture, count, quality, usable, level, start_price, min_increment, buyout_price, high_bid, high_bidder, owner, sale_status = GetAuctionItemInfo(query_type, index) + + local duration = GetAuctionItemTimeLeft(query_type, index) + local tooltip, tooltip_money = tooltip('auction', query_type, index) + local max_charges = max_item_charges(item_id) + local charges = max_charges and item_charges(tooltip) + local aux_quantity = charges or count + local blizzard_bid = high_bid > 0 and high_bid or start_price + local bid_price = high_bid > 0 and (high_bid + min_increment) or start_price + + return T.map( + 'item_id', item_id, + 'suffix_id', suffix_id, + 'unique_id', unique_id, + 'enchant_id', enchant_id, + + 'link', link, + 'itemstring', item_info.itemstring, + 'item_key', item_id .. ':' .. suffix_id, + 'search_signature', aux.join(T.temp-T.list(item_id, suffix_id, enchant_id, start_price, buyout_price, bid_price, aux_quantity, duration, query_type == 'owner' and high_bidder or (high_bidder and 1 or 0), aux.account_data.ignore_owner and (is_player(owner) and 0 or 1) or (owner or '?')), ':'), + 'sniping_signature', aux.join(T.temp-T.list(item_id, suffix_id, enchant_id, start_price, buyout_price, aux_quantity, aux.account_data.ignore_owner and (is_player(owner) and 0 or 1) or (owner or '?')), ':'), + + 'name', name, + 'texture', texture, + 'level', item_info.level, + 'type', item_info.type, + 'subtype', item_info.subtype, + 'slot', item_info.slot, + 'quality', quality, + 'max_stack', item_info.max_stack, + + 'count', count, + 'start_price', start_price, + 'high_bid', high_bid, + 'min_increment', min_increment, + 'blizzard_bid', blizzard_bid, + 'bid_price', bid_price, + 'buyout_price', buyout_price, + 'unit_blizzard_bid', blizzard_bid / aux_quantity, + 'unit_bid_price', bid_price / aux_quantity, + 'unit_buyout_price', buyout_price / aux_quantity, + 'high_bidder', high_bidder, + 'owner', owner, + 'sale_status', sale_status, + 'duration', duration, + 'usable', usable, + + 'tooltip', tooltip, + 'tooltip_money', tooltip_money, + 'max_charges', max_charges, + 'charges', charges, + 'aux_quantity', aux_quantity + ) + end +end + +function M.bid_update(auction_record) + auction_record.high_bid = auction_record.bid_price + auction_record.blizzard_bid = auction_record.bid_price + auction_record.min_increment = max(1, floor(auction_record.bid_price / 100) * 5) + auction_record.bid_price = auction_record.bid_price + auction_record.min_increment + auction_record.unit_blizzard_bid = auction_record.blizzard_bid / auction_record.aux_quantity + auction_record.unit_bid_price = auction_record.bid_price / auction_record.aux_quantity + auction_record.high_bidder = 1 + auction_record.search_signature = aux.join(T.temp-T.list(auction_record.item_id, auction_record.suffix_id, auction_record.enchant_id, auction_record.start_price, auction_record.buyout_price, auction_record.bid_price, auction_record.aux_quantity, auction_record.duration, 1, aux.account_data.ignore_owner and (is_player(auction_record.owner) and 0 or 1) or (auction_record.owner or '?')), ':') +end + +function M.set_tooltip(itemstring, owner, anchor) + GameTooltip:SetOwner(owner, anchor) + GameTooltip:SetHyperlink(itemstring) +end + +function M.set_shopping_tooltip(slot) + local index1, index2 = inventory_index(slot) + local tooltips = T.temp-T.acquire() + if index1 then + local tooltip = tooltip('inventory', 'player', index1) + if getn(tooltip) > 0 then + tinsert(tooltips, tooltip) + end + end + if index2 then + local tooltip = tooltip('inventory', 'player', index2) + if getn(tooltip) > 0 then + tinsert(tooltips, tooltip) + end + end + + if tooltips[1] then + tinsert(tooltips[1], 1, T.temp-T.map('left_text', 'Currently Equipped', 'left_color', T.temp-T.list(.5, .5, .5))) + ShoppingTooltip1:SetOwner(GameTooltip, 'ANCHOR_NONE') + ShoppingTooltip1:SetPoint('TOPLEFT', GameTooltip, 'TOPRIGHT', 0, -10) + load_tooltip(ShoppingTooltip1, tooltips[1]) + end + + if tooltips[2] then + tinsert(tooltips[2], 1, T.temp-T.map('left_text', 'Currently Equipped', 'left_color', T.temp-T.list(.5, .5, .5))) + ShoppingTooltip2:SetOwner(ShoppingTooltip1, 'ANCHOR_NONE') + ShoppingTooltip2:SetPoint('TOPLEFT', ShoppingTooltip1, 'TOPRIGHT') + load_tooltip(ShoppingTooltip2, tooltips[2]) + end +end + +function M.tooltip_match(entry, tooltip) + return aux.any(tooltip, function(line) + local left_match = line.left_text and strupper(line.left_text) == strupper(entry) + local right_match = line.right_text and strupper(line.right_text) == strupper(entry) + return left_match or right_match + end) +end + +function M.tooltip_find(pattern, tooltip) + local count = 0 + for _, line in tooltip do + if line.left_text and strfind(line.left_text, pattern) then + count = count + 1 + end + if line.right_text and strfind(line.right_text, pattern) then + count = count + 1 + end + end + return count +end + +function M.load_tooltip(frame, tooltip) + frame:ClearLines() + for _, line in ipairs(tooltip) do + if line.right_text then + frame:AddDoubleLine(line.left_text, line.right_text, line.left_color[1], line.left_color[2], line.left_color[3], line.right_color[1], line.right_color[2], line.right_color[3]) + else + frame:AddLine(line.left_text, line.left_color[1], line.left_color[2], line.left_color[3], true) + end + end + for i = 1, getn(tooltip) do -- TODO why is this needed? + _G[frame:GetName() .. 'TextLeft' .. i]:SetJustifyH('LEFT') + _G[frame:GetName() .. 'TextRight' .. i]:SetJustifyH('LEFT') + end + frame:Show() +end + +function M.display_name(item_id, no_brackets, no_color) + local item_info = item(item_id) + if item_info then + local name = item_info.name + if not no_brackets then + name = '[' .. name .. ']' + end + if not no_color then + name = aux.select(4, GetItemQualityColor(item_info.quality)) .. name .. FONT_COLOR_CODE_CLOSE + end + return name + end +end + +function M.auctionable(tooltip, quality, strict) + local status = tooltip[2] and tooltip[2].left_text + local durability, max_durability = durability(tooltip) + return (not quality or quality < 6) + and status ~= ITEM_BIND_ON_PICKUP + and status ~= ITEM_BIND_QUEST + and status ~= ITEM_SOULBOUND + and (not tooltip_match(ITEM_CONJURED, tooltip) or tooltip_find(ITEM_MIN_LEVEL, tooltip) > 1) + and not (strict and durability and durability < max_durability) +end + +function M.tooltip(setter, arg1, arg2) + AuxTooltip:SetOwner(UIParent, 'ANCHOR_NONE') + AuxTooltip.money = 0 + if setter == 'auction' then + AuxTooltip:SetAuctionItem(arg1, arg2) + elseif setter == 'bag' then + AuxTooltip:SetBagItem(arg1, arg2) + elseif setter == 'inventory' then + AuxTooltip:SetInventoryItem(arg1, arg2) + elseif setter == 'link' then + AuxTooltip:SetHyperlink(arg1) + end + local tooltip = T.acquire() + for i = 1, AuxTooltip:NumLines() do + tinsert(tooltip, T.map( + 'left_text', _G['AuxTooltipTextLeft' .. i]:GetText(), + 'left_color', T.list(_G['AuxTooltipTextLeft' .. i]:GetTextColor()), + 'right_text', _G['AuxTooltipTextRight' .. i]:IsVisible() and _G['AuxTooltipTextRight' .. i]:GetText(), + 'right_color', T.list(_G['AuxTooltipTextRight' .. i]:GetTextColor()) + )) + end + return tooltip, AuxTooltip.money +end + +do + local pattern = '^' .. gsub(gsub(ITEM_SPELL_CHARGES_P1, '%%d', '(%%d+)'), '%%%d+%$d', '(%%d+)') .. '$' + function item_charges(tooltip) + for _, line in tooltip do + local _, _, left_charges_string = strfind(line.left_text or '', pattern) + local _, _, right_charges_string = strfind(line.right_text or '', pattern) + + local charges = tonumber(left_charges_string) or tonumber(right_charges_string) + if charges then + return max(1, charges) -- TODO kronos bug? should never be 0 + end + end + return 1 + end +end + +do + local data = { + -- wizard oil + [20744] = 5, + [20746] = 5, + [20750] = 5, + [20749] = 5, + + -- mana oil + [20745] = 5, + [20747] = 5, + [20748] = 5, + + -- discombobulator + [4388] = 5, + + -- recombobulator + [4381] = 10, + [18637] = 10, + + -- deflector + [4376] = 5, + [4386] = 5, + + -- ... TODO + } + function M.max_item_charges(item_id) + return data[item_id] + end +end + +do + local pattern = '^' .. gsub(gsub(DURABILITY_TEMPLATE, '%%d', '(%%d+)'), '%%%d+%$d', '(%%d+)') .. '$' + function M.durability(tooltip) + for _, line in tooltip do + local _, _, left_durability_string, left_max_durability_string = strfind(line.left_text or '', pattern) + local _, _, right_durability_string, right_max_durability_string = strfind(line.right_text or '', pattern) + local durability = tonumber(left_durability_string) or tonumber(right_durability_string) + local max_durability = tonumber(left_max_durability_string) or tonumber(right_max_durability_string) + if durability then + return durability, max_durability + end + end + end +end + +function M.item_key(link) + local item_id, suffix_id = parse_link(link) + return item_id .. ':' .. suffix_id +end + +function M.parse_link(link) + local _, _, item_id, enchant_id, suffix_id, unique_id, name = strfind(link, '|c%x%x%x%x%x%x%x%x|Hitem:(%d*):(%d*):(%d*):(%d*)[:0-9]*|h%[(.-)%]|h|r') + return tonumber(item_id) or 0, tonumber(suffix_id) or 0, tonumber(unique_id) or 0, tonumber(enchant_id) or 0, name +end + +function M.itemstring(item_id, suffix_id, unique_id, enchant_id) + return 'item:' .. (item_id or 0) .. ':' .. (enchant_id or 0) .. ':' .. (suffix_id or 0) .. ':' .. (unique_id or 0) +end + +function M.item(item_id, suffix_id) + local itemstring = 'item:' .. (item_id or 0) .. ':0:' .. (suffix_id or 0) .. ':0' + local name, itemstring, quality, level, class, subclass, max_stack, slot, texture = GetItemInfo(itemstring) + return name and T.map( + 'name', name, + 'itemstring', itemstring, + 'quality', quality, + 'level', level, + 'class', class, + 'subclass', subclass, + 'slot', slot, + 'max_stack', max_stack, + 'texture', texture + ) or item_info(item_id) +end + +function M.item_class_index(item_class) + for i, class in T.temp-T.list(GetAuctionItemClasses()) do + if strupper(class) == strupper(item_class) then + return i, class + end + end +end + +function M.item_subclass_index(class_index, item_subclass) + for i, subclass in T.temp-T.list(GetAuctionItemSubClasses(class_index)) do + if strupper(subclass) == strupper(item_subclass) then + return i, subclass + end + end +end + +function M.item_slot_index(class_index, subclass_index, slot_name) + for i, slot in T.temp-T.list(GetAuctionInvTypes(class_index, subclass_index)) do + if strupper(_G[slot]) == strupper(slot_name) then + return i, _G[slot] + end + end +end + +function M.item_quality_index(item_quality) + for i = 0, 4 do + local quality = _G['ITEM_QUALITY' .. i .. '_DESC'] + if strupper(item_quality) == strupper(quality) then + return i, quality + end + end +end + +function M.inventory() + local bag, slot = 0, 0 + return function() + if not GetBagName(bag) or slot >= GetContainerNumSlots(bag) then + repeat bag = bag + 1 until GetBagName(bag) or bag > 4 + slot = 1 + else + slot = slot + 1 + end + if bag <= 4 then return T.list(bag, slot), bag_type(bag) end + end +end + +function M.bag_type(bag) + if bag == 0 then return 1 end + local link = GetInventoryItemLink('player', ContainerIDToInventoryID(bag)) + if link then + local item_id = parse_link(link) + local item_info = item(item_id) + return item_subclass_index(3, item_info.subclass) + end +end \ No newline at end of file diff --git a/aux-addon/util/money.lua b/aux-addon/util/money.lua new file mode 100644 index 0000000..734c4af --- /dev/null +++ b/aux-addon/util/money.lua @@ -0,0 +1,131 @@ +module 'aux.util.money' + +local T = require 'T' +local aux = require 'aux' + +M.GOLD_TEXT = '|cffffd70ag|r' +M.SILVER_TEXT = '|cffc7c7cfs|r' +M.COPPER_TEXT = '|cffeda55fc|r' + +local COPPER_PER_GOLD = 10000 +local COPPER_PER_SILVER = 100 + +function M.to_gsc(money) + local gold = floor(money / COPPER_PER_GOLD) + local silver = floor(mod(money, COPPER_PER_GOLD) / COPPER_PER_SILVER) + local copper = mod(money, COPPER_PER_SILVER) + return gold, silver, copper +end + +function M.from_gsc(gold, silver, copper) + return gold * COPPER_PER_GOLD + silver * COPPER_PER_SILVER + copper +end + +function M.to_string2(money, exact, color) + color = color or FONT_COLOR_CODE_CLOSE + + local TEXT_NONE = '0' + + local GOLD = 'ffd100' + local SILVER = 'e6e6e6' + local COPPER = 'c8602c' + local START = '|cff%s%d' .. FONT_COLOR_CODE_CLOSE + local PART = color .. '.|cff%s%02d' .. FONT_COLOR_CODE_CLOSE + local NONE = '|cffa0a0a0' .. TEXT_NONE .. FONT_COLOR_CODE_CLOSE + + if not exact and money >= COPPER_PER_GOLD then + money = aux.round(money / COPPER_PER_SILVER) * COPPER_PER_SILVER + end + local g, s, c = to_gsc(money) + + local str = '' + + local fmt = START + if g > 0 then + str = str .. format(fmt, GOLD, g) + fmt = PART + end + if s > 0 or c > 0 then + str = str .. format(fmt, SILVER, s) + fmt = PART + end + if c > 0 then + str = str .. format(fmt, COPPER, c) + end + if str == '' then + str = NONE + end + return str +end + +function M.to_string(money, pad, trim, color, no_color) + local is_negative = money < 0 + money = abs(money) + local gold, silver, copper = to_gsc(money) + + local gold_text, silver_text, copper_text + if no_color then + gold_text, silver_text, copper_text = 'g', 's', 'c' + else + gold_text, silver_text, copper_text = GOLD_TEXT, SILVER_TEXT, COPPER_TEXT + end + + local text + if trim then + local parts = T.temp-T.acquire() + if gold > 0 then + tinsert(parts, format_number(gold, false, color) .. gold_text) + end + if silver > 0 then + tinsert(parts, format_number(silver, pad, color) .. silver_text) + end + if copper > 0 or gold == 0 and silver == 0 then + tinsert(parts, format_number(copper, pad, color) .. copper_text) + end + text = aux.join(parts, ' ') + else + if gold > 0 then + text = format_number(gold, false, color) .. gold_text .. ' ' .. format_number(silver, pad, color) .. silver_text .. ' ' .. format_number(copper, pad, color) .. copper_text + elseif silver > 0 then + text = format_number(silver, false, color) .. silver_text .. ' ' .. format_number(copper, pad, color) .. copper_text + else + text = format_number(copper, false, color) .. copper_text + end + end + + if is_negative then + text = (color and color'-' or '-') .. text + end + + return text +end + +function M.from_string(value) + local number = tonumber(value) + if number and number >= 0 then + return number * COPPER_PER_GOLD + end + + value = gsub(gsub(strlower(value), '|c%x%x%x%x%x%x%x%x', ''), FONT_COLOR_CODE_CLOSE, '') + + local gold = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)g'))) + local silver = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)s'))) + local copper = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)c'))) + if not gold and not silver and not copper then return end + + value = gsub(value, '%d*%.?%d+g', '', 1) + value = gsub(value, '%d*%.?%d+s', '', 1) + value = gsub(value, '%d*%.?%d+c', '', 1) + if strfind(value, '%S') then return end + + return from_gsc(gold or 0, silver or 0, copper or 0) +end + +function M.format_number(num, pad, color) + num = format('%0' .. (pad and 2 or 0) .. 'd', num) + if color then + return color .. num .. FONT_COLOR_CODE_CLOSE + else + return num + end +end \ No newline at end of file diff --git a/aux-addon/util/persistence.lua b/aux-addon/util/persistence.lua new file mode 100644 index 0000000..8481547 --- /dev/null +++ b/aux-addon/util/persistence.lua @@ -0,0 +1,75 @@ +module 'aux.util.persistence' + +local T = require 'T' +local aux = require 'aux' + +function M.read(schema, str) + if schema == 'string' then + return str + elseif schema == 'boolean' then + return str == '1' + elseif schema == 'number' then + return tonumber(str) + elseif type(schema) == 'table' and schema[1] == 'list' then + return T.temp-read_list(schema, str) + elseif type(schema) == 'table' and schema[1] == 'tuple' then + return T.temp-read_tuple(schema, str) + else + error('Invalid schema.', 2) + end +end + +function M.write(schema, obj) + if schema == 'string' then + return obj or '' + elseif schema == 'boolean' then + return obj and '1' or '0' + elseif schema == 'number' then + return obj and tostring(obj) or '' + elseif type(schema) == 'table' and schema[1] == 'list' then + return write_list(schema, obj) + elseif type(schema) == 'table' and schema[1] == 'tuple' then + return write_tuple(schema, obj) + else + error('Invalid schema.', 2) + end +end + +function read_list(schema, str) + if str == '' then return T.acquire() end + local separator = schema[2] + local element_type = schema[3] + return aux.map(aux.split(str, separator), function(part) + return read(element_type, part) + end) +end + +function write_list(schema, list) + local separator = schema[2] + local element_type = schema[3] + local parts = aux.map(T.temp-aux.copy(list), function(element) + return write(element_type, element) + end) + return aux.join(parts, separator) +end + +function read_tuple(schema, str) + local separator = schema[2] + local tuple = T.acquire() + local parts = T.temp-aux.split(str, separator) + for i = 3, getn(schema) do + local key, type = next(schema[i]) + tuple[key] = read(type, parts[i - 2]) + end + return tuple +end + +function write_tuple(schema, tuple) + local separator = schema[2] + local parts = T.temp-T.acquire() + for i = 3, getn(schema) do + local key, type = next(schema[i]) + tinsert(parts, write(type, tuple[key])) + end + return aux.join(parts, separator) +end \ No newline at end of file diff --git a/aux-addon/util/scan.lua b/aux-addon/util/scan.lua new file mode 100644 index 0000000..68b3c8e --- /dev/null +++ b/aux-addon/util/scan.lua @@ -0,0 +1,82 @@ +module 'aux.util.scan' + +local T = require 'T' +local aux = require 'aux' +local info = require 'aux.util.info' +local filter_util = require 'aux.util.filter' +local scan = require 'aux.core.scan' + +function M.test(record, index) + local auction_record = T.temp-info.auction(index, record.query_type) + return auction_record and auction_record.search_signature == record.search_signature +end + +function M.find(auction_record, status_bar, on_abort, on_failure, on_success) + + local queries = T.list(T.acquire()) + + if auction_record.blizzard_query then + local blizzard_query1 = aux.copy(auction_record.blizzard_query) + blizzard_query1.first_page = auction_record.page + blizzard_query1.last_page = auction_record.page + tinsert(queries, T.map('blizzard_query', blizzard_query1)) + + if auction_record.page > 0 then + local blizzard_query2 = aux.copy(auction_record.blizzard_query) + blizzard_query2.first_page = auction_record.page - 1 + blizzard_query2.last_page = auction_record.page - 1 + tinsert(queries, T.map('blizzard_query', blizzard_query2)) + end + + local item_query = item_query(auction_record.item_id, 0, 0) + if not aux.eq(auction_record.blizzard_query, item_query.blizzard_query) then + tinsert(queries, item_query) + end + end + + local found + return scan.start{ + type = auction_record.query_type, + queries = queries, + on_scan_start = function() + status_bar:update_status(0, 0) + status_bar:set_text('Searching auction...') + end, + on_start_query = function(query_index) + status_bar:update_status((query_index - 1) / getn(queries), 0) + end, + on_auction = function(record) + if test(auction_record, record.index) then + found = true + scan.stop() + status_bar:update_status(1, 1) + status_bar:set_text('Auction found') + return on_success(record.index) + end + end, + on_abort = function() + if not found then + status_bar:update_status(1, 1) + status_bar:set_text('Auction not found') + return on_abort() + end + end, + on_complete = function() + if not found then + status_bar:update_status(1, 1) + status_bar:set_text('Auction not found') + return on_failure() + end + end, + } +end + +function M.item_query(item_id, first_page, last_page) + local item_info = info.item(item_id) + if item_info then + local query = filter_util.query(item_info.name .. '/exact') + query.blizzard_query.first_page = first_page + query.blizzard_query.last_page = last_page + return T.map('validator', query.validator, 'blizzard_query', query.blizzard_query) + end +end \ No newline at end of file diff --git a/aux-addon/util/sort.lua b/aux-addon/util/sort.lua new file mode 100644 index 0000000..fd8871c --- /dev/null +++ b/aux-addon/util/sort.lua @@ -0,0 +1,30 @@ +module 'aux.util.sort' + +local T = require 'T' + +M.EQ = 0 +M.LT = 1 +M.GT = 2 + +function M.compare(a, b, desc) + if a < b then + return desc and GT or LT + elseif a > b then + return desc and LT or GT + else + return EQ + end +end + +M.multi_lt = T.vararg-function(arg) + for i = 1, getn(arg), 2 do + if arg[i] and arg[i + 1] and arg[i] ~= arg[i + 1] then + return arg[i] < arg[i + 1] + elseif not arg[i] and arg[i + 1] then + return true + elseif not arg[i + 1] then + return false + end + end + return false +end \ No newline at end of file From 4517dae9528dca2d9913171ae52aae792286dcf1 Mon Sep 17 00:00:00 2001 From: geojak Date: Mon, 7 Mar 2022 17:43:42 +0100 Subject: [PATCH 2/6] Postkeybind 1. Improved autopricing heuristics 2. Added post keybind for much faster mass posting of items --- aux-addon/Bindings.xml | 5 +++ aux-addon/README_Geojak.md | 3 ++ aux-addon/aux-addon.toc | 2 + aux-addon/core/post.lua | 78 ++++++++++++++---------------------- aux-addon/localization.lua | 7 ++++ aux-addon/tabs/post/core.lua | 55 ++++++++++++++++++++++++- 6 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 aux-addon/Bindings.xml create mode 100644 aux-addon/README_Geojak.md create mode 100644 aux-addon/localization.lua diff --git a/aux-addon/Bindings.xml b/aux-addon/Bindings.xml new file mode 100644 index 0000000..6d7d97e --- /dev/null +++ b/aux-addon/Bindings.xml @@ -0,0 +1,5 @@ + + + post_auctions_bind() + + \ No newline at end of file diff --git a/aux-addon/README_Geojak.md b/aux-addon/README_Geojak.md new file mode 100644 index 0000000..4bff713 --- /dev/null +++ b/aux-addon/README_Geojak.md @@ -0,0 +1,3 @@ +1. Added auto bid feature, doubled the existing auto buy code and slighly editted +2. Added auto pricing heuristic, these are activate when posting an item with bid price 0, the buyout price works as a minimum +3. Added a keybind for the post button, this significantly speeds up mass posting of items \ No newline at end of file diff --git a/aux-addon/aux-addon.toc b/aux-addon/aux-addon.toc index 82b078a..3c3e970 100644 --- a/aux-addon/aux-addon.toc +++ b/aux-addon/aux-addon.toc @@ -51,4 +51,6 @@ tabs\bids\frame.lua core\crafting.lua +localization.lua + diff --git a/aux-addon/core/post.lua b/aux-addon/core/post.lua index 37a506a..f0046fe 100644 --- a/aux-addon/core/post.lua +++ b/aux-addon/core/post.lua @@ -33,37 +33,6 @@ function process() return stop() end -function post_auction2(slot, k) - local item_info = info.container_item(unpack(slot)) - if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then - - ClearCursor() - ClickAuctionSellItemButton() - ClearCursor() - PickupContainerItem(unpack(slot)) - ClickAuctionSellItemButton() - ClearCursor() - - StartAuction(max(1, aux.round(state.unit_start_price * item_info.aux_quantity)), aux.round(state.unit_buyout_price * item_info.aux_quantity), state.duration) - - local send_signal, signal_received = aux.signal() - aux.when(signal_received, function() - state.posted = state.posted + 1 - return k() - end) - - local posted - aux.event_listener('CHAT_MSG_SYSTEM', function(kill) - if arg1 == ERR_AUCTION_STARTED then - send_signal() - kill() - end - end) - else - return stop() - end -end - function post_auction(slot, k) local item_info = info.container_item(unpack(slot)) if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then @@ -77,37 +46,52 @@ function post_auction(slot, k) local start_price = state.unit_start_price local buyout_price = state.unit_buyout_price - local kz_undercut = 0 + local kz_daily = 0 + local kz_pricing = 0 + --use autoprice heuristic if start bid 0 is set if start_price == 0 then + if buyout_price > 0 then + kz_pricing=1 + end + if history.market_value(state.item_key) ~= nil then - start_price = max(start_price,0.93*tonumber(history.market_value(state.item_key))) - buyout_price = max(buyout_price,0.99*tonumber(history.market_value(state.item_key))) - - kz_undercut = 1 + local tmp = tonumber(history.market_value(state.item_key)) + tmp = max(0.99*tmp,tmp-50) + buyout_price = max(buyout_price,tmp) + kz_daily = 1 end if disenchant.value(item_info.slot, item_info.quality, item_info.level) ~= nil then - buyout_price = max(buyout_price,0.80* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level))) + buyout_price = max(buyout_price,0.85* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level))) end - if aux.account_data.merchant_sell[item_info.item_id] ~= nil then - start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * 1.35) - buyout_price = max(buyout_price,start_price) - - if kz_undercut == 0 then + if aux.account_data.merchant_sell[item_info.item_id] ~= nil then + if kz_daily == 0 then start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * (1.35+3.65*math.exp(-(1/4000)* tonumber(aux.account_data.merchant_sell[item_info.item_id] )))) buyout_price = max(buyout_price,start_price) + else + start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * 1.35) + buyout_price = max(buyout_price,start_price) end end + if aux.account_data.merchant_buy[item_info.item_id] ~= nil then + local tmp = tostring(aux.account_data.merchant_buy[item_info.item_id]) + tmp = strsub(tmp,1,-3) + start_price = max(start_price,tonumber(tmp) * 1.1) + buyout_price = max(buyout_price,tonumber(tmp) * 1.15) + end + if history.value(state.item_key) ~= nil then - start_price = max(start_price,0.89*tonumber(history.value(state.item_key))) - buyout_price = max(buyout_price,0.89*tonumber(history.value(state.item_key))) - - if kz_undercut == 0 then - buyout_price = max(buyout_price,1.0* tonumber(history.value(state.item_key))) + if kz_pricing == 1 and kz_daily == 1 then + buyout_price = max(buyout_price,1.0*tonumber(history.value(state.item_key))) + elseif kz_daily == 1 then + start_price = max(start_price,0.91*tonumber(history.value(state.item_key))) + buyout_price = max(buyout_price,0.91*tonumber(history.value(state.item_key))) + else + buyout_price = max(buyout_price,0.99* tonumber(history.value(state.item_key))) end end diff --git a/aux-addon/localization.lua b/aux-addon/localization.lua new file mode 100644 index 0000000..37e3b3a --- /dev/null +++ b/aux-addon/localization.lua @@ -0,0 +1,7 @@ +BINDING_HEADER_AUX = "aux"; + +local post = require 'aux.tabs.post' + +function post_auctions_bind() + post.post_auctions_bind() +end \ No newline at end of file diff --git a/aux-addon/tabs/post/core.lua b/aux-addon/tabs/post/core.lua index 41ca1da..b151967 100644 --- a/aux-addon/tabs/post/core.lua +++ b/aux-addon/tabs/post/core.lua @@ -248,6 +248,58 @@ function post_auctions() end end +function M.post_auctions_bind() + if selected_item then + local unit_start_price = get_unit_start_price() + local unit_buyout_price = get_unit_buyout_price() + local stack_size = stack_size_slider:GetValue() + local stack_count + stack_count = stack_count_slider:GetValue() + local duration = UIDropDownMenu_GetSelectedValue(duration_dropdown) + local key = selected_item.key + + local duration_code + if duration == DURATION_2 then + duration_code = 2 + elseif duration == DURATION_8 then + duration_code = 3 + elseif duration == DURATION_24 then + duration_code = 4 + end + + post.start( + key, + stack_size, + duration, + unit_start_price, + unit_buyout_price, + stack_count, + function(posted) + if not frame:IsShown() then + return + end + for i = 1, posted do + record_auction(key, stack_size, unit_start_price * stack_size, unit_buyout_price, duration_code, UnitName'player') + end + update_inventory_records() + local same + for _, record in inventory_records do + if record.key == key then + same = record + break + end + end + if same then + update_item(same) + else + selected_item = nil + end + refresh = true + end + ) + end +end + function validate_parameters() if not selected_item then post_button:Disable() @@ -390,7 +442,8 @@ function update_item(item) else stack_size_slider:SetMinMaxValues(1, min(selected_item.max_stack, selected_item.aux_quantity)) end - stack_size_slider:SetValue(aux.huge) + --stack_size_slider:SetValue(aux.huge) + stack_size_slider:SetValue(1) quantity_update(true) unit_start_price_input:SetText(money.to_string(settings.start_price, true, nil, nil, true)) From 70611ba356feb7d4b24fee705dbfe3fb8a54982d Mon Sep 17 00:00:00 2001 From: geojak Date: Sun, 27 Mar 2022 15:07:52 +0200 Subject: [PATCH 3/6] some more improvements see readme_geojak for changelist compared to the main aux branch --- aux-addon/README_Geojak.md | 9 +++- aux-addon/aux-addon.toc | 1 + aux-addon/core/disenchant.lua | 19 ++++++-- aux-addon/core/post.lua | 81 +++++++++++++++++++++++--------- aux-addon/core/scan.lua | 4 +- aux-addon/core/tooltip.lua | 11 ++++- aux-addon/tabs/post/core.lua | 8 +++- aux-addon/tabs/search/filter.lua | 2 +- aux-addon/util/filter.lua | 49 +++++++++++++++++-- 9 files changed, 144 insertions(+), 40 deletions(-) diff --git a/aux-addon/README_Geojak.md b/aux-addon/README_Geojak.md index 4bff713..064b4ac 100644 --- a/aux-addon/README_Geojak.md +++ b/aux-addon/README_Geojak.md @@ -1,3 +1,10 @@ 1. Added auto bid feature, doubled the existing auto buy code and slighly editted 2. Added auto pricing heuristic, these are activate when posting an item with bid price 0, the buyout price works as a minimum -3. Added a keybind for the post button, this significantly speeds up mass posting of items \ No newline at end of file +3. Added a keybind for the post button, this significantly speeds up mass posting of items +4. Changed the default post stack size to rand(1,max) instead of always max +5. FIXED TWILLIGHT CULTIST SET NOT DISENCHATABLE +6. use SHagu tweaks vendor values if aux values not avaialbe +7. dont auto bid on own items +8. new filter disenchant-percent and bid-disencahnt-percent +9. enchanting created wands are not encahntabke, same with twillight stuff at 5. reworked implementation for easier addition +10. disenchant values are calcualted from min(value,value_today), this protects users from low enchanting mat prices \ No newline at end of file diff --git a/aux-addon/aux-addon.toc b/aux-addon/aux-addon.toc index 3c3e970..919f718 100644 --- a/aux-addon/aux-addon.toc +++ b/aux-addon/aux-addon.toc @@ -1,5 +1,6 @@ ## Interface: 11200 ## Author: shirsig +## OptionalDeps: ShaguTweaks ## Title: aux ## Notes: Auction House replacement ## SavedVariables: aux diff --git a/aux-addon/core/disenchant.lua b/aux-addon/core/disenchant.lua index 3d61dab..1752167 100644 --- a/aux-addon/core/disenchant.lua +++ b/aux-addon/core/disenchant.lua @@ -33,21 +33,30 @@ local WEAPON = T.set( 'INVTYPE_RANGEDRIGHT' ) -function M.value(slot, quality, level) +function M.value(slot, quality, level, item_id) local expectation - for _, event in distribution(slot, quality, level) do + for _, event in distribution(slot, quality, level, item_id) do local value = history.value(event.item_id .. ':' .. 0) - if not value then + if not value then return else + local value_today = history.market_value(event.item_id .. ':' .. 0) + if value_today then + value=min(value,value_today) + end expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value end end return expectation end -function M.distribution(slot, quality, level) - if not ARMOR[slot] and not WEAPON[slot] or level == 0 then +function M.distribution(slot, quality, level, item_id) + if not ARMOR[slot] and not WEAPON[slot] or level == 0 + --twillight cultist items are not disenchantable + or item_id==20408 or item_id==20407 or item_id==20406 + --enchanting created wands are not disenchantable + or item_id==11288 or item_id==11290 or item_id==11287 or item_id==11289 + then return T.acquire() end diff --git a/aux-addon/core/post.lua b/aux-addon/core/post.lua index f0046fe..0e8415f 100644 --- a/aux-addon/core/post.lua +++ b/aux-addon/core/post.lua @@ -46,14 +46,17 @@ function post_auction(slot, k) local start_price = state.unit_start_price local buyout_price = state.unit_buyout_price - local kz_daily = 0 - local kz_pricing = 0 --use autoprice heuristic if start bid 0 is set if start_price == 0 then + local kz_daily = 0 + local kz_pricing = 0 + local kz_warn = 0 + if buyout_price > 0 then kz_pricing=1 + start_price = buyout_price end if history.market_value(state.item_key) ~= nil then @@ -63,39 +66,71 @@ function post_auction(slot, k) kz_daily = 1 end - if disenchant.value(item_info.slot, item_info.quality, item_info.level) ~= nil then - buyout_price = max(buyout_price,0.85* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level))) - end - - if aux.account_data.merchant_sell[item_info.item_id] ~= nil then - if kz_daily == 0 then - start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * (1.35+3.65*math.exp(-(1/4000)* tonumber(aux.account_data.merchant_sell[item_info.item_id] )))) - buyout_price = max(buyout_price,start_price) + if history.value(state.item_key) ~= nil then + if kz_pricing == 1 and kz_daily == 1 then + buyout_price = max(buyout_price,1.0*tonumber(history.value(state.item_key))) + elseif kz_daily == 1 then + start_price = max(start_price,0.91*tonumber(history.value(state.item_key))) else - start_price = max(start_price,tonumber(aux.account_data.merchant_sell[item_info.item_id]) * 1.35) - buyout_price = max(buyout_price,start_price) + buyout_price = max(buyout_price,0.99* tonumber(history.value(state.item_key))) end + else + kz_warn = kz_warn + 1 + end + + if disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id) ~= nil then + start_price = max(start_price,0.85* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id))) end if aux.account_data.merchant_buy[item_info.item_id] ~= nil then local tmp = tostring(aux.account_data.merchant_buy[item_info.item_id]) tmp = strsub(tmp,1,-3) start_price = max(start_price,tonumber(tmp) * 1.1) - buyout_price = max(buyout_price,tonumber(tmp) * 1.15) - end - - if history.value(state.item_key) ~= nil then - if kz_pricing == 1 and kz_daily == 1 then - buyout_price = max(buyout_price,1.0*tonumber(history.value(state.item_key))) - elseif kz_daily == 1 then - start_price = max(start_price,0.91*tonumber(history.value(state.item_key))) - buyout_price = max(buyout_price,0.91*tonumber(history.value(state.item_key))) + buyout_price = max(buyout_price,tonumber(tmp) * 1.15) + else + local vendor_price = 0 + if aux.account_data.merchant_sell[item_info.item_id] ~= nil then + vendor_price = tonumber(aux.account_data.merchant_sell[item_info.item_id]) + elseif ShaguTweaks.SellValueDB[item_info.item_id] ~= nil then + local charges = 1 + if info.max_item_charges(item_info.item_id) ~= nil then + charges=info.max_item_charges(item_info.item_id) + end + vendor_price = ShaguTweaks.SellValueDB[item_info.item_id] / charges + end + if vendor_price > 0 then + if kz_daily == 0 then + start_price = max(start_price, vendor_price * (1.35+3.65*math.exp(-(1/4000)* vendor_price))) + else + start_price = max(start_price, vendor_price * 1.35) + end else - buyout_price = max(buyout_price,0.99* tonumber(history.value(state.item_key))) + kz_warn = kz_warn + 1 end end - start_price = max(start_price,0.75 * buyout_price) + start_price = max(start_price,0.91 * buyout_price) + buyout_price = max(start_price, buyout_price) + + if kz_warn == 2 and kz_pricing == 0 then + print("WARNING, insufficient data for autopricing!") + if start_price == 0 then + return stop() + end + end + + local gold = floor(start_price / COPPER_PER_GOLD) + local silver = floor(mod(start_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) + local copper = aux.round(mod(start_price, COPPER_PER_SILVER)) + + print("bid_price: "..gold.."g "..silver.."s "..copper.."c") + + gold = floor(buyout_price / COPPER_PER_GOLD) + silver = floor(mod(buyout_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) + copper = aux.round(mod(buyout_price, COPPER_PER_SILVER)) + + print("buyout_price: "..gold.."g "..silver.."s "..copper.."c") + end StartAuction(max(1, aux.round(start_price * item_info.aux_quantity)), aux.round(buyout_price * item_info.aux_quantity), state.duration) diff --git a/aux-addon/core/scan.lua b/aux-addon/core/scan.lua index f59e70e..7da6a61 100644 --- a/aux-addon/core/scan.lua +++ b/aux-addon/core/scan.lua @@ -147,11 +147,11 @@ function scan_page(i) history.process_auction(auction_info) - if (get_state().params.auto_bid_validator or pass)(auction_info) then + if (get_state().params.auto_bid_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then local send_signal, signal_received = aux.signal() aux.when(signal_received, scan_page, i) return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.bid_price, send_signal) - elseif (get_state().params.auto_buy_validator or pass)(auction_info) then + elseif (get_state().params.auto_buy_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then local send_signal, signal_received = aux.signal() aux.when(signal_received, scan_page, i) return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, send_signal) diff --git a/aux-addon/core/tooltip.lua b/aux-addon/core/tooltip.lua index c43365b..353e893 100644 --- a/aux-addon/core/tooltip.lua +++ b/aux-addon/core/tooltip.lua @@ -54,7 +54,7 @@ function M.extend_tooltip(tooltip, link, quantity) quantity = IsShiftKeyDown() and quantity or 1 local item_info = T.temp-info.item(item_id) if item_info then - local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level) + local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level, item_id) if getn(distribution) > 0 then if settings.disenchant_distribution then tooltip:AddLine('Disenchants into:', aux.color.tooltip.disenchant.distribution()) @@ -64,7 +64,7 @@ function M.extend_tooltip(tooltip, link, quantity) end end if settings.disenchant_value then - local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level) + local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level, item_id) tooltip:AddLine('Disenchant: ' .. (disenchant_value and money.to_string2(disenchant_value) or UNKNOWN), aux.color.tooltip.disenchant.value()) end end @@ -77,6 +77,13 @@ function M.extend_tooltip(tooltip, link, quantity) end if settings.merchant_sell then local price = info.merchant_info(item_id) + if price == nil and ShaguTweaks.SellValueDB[item_id] ~= nil then + local charges = 1 + if info.max_item_charges(item_id) ~= nil then + charges=info.max_item_charges(item_id) + end + price = ShaguTweaks.SellValueDB[item_id] / charges + end if price ~= 0 then tooltip:AddLine('Vendor: ' .. (price and money.to_string2(price * quantity) or UNKNOWN), aux.color.tooltip.merchant()) end diff --git a/aux-addon/tabs/post/core.lua b/aux-addon/tabs/post/core.lua index b151967..08a3581 100644 --- a/aux-addon/tabs/post/core.lua +++ b/aux-addon/tabs/post/core.lua @@ -431,19 +431,23 @@ function update_item(item) UIDropDownMenu_SetSelectedValue(duration_dropdown, settings.duration) hide_checkbox:SetChecked(settings.hidden) - + local ii = 1 if selected_item.max_charges then for i = selected_item.max_charges, 1, -1 do if selected_item.availability[i] > 0 then stack_size_slider:SetMinMaxValues(1, i) + ii=i break end end else + ii = min(selected_item.max_stack, selected_item.aux_quantity) stack_size_slider:SetMinMaxValues(1, min(selected_item.max_stack, selected_item.aux_quantity)) end + + stack_size_slider:SetValue(math.random(1,ii)) --stack_size_slider:SetValue(aux.huge) - stack_size_slider:SetValue(1) + --stack_size_slider:SetValue(1) quantity_update(true) unit_start_price_input:SetText(money.to_string(settings.start_price, true, nil, nil, true)) diff --git a/aux-addon/tabs/search/filter.lua b/aux-addon/tabs/search/filter.lua index 44618bf..eaac271 100644 --- a/aux-addon/tabs/search/filter.lua +++ b/aux-addon/tabs/search/filter.lua @@ -323,7 +323,7 @@ function set_filter_display_offset(x_offset, y_offset) end function initialize_filter_dropdown() - for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do + for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'disenchant-percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'bid-disenchant-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do UIDropDownMenu_AddButton(T.map( 'text', filter, 'value', filter, diff --git a/aux-addon/util/filter.lua b/aux-addon/util/filter.lua index bb7092e..e997192 100644 --- a/aux-addon/util/filter.lua +++ b/aux-addon/util/filter.lua @@ -110,6 +110,16 @@ M.filters = { end end }, + + ['bid-disenchant-percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + and auction_record.unit_bid_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct + end + end + }, ['percent'] = { input_type = 'number', @@ -121,6 +131,17 @@ M.filters = { end end }, + + ['disenchant-percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return auction_record.unit_buyout_price > 0 + and disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + and auction_record.unit_buyout_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct + end + end + }, ['bid-profit'] = { input_type = 'money', @@ -144,8 +165,8 @@ M.filters = { input_type = 'money', validator = function(amount) return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return disenchant_value and disenchant_value - auction_record.bid_price >= amount + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + return disenchant_value and disenchant_value - auction_record.bid_price >= amount end end }, @@ -154,8 +175,8 @@ M.filters = { input_type = 'money', validator = function(amount) return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount end end }, @@ -165,6 +186,16 @@ M.filters = { validator = function(amount) return function(auction_record) local vendor_price = info.merchant_info(auction_record.item_id) + if not vendor_price then + vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] + if vendor_price then + local charges = 1 + if info.max_item_charges(auction_record.item_id) ~= nil then + info.charges=info.max_item_charges(auction_record.item_id) + end + vendor_price= vendor_price/ charges + end + end return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount end end @@ -175,6 +206,16 @@ M.filters = { validator = function(amount) return function(auction_record) local vendor_price = info.merchant_info(auction_record.item_id) + if not vendor_price then + vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] + if vendor_price then + local charges = 1 + if info.max_item_charges(auction_record.item_id) ~= nil then + info.charges=info.max_item_charges(auction_record.item_id) + end + vendor_price= vendor_price/ charges + end + end return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount end end From eab57d72e7a1da1bd7780b7c2a43a2ad6622d132 Mon Sep 17 00:00:00 2001 From: geojak Date: Sat, 2 Apr 2022 14:53:11 +0200 Subject: [PATCH 4/6] Delete aux-addon directory delete wrong folder --- aux-addon/Bindings.xml | 5 - aux-addon/README.md | 156 --- aux-addon/README_Geojak.md | 10 - aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP | Bin 3908 -> 0 bytes aux-addon/aux-addon.lua | 243 ---- aux-addon/aux-addon.toc | 57 - aux-addon/color.lua | 51 - aux-addon/control.lua | 121 -- aux-addon/core/cache.lua | 211 ---- aux-addon/core/crafting.lua | 99 -- aux-addon/core/disenchant.lua | 172 --- aux-addon/core/history.lua | 109 -- aux-addon/core/post.lua | 184 --- aux-addon/core/scan.lua | 236 ---- aux-addon/core/shortcut.lua | 31 - aux-addon/core/slash.lua | 74 -- aux-addon/core/stack.lua | 130 --- aux-addon/core/tooltip.lua | 223 ---- aux-addon/frame.lua | 51 - aux-addon/gui/auction_listing.lua | 1046 ------------------ aux-addon/gui/core.lua | 569 ---------- aux-addon/gui/item_listing.lua | 129 --- aux-addon/gui/listing.lua | 300 ----- aux-addon/libs/T.lua | 149 --- aux-addon/libs/package.lua | 37 - aux-addon/localization.lua | 7 - aux-addon/tabs/auctions/core.lua | 101 -- aux-addon/tabs/auctions/frame.lua | 62 -- aux-addon/tabs/bids/core.lua | 125 --- aux-addon/tabs/bids/frame.lua | 71 -- aux-addon/tabs/post/core.lua | 617 ----------- aux-addon/tabs/post/frame.lua | 326 ------ aux-addon/tabs/search/core.lua | 85 -- aux-addon/tabs/search/filter.lua | 395 ------- aux-addon/tabs/search/frame.lua | 559 ---------- aux-addon/tabs/search/results.lua | 443 -------- aux-addon/tabs/search/saved.lua | 181 --- aux-addon/util.lua | 188 ---- aux-addon/util/completion.lua | 45 - aux-addon/util/filter.lua | 590 ---------- aux-addon/util/info.lua | 443 -------- aux-addon/util/money.lua | 131 --- aux-addon/util/persistence.lua | 75 -- aux-addon/util/scan.lua | 82 -- aux-addon/util/sort.lua | 30 - 45 files changed, 8949 deletions(-) delete mode 100644 aux-addon/Bindings.xml delete mode 100644 aux-addon/README.md delete mode 100644 aux-addon/README_Geojak.md delete mode 100644 aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP delete mode 100644 aux-addon/aux-addon.lua delete mode 100644 aux-addon/aux-addon.toc delete mode 100644 aux-addon/color.lua delete mode 100644 aux-addon/control.lua delete mode 100644 aux-addon/core/cache.lua delete mode 100644 aux-addon/core/crafting.lua delete mode 100644 aux-addon/core/disenchant.lua delete mode 100644 aux-addon/core/history.lua delete mode 100644 aux-addon/core/post.lua delete mode 100644 aux-addon/core/scan.lua delete mode 100644 aux-addon/core/shortcut.lua delete mode 100644 aux-addon/core/slash.lua delete mode 100644 aux-addon/core/stack.lua delete mode 100644 aux-addon/core/tooltip.lua delete mode 100644 aux-addon/frame.lua delete mode 100644 aux-addon/gui/auction_listing.lua delete mode 100644 aux-addon/gui/core.lua delete mode 100644 aux-addon/gui/item_listing.lua delete mode 100644 aux-addon/gui/listing.lua delete mode 100644 aux-addon/libs/T.lua delete mode 100644 aux-addon/libs/package.lua delete mode 100644 aux-addon/localization.lua delete mode 100644 aux-addon/tabs/auctions/core.lua delete mode 100644 aux-addon/tabs/auctions/frame.lua delete mode 100644 aux-addon/tabs/bids/core.lua delete mode 100644 aux-addon/tabs/bids/frame.lua delete mode 100644 aux-addon/tabs/post/core.lua delete mode 100644 aux-addon/tabs/post/frame.lua delete mode 100644 aux-addon/tabs/search/core.lua delete mode 100644 aux-addon/tabs/search/filter.lua delete mode 100644 aux-addon/tabs/search/frame.lua delete mode 100644 aux-addon/tabs/search/results.lua delete mode 100644 aux-addon/tabs/search/saved.lua delete mode 100644 aux-addon/util.lua delete mode 100644 aux-addon/util/completion.lua delete mode 100644 aux-addon/util/filter.lua delete mode 100644 aux-addon/util/info.lua delete mode 100644 aux-addon/util/money.lua delete mode 100644 aux-addon/util/persistence.lua delete mode 100644 aux-addon/util/scan.lua delete mode 100644 aux-addon/util/sort.lua diff --git a/aux-addon/Bindings.xml b/aux-addon/Bindings.xml deleted file mode 100644 index 6d7d97e..0000000 --- a/aux-addon/Bindings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - post_auctions_bind() - - \ No newline at end of file diff --git a/aux-addon/README.md b/aux-addon/README.md deleted file mode 100644 index 34cc2da..0000000 --- a/aux-addon/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# aux - WoW 1.12 AddOn - -The most advanced auction house addOn for the 1.12 client with some features more advanced than anything even on retail. - -## Core Features - -### General -* Completely independent replacement for the Blizzard interface. -* Elegant look based on the retail addOn TSM. -* Many convenient shortcuts. -* Convenient access to the unaltered Blizzard interface. - -### Search -* Automatic scanning of all pages for a query. -* Saving of recent and favorite queries. -* History of result listings with internet browser-like interface. -* Advanced search filters which can be combined with logical operators. -* Autocompletion for entering filters. -* Concise listings cleary showing the most important information. -* Sorting by percentage of historical value and unit price. -* Sorting across all scanned pages. -* Quick buying from any page without rescanning everything. -* Real time mode which continuously scans the last page. - -### Post -* Automatic assembling and posting of multiple stacks. -* Automatic scanning of existing auctions. -* Concise listing of existing auctions. -* Undercutting of existing auctions by click. -* Concise listing of inventory items excluding the non auctionable. -* Manual exclusion of specific items from the inventory listing. -* Saving post configuration per item. -* Efficient price input inspired by the retail addOn TSM. - -### History -* Automatic gathering of historical data from all scans. -* Automatic collection of vendor prices. -* Intricate calculations for a reliable historical value. -* Tooltip with historical value, vendor prices and disenchant value. -* Efficient storage of data. - -## Slash Commands -### General -**/aux** (Lists the settings)
-**/aux scale _factor_** (Scales the aux GUI by _factor_)
-**/aux ignore owner** (Disables waiting for owner names when scanning. Recommended)
-**/aux post bid** (Adds a bid price listing to the post tab)
-**/aux crafting cost** (Toggles the crafting price information)
-**/aux post duration _hours_** (Sets the default auction duration to _2_/_8_/_24_ hours)
-### Tooltip -**/aux tooltip value**
-**/aux tooltip daily**
-**/aux tooltip disenchant value**
-**/aux tooltip disenchant distribution**
-**/aux tooltip vendor buy**
-**/aux tooltip vendor sell**
- -## Usage -### General -For the auction listings in the search, auctions and bids tabs the following shortcuts are available. -- Double-click on a row with blue colored count to expand it. -- Alt-left-click on the selected row for buyout/cancel. -- Alt-right-click on the selected row for bid/cancel. -- Right-click on a row to start a search for the auctioned item. -- Control-click on a row the show a preview in the wardrobe frame. -- Shift-click on a row to copy the link to the chatframe. -- Left-click on a header to sort. -- Right-click on a header of a price column to switch between unit and stack price. - -Furthermore -- Double-click in editboxes will highlight everything. - -### Search -- Hitting tab in the search box will accept an autocompletion. -- Dragging inventory items to the search box or right-clicking them will start a search. -- Right-clicking item links will start a search. - -#### Search Results -![Alt text](http://i.imgur.com/hI6ODqM.png) -- Bid prices for your own active bids are colored in green. -- Bid prices for other auctions with an active bid are colored in orange. - -#### Saved Searches -![Alt text](http://i.imgur.com/dICDnxR.png) -- When hovering over an entry the tooltip shows a longer and more nicely formatted version. -- Left-click on an entry will start a search. -- Right-click on an entry will show a menu with various options, including toggling Auto Buy. -- Shift-left-click on an entry will copy a search to the search box. -- Shift-right-click on an entry will add a search to the existing query in the search box. - -#### Filter Builder -![Alt text](http://i.imgur.com/8hilZc9.png) -While it is faster to type filters directly into the search box this sub-tab serves as a tutorial to learn how to formulate queries. -The filters on the left side are Blizzard filters which may reduce the number of pages to be scanned and those on the right side are post filters which do not affect the scan time but can be combined with logical operators to formulate very complex filters. -### Post -![Alt text](http://i.imgur.com/otzOT2I.png) -- When entering prices **g**, **s** and **c** denote gold, silver and copper respectively. -- A price value without explicit denotations will count as gold. (e.g., 10.5 = 10g50s) -- Price values can contain decimals. (e.g., 1.5g = 1g50s) -- Right-clicking an item in the inventory listing will start a search. -- Right-clicking a bag item will select it in the listing. -- In the listing of bids/buyouts a red price is undercutting stack/unit price. -- Clicking an entry in the in the listings of bids/buyouts of existing auctions will undercut with your bid stack/buyout unit price. -- Double-click in the bids/buyouts listings will also match the stack size. - -### Auctions -![Alt text](http://i.imgur.com/6HjaIo2.png) - -### Bids -![Alt text](http://i.imgur.com/NOjPKNW.png) - -## Search Filters -AddOns do not have any additional Blizzard filters available to them beyond the ones in the default auction house interface, nor do they have any other ways to combine them. -Of course it is possible for an addOn to apply arbitrary filters after the Blizzard query but only the Blizzard query will affect the number of pages to be scanned and thus the time it takes for a scan. -Since the Vanilla API will only let you request a page every 4 seconds having no Blizzard query in your filter can lead to very long scan times. - -aux queries are separated by semicolons and always contain exactly one Blizzard query. The Blizzard query may be empty, i.e., all pages are scanned, which can be useful for collecting historical data. -The real time mode only supports empty Blizzard queries. -Semicolons always mean "or", i.e., **q1;q2;q3** means all items matching **q1** or **q2** or **q3** will be listed. - -The parts of individual queries are separated by slashes, e.g., **q1p1/q1p2;q2p1/q2p2/q2p3**. All parts either belong to the Blizzard filter or the post processing filter. - -Blizzard filters can be created through the form on the left side of the "New Filter" sub-tab of the "Search" tab or typed directly into the search box. -For learning to write queries you can fill in the form, add the query to the search box with the "Add" or "Replace" buttons and inspect the generated output until you feel comfortable typing them out yourself. -For the most part it should be rather intuitive. -The first part is special in that if it doesn't match any specific filter keyword it will be treated as a Blizzard name search. E.g., a query consisting only of **felcloth** would list the items Felcloth, Pattern: Felcloth Hood, Felcloth Bag etc. -Usually you would want to use the **exact** modifier which only matches auctions where the name, apart from case, exactly equals the first part of the query. -**exact** is the only modifier which is part Blizzard and part post filter, though it is mostly treated as a Blizzard filter. **exact** will tailor the Blizzard query as well as possible towards the item searched (level range, item class/subclass/slot, quality ...) and it cannot be used together with Blizzard filters for these properties. - -Post processing filters are more flexible. -They are specified using the filter primitives you find on the right side of the "New Filter" sub-tab and can be combined with **and**, **or** and **not** using polish notation (https://en.wikipedia.org/wiki/Polish_notation). -Filter parts other than the first which don't match any specific filter, just like the first part is treated as a Blizzard name filter, are treated as a tooltip filter. -For using a tooltip filter as the first filter part there is an explicit **tooltip** modifier. - -Here are some queries I use myself for illustration: - -**or/and2/profit/5g/percent/60/and3/bid-profit/5g/bid-percent/60/left/30m**
-This filter will search the whole auction house for auctions either with a buyout price of 5g or more below market value and 60% or less of the market value or a bid price for which the same is true and in addition only 30m or less remaining. - -**wrangler's wristbands/exact/or2/and2/+3 agility/+3 stamina/+5 stamina/price/1g**
-This will search for wrangler's wristband with 3/3 monkey or 5 stam suffixes for at most 1g buyout price. - -**recipe/usable/not/libram**
-This will scan for usable recipes and exclude those with "libram" in the tooltip (i.e., librams) - -**armor/cloth/50/intellect/stamina**
-This will scan the auction house for cloth armor which has a requirement of at least lvl 50 as well both intellect and stamina stats. - -## Historical Value - -aux condenses the prices you've scanned during a day (midnight to midnight) into a single value, similarly to retail Auctioneer's "stat-simple" module. -This daily value is calculated as the minimum buyout of the day which in practice gives a similar enough value to that of retail TSM while using much less memory. -Limiting the memory usage is important because like Auctioneer and unlike TSM aux is using a day based instead of scan based interval and thus has to store the daily progress in the savedvariables. -Finally these daily values are collected in a list of the last 11 of them from which the market value is taken as the median. The values are weighted by their age but it doesn't have a large effect unless they're older than a month. - -The bottom line is that you get a fairly accurate market value for both very active markets as well as rarer items that has a reasonably short reaction time to market changes, recovers easily and never needs to be reset while still being reasonably stable and hard to manipulate and dealing with outliers very well, not getting distorted by multiple scans per day, not needing a single full scan per day but instead naturally picking up every price you scan while going about your usual business, letting you focus on a certain part of the auction house by scanning only that part regularly and avoiding information overload by giving you a single concise value for the tooltip. diff --git a/aux-addon/README_Geojak.md b/aux-addon/README_Geojak.md deleted file mode 100644 index 064b4ac..0000000 --- a/aux-addon/README_Geojak.md +++ /dev/null @@ -1,10 +0,0 @@ -1. Added auto bid feature, doubled the existing auto buy code and slighly editted -2. Added auto pricing heuristic, these are activate when posting an item with bid price 0, the buyout price works as a minimum -3. Added a keybind for the post button, this significantly speeds up mass posting of items -4. Changed the default post stack size to rand(1,max) instead of always max -5. FIXED TWILLIGHT CULTIST SET NOT DISENCHATABLE -6. use SHagu tweaks vendor values if aux values not avaialbe -7. dont auto bid on own items -8. new filter disenchant-percent and bid-disencahnt-percent -9. enchanting created wands are not encahntabke, same with twillight stuff at 5. reworked implementation for easier addition -10. disenchant values are calcualted from min(value,value_today), this protects users from low enchanting mat prices \ No newline at end of file diff --git a/aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP b/aux-addon/WORLDSTATEFINALSCORE-HIGHLIGHT.BLP deleted file mode 100644 index 2abd5465ad0449a65b3cdf9007ad929c0daaa704..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3908 zcmeH~y=&Y^7{({U0a=)23m03sFp8XupPUUTV8eJG?I0|CWmf^kavbmsfVk z`IBQk-tg~#wO!}a_wAVd4cqbCQ~Im(y43v)o3FLM&vETcw||ZO&fW#*`&LqS$f)#p zP^f>N7X|t1)yWCTc9QL>AGrkzd4xhr>3NZx`aGt{*ZiYm>Sw{$*5M&ZKeO0FeHNrX z+wBnXf?~*e&+@GQf-Io_I_)qEiefS$^|)IOP_h8}DZhGt_M@EzIDeGN_DIjN#C&Ln zoTnXwlgUI8j@z*ruf`qPVLR^idI|X#`jfwbSupO>ub~~cV^k;OuHIk213S-ilIyKe zD*H$((hqGZElo#Qv~k^wey<;L{J7iiB+x_A*fkcF4XFOWko`fWJLh2>IF4mSu8s%l zeS$J->V4C;P^dS@E8Ve9(z6_lo7VkP{rC6xw`S?ic}@M7^yj>F-HlD%AQR4C>0VSs z{A9r_{f+L1?B}5v-3_UGo-+>g{%H?Us5941CP;ur=g-mTPu&vuBlEJDslOZ^9cjId z?$gBUC#Q7BzNsVhRfH}o4nU*xOkx|V9@{9Z_dg*KR*x(FA2R=c_)u8P{|H~FqG0}q zdpYionxJ6*r_KD2@sjx;{Y&P5lKG!x-e>tx<%|648|&=2!J$^6gxnfE!~ka?eWoy_~}Z^*n)KXo$i zvmf(4{d|qg`|MX|zq58m|F=}<1N+-Z-H#Ffe81=O;xr2)_YoFGy{$=}x8u~eV6X1z zdz?D)zNe0#vazk}sTc42ELh#q_p|20dgL-to;=f}4jjLBW{2!=hr1iRw|L&PpEvM5 zfB@zBvLDZr_3c=H2Q;=*pZ<7mY&YaOS)UyDI}-g3c|Kf^;o6yAKlhK%o)-6iIvmb( z{}}a%`^PW`W$D<$$!7W|F{^4#aK@P+l%Z-LCK_!n8k)jj$VAifRZ diff --git a/aux-addon/aux-addon.lua b/aux-addon/aux-addon.lua deleted file mode 100644 index b86e066..0000000 --- a/aux-addon/aux-addon.lua +++ /dev/null @@ -1,243 +0,0 @@ -module 'aux' - -local T = require 'T' -local post = require 'aux.tabs.post' - -M.print = T.vararg-function(arg) - DEFAULT_CHAT_FRAME:AddMessage(LIGHTYELLOW_FONT_COLOR_CODE .. ' ' .. join(map(arg, tostring), ' ')) -end - -local bids_loaded -function M.bids_loaded() return bids_loaded end - -local current_owner_page -function M.current_owner_page() return current_owner_page end - -local event_frame = CreateFrame'Frame' -for event in T.temp-T.set('ADDON_LOADED', 'VARIABLES_LOADED', 'PLAYER_LOGIN', 'AUCTION_HOUSE_SHOW', 'AUCTION_HOUSE_CLOSED', 'AUCTION_BIDDER_LIST_UPDATE', 'AUCTION_OWNED_LIST_UPDATE') do - event_frame:RegisterEvent(event) -end - -local set_handler = {} -M.handle = setmetatable({}, {__metatable=false, __newindex=function(_, k, v) set_handler[k](v) end}) - -do - local handlers, handlers2 = {}, {} - function set_handler.LOAD(f) - tinsert(handlers, f) - end - function set_handler.LOAD2(f) - tinsert(handlers2, f) - end - event_frame:SetScript('OnEvent', function() - if event == 'ADDON_LOADED' then - if arg1 == 'Blizzard_AuctionUI' then - auction_ui_loaded() - end - elseif event == 'VARIABLES_LOADED' then - for _, f in handlers do f() end - elseif event == 'PLAYER_LOGIN' then - for _, f in handlers2 do f() end - print('loaded - /aux') - else - _M[event]() - end - end) -end - -function handle.LOAD() - _G.aux = aux or {} - assign(aux, { - account = {}, - realm = {}, - faction = {}, - character = {}, - }) - M.account_data = assign(aux.account, { - scale = 1, - ignore_owner = true, - crafting_cost = true, - post_bid = false, - post_duration = post.DURATION_24, - items = {}, - item_ids = {}, - auctionable_items = {}, - merchant_buy = {}, - merchant_sell = {}, - }) - do - local key = format('%s|%s', GetCVar'realmName', UnitName'player') - aux.character[key] = aux.character[key] or {} - M.character_data = assign(aux.character[key], { - tooltip = { - value = true, - merchant_sell = false, - merchant_buy = false, - daily = false, - disenchant_value = false, - disenchant_distribution = false, - } - }) - end - do - local key = GetCVar'realmName' - aux.realm[key] = aux.realm[key] or {} - M.realm_data = assign(aux.realm[key], { - characters = {}, - recent_searches = {}, - favorite_searches = {}, - }) - end -end - -function handle.LOAD2() - local key = format('%s|%s', GetCVar'realmName', UnitFactionGroup'player') - aux.faction[key] = aux.faction[key] or {} - M.faction_data = assign(aux.faction[key], { - history = {}, - post = {}, - }) -end - -tab_info = {} -function M.tab(name) - local tab = T.map('name', name) - local tab_event = { - OPEN = function(f) tab.OPEN = f end, - CLOSE = function(f) tab.CLOSE = f end, - USE_ITEM = function(f) tab.USE_ITEM = f end, - CLICK_LINK = function(f) tab.CLICK_LINK = f end, - } - tinsert(tab_info, tab) - return setmetatable({}, {__metatable=false, __newindex=function(_, k, v) tab_event[k](v) end}) -end - -do - local index - function M.get_tab() return tab_info[index] end - function on_tab_click(i) - CloseDropDownMenus() - do (index and get_tab().CLOSE or pass)() end - index = i - do (index and get_tab().OPEN or pass)() end - end -end - -M.orig = setmetatable({[_G]=T.acquire()}, {__index=function(self, key) return self[_G][key] end}) -M.hook = T.vararg-function(arg) - local name, object, handler - if getn(arg) == 3 then - name, object, handler = unpack(arg) - else - object, name, handler = _G, unpack(arg) - end - handler = handler or getfenv(3)[name] - orig[object] = orig[object] or T.acquire() - assert(not orig[object][name], '"' .. name .. '" is already hooked into.') - orig[object][name], object[name] = object[name], handler - return hook -end - -do - local locked - function M.bid_in_progress() return locked end - function M.place_bid(type, index, amount, on_success) - if locked then return end - local money = GetMoney() - PlaceAuctionBid(type, index, amount) - if money >= amount then - locked = true - local send_signal, signal_received = signal() - thread(when, signal_received, function() - do (on_success or pass)() end - locked = false - end) - thread(when, later(5), send_signal) - event_listener('CHAT_MSG_SYSTEM', function(kill) - if arg1 == ERR_AUCTION_BID_PLACED then - send_signal() - kill() - end - end) - end - end -end - -do - local locked - function M.cancel_in_progress() return locked end - function M.cancel_auction(index, on_success) - if locked then return end - locked = true - CancelAuction(index) - local send_signal, signal_received = signal() - thread(when, signal_received, function() - do (on_success or pass)() end - locked = false - end) - thread(when, later(5), send_signal) - event_listener('CHAT_MSG_SYSTEM', function(kill) - if arg1 == ERR_AUCTION_REMOVED then - send_signal() - kill() - end - end) - end -end - -function handle.LOAD2() - frame:SetScale(account_data.scale) -end - -function AUCTION_HOUSE_SHOW() - AuctionFrame:Hide() - frame:Show() - set_tab(1) -end - -do - local handlers = {} - function set_handler.CLOSE(f) - tinsert(handlers, f) - end - function AUCTION_HOUSE_CLOSED() - bids_loaded = false - current_owner_page = nil - for _, handler in handlers do - handler() - end - set_tab() - frame:Hide() - end -end - -function AUCTION_BIDDER_LIST_UPDATE() - bids_loaded = true -end - -do - local last_owner_page_requested - function GetOwnerAuctionItems(index) - last_owner_page_requested = index - return orig.GetOwnerAuctionItems(index) - end - function AUCTION_OWNED_LIST_UPDATE() - current_owner_page = last_owner_page_requested or 0 - end -end - -function auction_ui_loaded() - AuctionFrame:UnregisterEvent('AUCTION_HOUSE_SHOW') - AuctionFrame:SetScript('OnHide', nil) - hook('ShowUIPanel', T.vararg-function(arg) - if arg[1] == AuctionFrame then return AuctionFrame:Show() end - return orig.ShowUIPanel(unpack(arg)) - end) - hook 'GetOwnerAuctionItems' 'SetItemRef' 'UseContainerItem' 'AuctionFrameAuctions_OnEvent' -end - -AuctionFrameAuctions_OnEvent = T.vararg-function(arg) - if AuctionFrameAuctions:IsVisible() then - return orig.AuctionFrameAuctions_OnEvent(unpack(arg)) - end -end \ No newline at end of file diff --git a/aux-addon/aux-addon.toc b/aux-addon/aux-addon.toc deleted file mode 100644 index 919f718..0000000 --- a/aux-addon/aux-addon.toc +++ /dev/null @@ -1,57 +0,0 @@ -## Interface: 11200 -## Author: shirsig -## OptionalDeps: ShaguTweaks -## Title: aux -## Notes: Auction House replacement -## SavedVariables: aux - -libs\package.lua -libs\T.lua - -aux-addon.lua -util.lua -control.lua -color.lua -gui\core.lua -frame.lua -util\persistence.lua -util\sort.lua -util\money.lua -core\cache.lua -util\info.lua -core\history.lua -core\disenchant.lua -util\filter.lua -util\completion.lua -core\slash.lua -core\scan.lua -util\scan.lua -core\stack.lua -core\post.lua -core\tooltip.lua -core\shortcut.lua - -gui\listing.lua -gui\auction_listing.lua -gui\item_listing.lua - -tabs\search\core.lua -tabs\search\results.lua -tabs\search\saved.lua -tabs\search\filter.lua -tabs\search\frame.lua - -tabs\post\core.lua -tabs\post\frame.lua - -tabs\auctions\core.lua -tabs\auctions\frame.lua - -tabs\bids\core.lua -tabs\bids\frame.lua - -core\crafting.lua - -localization.lua - - diff --git a/aux-addon/color.lua b/aux-addon/color.lua deleted file mode 100644 index cb7e2ce..0000000 --- a/aux-addon/color.lua +++ /dev/null @@ -1,51 +0,0 @@ -module 'aux' - -local T = require 'T' - -function C(r, g, b, a) - local mt = T.map('__metatable', false, '__newindex', pass, 'color', T.list(r, g, b, a)) - function mt:__call(text) - local r, g, b, a = unpack(mt.color) - if text then - return format('|c%02X%02X%02X%02X', a, r, g, b) .. text .. FONT_COLOR_CODE_CLOSE - else - return r/255, g/255, b/255, a - end - end - function mt:__concat(text) - local r, g, b, a = unpack(mt.color) - return format('|c%02X%02X%02X%02X', a, r, g, b) .. text - end - return setmetatable(T.acquire(), mt) -end - -M.color = immutable-{ - none = setmetatable({}, {__metatable=false, __newindex=pass, __call=function(_, v) return v end, __concat=function(_, v) return v end}), - text = immutable-{enabled = C(255, 254, 250, 1), disabled = C(147, 151, 139, 1)}, - label = immutable-{enabled = C(216, 225, 211, 1), disabled = C(150, 148, 140, 1)}, - link = C(153, 255, 255, 1), - window = immutable-{background = C(24, 24, 24, .93), border = C(30, 30, 30, 1)}, - panel = immutable-{background = C(24, 24, 24, 1), border = C(255, 255, 255, .03)}, - content = immutable-{background = C(42, 42, 42, 1), border = C(0, 0, 0, 0)}, - state = immutable-{enabled = C(70, 140, 70, 1), disabled = C(140, 70, 70, 1)}, - - tooltip = immutable-{ - value = C(255, 255, 154, 1), - merchant = C(204, 127, 25, 1), - disenchant = immutable-{ - value = C(25, 153, 153, 1), - distribution = C(204, 204, 51, 1), - source = C(178, 178, 178, 1), - } - }, - - blue = C(41, 146, 255, 1), - green = C(22, 255, 22, 1), - yellow = C(255, 255, 0, 1), - orange = C(255, 146, 24, 1), - red = C(255, 0, 0, 1), - gray = C(187, 187, 187, 1), - gold = C(255, 255, 154, 1), - - blizzard = C(0, 180, 255, 1), -} diff --git a/aux-addon/control.lua b/aux-addon/control.lua deleted file mode 100644 index 7875f4e..0000000 --- a/aux-addon/control.lua +++ /dev/null @@ -1,121 +0,0 @@ -module 'aux' - -local T = require 'T' - -local event_frame = CreateFrame'Frame' - -local listeners, threads = T.acquire(), T.acquire() - -local thread_id -function M.thread_id() return thread_id end - -function handle.LOAD() - event_frame:SetScript('OnUpdate', UPDATE) - event_frame:SetScript('OnEvent', EVENT) -end - -function EVENT() - for id, listener in listeners do - if listener.killed then - listeners[id] = nil - elseif event == listener.event then - listener.cb(listener.kill) - end - end -end - -do - function UPDATE() - for _, listener in listeners do - local event, needed = listener.event, false - for _, listener in listeners do - needed = needed or listener.event == event and not listener.killed - end - if not needed then - event_frame:UnregisterEvent(event) - end - end - - for id, thread in threads do - if thread.killed or not thread.k then - threads[id] = nil - else - local k = thread.k - thread.k = nil - thread_id = id - k() - thread_id = nil - end - end - end -end - -do - local id = 0 - function unique_id() - id = id + 1 - return id - end -end - -function M.kill_listener(listener_id) - local listener = listeners[listener_id] - if listener then - listener.killed = true - end -end - -function M.kill_thread(thread_id) - local thread = threads[thread_id] - if thread then - thread.killed = true - end -end - -function M.event_listener(event, cb) - local listener_id = unique_id() - listeners[listener_id] = T.map( - 'event', event, - 'cb', cb, - 'kill', T.vararg-function(arg) if getn(arg) == 0 or arg[1] then kill_listener(listener_id) end end - ) - event_frame:RegisterEvent(event) - return listener_id -end - -function M.on_next_event(event, callback) - event_listener(event, function(kill) callback(); kill() end) -end - -do - local mt = { - __call = function(self) - T.temp(self) - return self.f(unpack(self)) - end, - } - - M.thread = T.vararg-function(arg) - T.static(arg) - arg.f = tremove(arg, 1) - local thread_id = unique_id() - threads[thread_id] = T.map('k', setmetatable(arg, mt)) - return thread_id - end - - M.wait = T.vararg-function(arg) - T.static(arg) - arg.f = tremove(arg, 1) - threads[thread_id].k = setmetatable(arg, mt) - end -end - -M.when = T.vararg-function(arg) - local c = tremove(arg, 1) - local k = tremove(arg, 1) - if c() then - return k(unpack(arg)) - else - return wait(when, c, k, unpack(arg)) - end -end diff --git a/aux-addon/core/cache.lua b/aux-addon/core/cache.lua deleted file mode 100644 index 563045c..0000000 --- a/aux-addon/core/cache.lua +++ /dev/null @@ -1,211 +0,0 @@ -module 'aux.util.info' - -local T = require 'T' -local aux = require 'aux' -local persistence = require 'aux.util.persistence' - -local MIN_ITEM_ID = 1 -local MAX_ITEM_ID = 30000 - -local items_schema = {'tuple', '#', {name='string'}, {quality='number'}, {level='number'}, {class='string'}, {subclass='string'}, {slot='string'}, {max_stack='number'}, {texture='string'}} -local merchant_buy_schema = {'tuple', '#', {unit_price='number'}, {limited='boolean'}} - -function aux.handle.LOAD() - scan_wdb() - - aux.event_listener('MERCHANT_SHOW', on_merchant_show) - aux.event_listener('MERCHANT_CLOSED', on_merchant_closed) - aux.event_listener('MERCHANT_UPDATE', on_merchant_update) - aux.event_listener('BAG_UPDATE', on_bag_update) - - CreateFrame('Frame', nil, MerchantFrame):SetScript('OnUpdate', merchant_on_update) - - aux.event_listener('NEW_AUCTION_UPDATE', function() - local data = auction_sell_item() - if data then - local item_id = item_id(data.name) - if item_id then - aux.account_data.merchant_sell[item_id] = data.vendor_price / (max_item_charges(item_id) or data.count) - end - end - end) -end - -do - local characters = {} - function M.is_player(name) - return not not characters[name] - end - function aux.handle.LOAD() - characters = aux.realm_data.characters - for k, v in characters do - if GetTime() > v + 60 * 60 * 24 * 30 then - characters[k] = nil - end - end - characters[UnitName'player'] = GetTime() - end -end - -do - local sell_scan_queued, incomplete_buy_data - function on_merchant_show() - merchant_sell_scan() - incomplete_buy_data = not merchant_buy_scan() - end - function on_merchant_closed() - sell_scan_queued = nil - incomplete_buy_data = false - end - function on_merchant_update() - if incomplete_buy_data then - incomplete_buy_data = not merchant_buy_scan() - end - end - function on_bag_update() - if MerchantFrame:IsVisible() then - sell_scan_queued = true - end - end - function merchant_on_update() - if sell_scan_queued then - sell_scan_queued = nil - merchant_sell_scan() - end - end -end - -function merchant_loaded() - for i = 1, GetMerchantNumItems() do - if not GetMerchantItemLink(i) then - return false - end - end - return true -end - -function M.merchant_info(item_id) - local buy_info - if aux.account_data.merchant_buy[item_id] then - buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id]) - end - return aux.account_data.merchant_sell[item_id], buy_info and buy_info.unit_price, buy_info and buy_info.limited -end - -function M.item_info(item_id) - local data_string = aux.account_data.items[item_id] - if data_string then - local cached_data = persistence.read(items_schema, data_string) - return T.map( - 'name', cached_data.name, - 'itemstring', 'item:' .. item_id .. ':0:0:0', - 'quality', cached_data.quality, - 'level', cached_data.level, - 'class', cached_data.class, - 'subclass', cached_data.subclass, - 'slot', cached_data.slot, - 'max_stack', cached_data.max_stack, - 'texture', cached_data.texture - ) - end -end - -function M.item_id(item_name) - return aux.account_data.item_ids[strlower(item_name)] -end - -function merchant_buy_scan() - local incomplete_data - for i = 1, GetMerchantNumItems() do - local _, _, price, count, stock = GetMerchantItemInfo(i) - local link = GetMerchantItemLink(i) - if link then - local item_id = parse_link(link) - local new_unit_price, new_limited = price / count, stock >= 0 - if aux.account_data.merchant_buy[item_id] then - local buy_info = persistence.read(merchant_buy_schema, aux.account_data.merchant_buy[item_id]) - - local unit_price - if buy_info.limited and not new_limited then - unit_price = new_unit_price - elseif new_limited and not buy_info.limited then - unit_price = buy_info.unit_price - else - unit_price = min(buy_info.unit_price, new_unit_price) - end - - aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map( - 'unit_price', unit_price, - 'limited', buy_info.limited and new_limited - )) - else - aux.account_data.merchant_buy[item_id] = persistence.write(merchant_buy_schema, T.temp-T.map( - 'unit_price', new_unit_price, - 'limited', new_limited - )) - end - else - incomplete_data = true - end - end - - return not incomplete_data -end - -function merchant_sell_scan() - for slot in inventory() do - T.temp(slot) - local item_info = T.temp-container_item(unpack(slot)) - if item_info then - aux.account_data.merchant_sell[item_info.item_id] = item_info.tooltip_money / item_info.aux_quantity - end - end -end - -function scan_wdb(item_id) - item_id = item_id or MIN_ITEM_ID - - local processed = 0 - while processed <= 100 and item_id <= MAX_ITEM_ID do - local itemstring = 'item:' .. item_id - local name, _, quality, level, class, subclass, max_stack, slot, texture = GetItemInfo(itemstring) - if name and not aux.account_data.item_ids[strlower(name)] then - aux.account_data.item_ids[strlower(name)] = item_id - aux.account_data.items[item_id] = persistence.write(items_schema, T.temp-T.map( - 'name', name, - 'quality', quality, - 'level', level, - 'class', class, - 'subclass', subclass, - 'slot', slot, - 'max_stack', max_stack, - 'texture', texture - )) - local tooltip = tooltip('link', itemstring) - if auctionable(tooltip, quality) then - tinsert(aux.account_data.auctionable_items, strlower(name)) - end - processed = processed + 1 - end - item_id = item_id + 1 - end - - if item_id <= MAX_ITEM_ID then - aux.thread(aux.when, aux.later(.5), scan_wdb, item_id) - else - sort(aux.account_data.auctionable_items, function(a, b) return strlen(a) < strlen(b) or (strlen(a) == strlen(b) and a < b) end) - end -end - -function M.populate_wdb(item_id) - item_id = item_id or MIN_ITEM_ID - if item_id > MAX_ITEM_ID then - aux.print('Cache populated.') - return - end - if not GetItemInfo('item:' .. item_id) then - aux.print('Fetching item ' .. item_id .. '.') - AuxTooltip:SetHyperlink('item:' .. item_id) - end - aux.thread(populate_wdb, item_id + 1) -end \ No newline at end of file diff --git a/aux-addon/core/crafting.lua b/aux-addon/core/crafting.lua deleted file mode 100644 index b00df11..0000000 --- a/aux-addon/core/crafting.lua +++ /dev/null @@ -1,99 +0,0 @@ -module 'aux.core.crafting' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local money = require 'aux.util.money' -local history = require 'aux.core.history' -local search_tab = require 'aux.tabs.search' - -function aux.handle.LOAD() - if not aux.account_data.crafting_cost then - return - end - aux.event_listener('ADDON_LOADED', function() - if arg1 == 'Blizzard_CraftUI' then - craft_ui_loaded() - elseif arg1 == 'Blizzard_TradeSkillUI' then - trade_skill_ui_loaded() - end - end) -end - -do - local function cost_label(cost) - local label = LIGHTYELLOW_FONT_COLOR_CODE .. '(Total Cost: ' .. FONT_COLOR_CODE_CLOSE - label = label .. (cost and money.to_string2(cost, nil, LIGHTYELLOW_FONT_COLOR_CODE) or GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE) - label = label .. LIGHTYELLOW_FONT_COLOR_CODE .. ')' .. FONT_COLOR_CODE_CLOSE - return label - end - local function hook_quest_item(f) - f:SetScript('OnMouseUp', function() - if arg1 == 'RightButton' then - if aux.get_tab() then - aux.set_tab(1) - search_tab.set_filter(_G[this:GetName() .. 'Name']:GetText() .. '/exact') - search_tab.execute(nil, false) - end - end - end) - end - function craft_ui_loaded() - aux.hook('CraftFrame_SetSelection', T.vararg-function(arg) - local ret = T.temp-T.list(aux.orig.CraftFrame_SetSelection(unpack(arg))) - local id = GetCraftSelectionIndex() - local total_cost = 0 - for i = 1, GetCraftNumReagents(id) do - local link = GetCraftReagentItemLink(id, i) - if not link then - total_cost = nil - break - end - local item_id, suffix_id = info.parse_link(link) - local count = aux.select(3, GetCraftReagentInfo(id, i)) - local _, price, limited = info.merchant_info(item_id) - local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id) - if not value then - total_cost = nil - break - else - total_cost = total_cost + value * count - end - end - CraftReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) - return unpack(ret) - end) - for i = 1, 8 do - hook_quest_item(_G['CraftReagent' .. i]) - end - end - function trade_skill_ui_loaded() - aux.hook('TradeSkillFrame_SetSelection', T.vararg-function(arg) - local ret = T.temp-T.list(aux.orig.TradeSkillFrame_SetSelection(unpack(arg))) - local id = GetTradeSkillSelectionIndex() - local total_cost = 0 - for i = 1, GetTradeSkillNumReagents(id) do - local link = GetTradeSkillReagentItemLink(id, i) - if not link then - total_cost = nil - break - end - local item_id, suffix_id = info.parse_link(link) - local count = aux.select(3, GetTradeSkillReagentInfo(id, i)) - local _, price, limited = info.merchant_info(item_id) - local value = price and not limited and price or history.value(item_id .. ':' .. suffix_id) - if not value then - total_cost = nil - break - else - total_cost = total_cost + value * count - end - end - TradeSkillReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) - return unpack(ret) - end) - for i = 1, 8 do - hook_quest_item(_G['TradeSkillReagent' .. i]) - end - end -end \ No newline at end of file diff --git a/aux-addon/core/disenchant.lua b/aux-addon/core/disenchant.lua deleted file mode 100644 index 1752167..0000000 --- a/aux-addon/core/disenchant.lua +++ /dev/null @@ -1,172 +0,0 @@ -module 'aux.core.disenchant' - -local T = require 'T' -local history = require 'aux.core.history' - -local UNCOMMON, RARE, EPIC = 2, 3, 4 - -local ARMOR = T.set( - 'INVTYPE_HEAD', - 'INVTYPE_NECK', - 'INVTYPE_SHOULDER', - 'INVTYPE_BODY', - 'INVTYPE_CHEST', - 'INVTYPE_ROBE', - 'INVTYPE_WAIST', - 'INVTYPE_LEGS', - 'INVTYPE_FEET', - 'INVTYPE_WRIST', - 'INVTYPE_HAND', - 'INVTYPE_FINGER', - 'INVTYPE_TRINKET', - 'INVTYPE_CLOAK', - 'INVTYPE_HOLDABLE' -) - -local WEAPON = T.set( - 'INVTYPE_2HWEAPON', - 'INVTYPE_WEAPONMAINHAND', - 'INVTYPE_WEAPON', - 'INVTYPE_WEAPONOFFHAND', - 'INVTYPE_SHIELD', - 'INVTYPE_RANGED', - 'INVTYPE_RANGEDRIGHT' -) - -function M.value(slot, quality, level, item_id) - local expectation - for _, event in distribution(slot, quality, level, item_id) do - local value = history.value(event.item_id .. ':' .. 0) - if not value then - return - else - local value_today = history.market_value(event.item_id .. ':' .. 0) - if value_today then - value=min(value,value_today) - end - expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value - end - end - return expectation -end - -function M.distribution(slot, quality, level, item_id) - if not ARMOR[slot] and not WEAPON[slot] or level == 0 - --twillight cultist items are not disenchantable - or item_id==20408 or item_id==20407 or item_id==20406 - --enchanting created wands are not disenchantable - or item_id==11288 or item_id==11290 or item_id==11287 or item_id==11289 - then - return T.acquire() - end - - local function p(probability_armor, probability_weapon) - if ARMOR[slot] then - return probability_armor - elseif WEAPON[slot] then - return probability_weapon - end - end - - if quality == UNCOMMON then - if level <= 10 then - return T.temp-T.list( - T.temp-T.map('item_id', 10940, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.8, .2)), - T.temp-T.map('item_id', 10938, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .8)) - ) - elseif level <= 15 then - return T.temp-T.list( - T.temp-T.map('item_id', 10940, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 10939, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 20 then - return T.temp-T.list( - T.temp-T.map('item_id', 10940, 'min_quantity', 4, 'max_quantity', 6, 'probability', p(.75, .15)), - T.temp-T.map('item_id', 10998, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.15, .75)), - T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', .10) - ) - elseif level <= 25 then - return T.temp-T.list( - T.temp-T.map('item_id', 11083, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 11082, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 30 then - return T.temp-T.list( - T.temp-T.map('item_id', 11083, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 11134, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 35 then - return T.temp-T.list( - T.temp-T.map('item_id', 11137, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 11135, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 40 then - return T.temp-T.list( - T.temp-T.map('item_id', 11137, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 11174, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 45 then - return T.temp-T.list( - T.temp-T.map('item_id', 11176, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .2)), - T.temp-T.map('item_id', 11175, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', .05) - ) - elseif level <= 50 then - return T.temp-T.list( - T.temp-T.map('item_id', 11176, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)), - T.temp-T.map('item_id', 16202, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) - ) - elseif level <= 55 then - return T.temp-T.list( - T.temp-T.map('item_id', 16204, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.75, .22)), - T.temp-T.map('item_id', 16203, 'min_quantity', 1, 'max_quantity', 2, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) - ) - elseif level <= 60 then - return T.temp-T.list( - T.temp-T.map('item_id', 16204, 'min_quantity', 2, 'max_quantity', 5, 'probability', p(.75, .22)), - T.temp-T.map('item_id', 16203, 'min_quantity', 2, 'max_quantity', 3, 'probability', p(.2, .75)), - T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', p(.05, .03)) - ) - end - elseif quality == RARE then - if level <= 20 then - return T.temp-T.list(T.temp-T.map('item_id', 10978, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 25 then - return T.temp-T.list(T.temp-T.map('item_id', 11084, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 30 then - return T.temp-T.list(T.temp-T.map('item_id', 11138, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 35 then - return T.temp-T.list(T.temp-T.map('item_id', 11139, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 40 then - return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 45 then - return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 50 then - return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 55 then - return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005)) - elseif level <= 60 then - return T.temp-T.list(T.temp-T.map('item_id', 14344, 'min_quantity', 1, 'max_quantity', 1, 'probability', .995), T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', .005)) - end - elseif quality == EPIC then - if level <= 40 then - return T.temp-T.list(T.temp-T.map('item_id', 11177, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) - elseif level <= 45 then - return T.temp-T.list(T.temp-T.map('item_id', 11178, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) - elseif level <= 50 then - return T.temp-T.list(T.temp-T.map('item_id', 14343, 'min_quantity', 2, 'max_quantity', 4, 'probability', 1)) - elseif level <= 55 then - return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 1, 'probability', 1)) - elseif level <= 60 then - return T.temp-T.list(T.temp-T.map('item_id', 20725, 'min_quantity', 1, 'max_quantity', 2, 'probability', 1)) - end - end - return T.acquire() -end \ No newline at end of file diff --git a/aux-addon/core/history.lua b/aux-addon/core/history.lua deleted file mode 100644 index 140c77b..0000000 --- a/aux-addon/core/history.lua +++ /dev/null @@ -1,109 +0,0 @@ -module 'aux.core.history' - -local T = require 'T' -local aux = require 'aux' - -local persistence = require 'aux.util.persistence' - -local history_schema = {'tuple', '#', {next_push='number'}, {daily_min_buyout='number'}, {data_points={'list', ';', {'tuple', '@', {value='number'}, {time='number'}}}}} - -local value_cache = {} - -function aux.handle.LOAD2() - data = aux.faction_data.history -end - -do - local next_push = 0 - function get_next_push() - if time() > next_push then - local date = date('*t') - date.hour, date.min, date.sec = 24, 0, 0 - next_push = time(date) - end - return next_push - end -end - -function new_record() - return T.temp-T.map('next_push', get_next_push(), 'data_points', T.acquire()) -end - -function read_record(item_key) - local record = data[item_key] and persistence.read(history_schema, data[item_key]) or new_record() - if record.next_push <= time() then - push_record(record) - write_record(item_key, record) - end - return record -end - -function write_record(item_key, record) - data[item_key] = persistence.write(history_schema, record) - if value_cache[item_key] then - T.release(value_cache[item_key]) - value_cache[item_key] = nil - end -end - -function M.process_auction(auction_record) - local item_record = read_record(auction_record.item_key) - local unit_buyout_price = ceil(auction_record.buyout_price / auction_record.aux_quantity) - if unit_buyout_price > 0 and unit_buyout_price < (item_record.daily_min_buyout or aux.huge) then - item_record.daily_min_buyout = unit_buyout_price - write_record(auction_record.item_key, item_record) - end -end - -function M.data_points(item_key) - return read_record(item_key).data_points -end - -function M.value(item_key) - if not value_cache[item_key] or value_cache[item_key].next_push <= time() then - local item_record, value - item_record = read_record(item_key) - if getn(item_record.data_points) > 0 then - local total_weight, weighted_values = 0, T.temp-T.acquire() - for _, data_point in item_record.data_points do - local weight = .99 ^ aux.round((item_record.data_points[1].time - data_point.time) / (60 * 60 * 24)) - total_weight = total_weight + weight - tinsert(weighted_values, T.map('value', data_point.value, 'weight', weight)) - end - for _, weighted_value in weighted_values do - weighted_value.weight = weighted_value.weight / total_weight - end - value = weighted_median(weighted_values) - else - value = item_record.daily_min_buyout - end - value_cache[item_key] = T.map('value', value, 'next_push', item_record.next_push) - end - return value_cache[item_key].value -end - -function M.market_value(item_key) - return read_record(item_key).daily_min_buyout -end - -function weighted_median(list) - sort(list, function(a,b) return a.value < b.value end) - local weight = 0 - for _, v in ipairs(list) do - weight = weight + v.weight - if weight >= .5 then - return v.value - end - end -end - -function push_record(item_record) - if item_record.daily_min_buyout then - tinsert(item_record.data_points, 1, T.map('value', item_record.daily_min_buyout, 'time', item_record.next_push)) - while getn(item_record.data_points) > 11 do - T.release(item_record.data_points[getn(item_record.data_points)]) - tremove(item_record.data_points) - end - end - item_record.next_push, item_record.daily_min_buyout = get_next_push(), nil -end \ No newline at end of file diff --git a/aux-addon/core/post.lua b/aux-addon/core/post.lua deleted file mode 100644 index 0e8415f..0000000 --- a/aux-addon/core/post.lua +++ /dev/null @@ -1,184 +0,0 @@ -module 'aux.core.post' - -local aux = require 'aux' -local info = require 'aux.util.info' -local stack = require 'aux.core.stack' -local history = require 'aux.core.history' -local disenchant = require 'aux.core.disenchant' - -local state - -function aux.handle.CLOSE() - stop() -end - -function process() - if state.posted < state.count then - - local stacking_complete - - local send_signal, signal_received = aux.signal() - aux.when(signal_received, function() - local slot = signal_received()[1] - if slot then - return post_auction(slot, process) - else - return stop() - end - end) - - return stack.start(state.item_key, state.stack_size, send_signal) - end - - return stop() -end - -function post_auction(slot, k) - local item_info = info.container_item(unpack(slot)) - if item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and item_info.aux_quantity == state.stack_size then - - ClearCursor() - ClickAuctionSellItemButton() - ClearCursor() - PickupContainerItem(unpack(slot)) - ClickAuctionSellItemButton() - ClearCursor() - - local start_price = state.unit_start_price - local buyout_price = state.unit_buyout_price - - --use autoprice heuristic if start bid 0 is set - if start_price == 0 then - - local kz_daily = 0 - local kz_pricing = 0 - local kz_warn = 0 - - if buyout_price > 0 then - kz_pricing=1 - start_price = buyout_price - end - - if history.market_value(state.item_key) ~= nil then - local tmp = tonumber(history.market_value(state.item_key)) - tmp = max(0.99*tmp,tmp-50) - buyout_price = max(buyout_price,tmp) - kz_daily = 1 - end - - if history.value(state.item_key) ~= nil then - if kz_pricing == 1 and kz_daily == 1 then - buyout_price = max(buyout_price,1.0*tonumber(history.value(state.item_key))) - elseif kz_daily == 1 then - start_price = max(start_price,0.91*tonumber(history.value(state.item_key))) - else - buyout_price = max(buyout_price,0.99* tonumber(history.value(state.item_key))) - end - else - kz_warn = kz_warn + 1 - end - - if disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id) ~= nil then - start_price = max(start_price,0.85* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id))) - end - - if aux.account_data.merchant_buy[item_info.item_id] ~= nil then - local tmp = tostring(aux.account_data.merchant_buy[item_info.item_id]) - tmp = strsub(tmp,1,-3) - start_price = max(start_price,tonumber(tmp) * 1.1) - buyout_price = max(buyout_price,tonumber(tmp) * 1.15) - else - local vendor_price = 0 - if aux.account_data.merchant_sell[item_info.item_id] ~= nil then - vendor_price = tonumber(aux.account_data.merchant_sell[item_info.item_id]) - elseif ShaguTweaks.SellValueDB[item_info.item_id] ~= nil then - local charges = 1 - if info.max_item_charges(item_info.item_id) ~= nil then - charges=info.max_item_charges(item_info.item_id) - end - vendor_price = ShaguTweaks.SellValueDB[item_info.item_id] / charges - end - if vendor_price > 0 then - if kz_daily == 0 then - start_price = max(start_price, vendor_price * (1.35+3.65*math.exp(-(1/4000)* vendor_price))) - else - start_price = max(start_price, vendor_price * 1.35) - end - else - kz_warn = kz_warn + 1 - end - end - - start_price = max(start_price,0.91 * buyout_price) - buyout_price = max(start_price, buyout_price) - - if kz_warn == 2 and kz_pricing == 0 then - print("WARNING, insufficient data for autopricing!") - if start_price == 0 then - return stop() - end - end - - local gold = floor(start_price / COPPER_PER_GOLD) - local silver = floor(mod(start_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) - local copper = aux.round(mod(start_price, COPPER_PER_SILVER)) - - print("bid_price: "..gold.."g "..silver.."s "..copper.."c") - - gold = floor(buyout_price / COPPER_PER_GOLD) - silver = floor(mod(buyout_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) - copper = aux.round(mod(buyout_price, COPPER_PER_SILVER)) - - print("buyout_price: "..gold.."g "..silver.."s "..copper.."c") - - end - - StartAuction(max(1, aux.round(start_price * item_info.aux_quantity)), aux.round(buyout_price * item_info.aux_quantity), state.duration) - - local send_signal, signal_received = aux.signal() - aux.when(signal_received, function() - state.posted = state.posted + 1 - return k() - end) - - local posted - aux.event_listener('CHAT_MSG_SYSTEM', function(kill) - if arg1 == ERR_AUCTION_STARTED then - send_signal() - kill() - end - end) - else - return stop() - end -end - -function M.stop() - if state then - aux.kill_thread(state.thread_id) - - local callback = state.callback - local posted = state.posted - - state = nil - - if callback then - callback(posted) - end - end -end - -function M.start(item_key, stack_size, duration, unit_start_price, unit_buyout_price, count, callback) - stop() - state = { - thread_id = aux.thread(process), - item_key = item_key, - stack_size = stack_size, - duration = duration, - unit_start_price = unit_start_price, - unit_buyout_price = unit_buyout_price, - count = count, - posted = 0, - callback = callback, - } -end \ No newline at end of file diff --git a/aux-addon/core/scan.lua b/aux-addon/core/scan.lua deleted file mode 100644 index 7da6a61..0000000 --- a/aux-addon/core/scan.lua +++ /dev/null @@ -1,236 +0,0 @@ -module 'aux.core.scan' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local history = require 'aux.core.history' - -local PAGE_SIZE = 50 - -function aux.handle.CLOSE() - abort() -end - -do - local scan_states = {} - - function M.start(params) - local old_state = scan_states[params.type] - if old_state then - abort(old_state.id) - end - do (params.on_scan_start or pass)() end - local thread_id = aux.thread(scan) - scan_states[params.type] = { - id = thread_id, - params = params, - } - return thread_id - end - - function M.abort(scan_id) - local aborted = T.acquire() - for type, state in scan_states do - if not scan_id or state.id == scan_id then - aux.kill_thread(state.id) - scan_states[type] = nil - tinsert(aborted, state) - end - end - for _, state in aborted do - do (state.params.on_abort or pass)() end - end - end - - function M.stop() - get_state().stopped = true - end - - function complete() - local on_complete = get_state().params.on_complete - scan_states[get_state().params.type] = nil - do (on_complete or pass)() end - end - - function get_state() - for _, state in scan_states do - if state.id == aux.thread_id() then - return state - end - end - end -end - -function get_query() - return get_state().params.queries[get_state().query_index] -end - -function total_pages(total_auctions) - return ceil(total_auctions / PAGE_SIZE) -end - -function last_page(total_auctions) - local last_page = max(total_pages(total_auctions) - 1, 0) - local last_page_limit = get_query().blizzard_query.last_page or last_page - return min(last_page_limit, last_page) -end - -function scan() - get_state().query_index = get_state().query_index and get_state().query_index + 1 or 1 - if get_query() and not get_state().stopped then - do (get_state().params.on_start_query or pass)(get_state().query_index) end - if get_query().blizzard_query then - if (get_query().blizzard_query.first_page or 0) <= (get_query().blizzard_query.last_page or aux.huge) then - get_state().page = get_query().blizzard_query.first_page or 0 - return submit_query() - end - else - get_state().page = nil - return scan_page() - end - end - return complete() -end - -do - local function submit() - if get_state().params.type == 'bidder' then - GetBidderAuctionItems(get_state().page) - elseif get_state().params.type == 'owner' then - GetOwnerAuctionItems(get_state().page) - else - get_state().last_list_query = GetTime() - local blizzard_query = get_query().blizzard_query or T.acquire() - QueryAuctionItems( - blizzard_query.name, - blizzard_query.min_level, - blizzard_query.max_level, - blizzard_query.slot, - blizzard_query.class, - blizzard_query.subclass, - get_state().page, - blizzard_query.usable, - blizzard_query.quality - ) - end - return wait_for_results() - end - function submit_query() - if get_state().stopped then return end - if get_state().params.type ~= 'list' then - return submit() - else - return aux.when(CanSendAuctionQuery, submit) - end - end -end - -function scan_page(i) - i = i or 1 - - if i > PAGE_SIZE then - do (get_state().params.on_page_scanned or pass)() end - if get_query().blizzard_query and get_state().page < last_page(get_state().total_auctions) then - get_state().page = get_state().page + 1 - return submit_query() - else - return scan() - end - end - - local auction_info = info.auction(i, get_state().params.type) - if auction_info and (auction_info.owner or get_state().params.ignore_owner or aux.account_data.ignore_owner) then - auction_info.index = i - auction_info.page = get_state().page - auction_info.blizzard_query = get_query().blizzard_query - auction_info.query_type = get_state().params.type - - history.process_auction(auction_info) - - if (get_state().params.auto_bid_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then - local send_signal, signal_received = aux.signal() - aux.when(signal_received, scan_page, i) - return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.bid_price, send_signal) - elseif (get_state().params.auto_buy_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then - local send_signal, signal_received = aux.signal() - aux.when(signal_received, scan_page, i) - return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, send_signal) - elseif not get_query().validator or get_query().validator(auction_info) then - do (get_state().params.on_auction or pass)(auction_info) end - end - end - - return scan_page(i + 1) -end - -function wait_for_results() - if get_state().params.type == 'bidder' then - return aux.when(function() return aux.bids_loaded() end, accept_results) - elseif get_state().params.type == 'owner' then - return wait_for_owner_results() - elseif get_state().params.type == 'list' then - return wait_for_list_results() - end -end - -function accept_results() - _, get_state().total_auctions = GetNumAuctionItems(get_state().params.type) - do - (get_state().params.on_page_loaded or pass)( - get_state().page - (get_query().blizzard_query.first_page or 0) + 1, - last_page(get_state().total_auctions) - (get_query().blizzard_query.first_page or 0) + 1, - total_pages(get_state().total_auctions) - 1 - ) - end - return scan_page() -end - -function wait_for_owner_results() - if get_state().page == aux.current_owner_page() then - return accept_results() - else - local updated - aux.on_next_event('AUCTION_OWNED_LIST_UPDATE', function() updated = true end) - return aux.when(function() return updated end, accept_results) - end -end - -function wait_for_list_results() - local updated, last_update - local listener_id = aux.event_listener('AUCTION_ITEM_LIST_UPDATE', function() - last_update = GetTime() - updated = true - end) - local timeout = aux.later(5, get_state().last_list_query) - local ignore_owner = get_state().params.ignore_owner or aux.account_data.ignore_owner - return aux.when(function() - if not last_update and timeout() then - return true - end - if last_update and GetTime() - last_update > 5 then - return true - end - -- short circuiting order important, owner_data_complete must be called iif an update has happened. - if updated and (ignore_owner or owner_data_complete()) then - return true - end - updated = false - end, function() - aux.kill_listener(listener_id) - if not last_update and timeout() then - return submit_query() - else - return accept_results() - end - end) -end - -function owner_data_complete() - for i = 1, PAGE_SIZE do - local auction_info = info.auction(i, 'list') - if auction_info and not auction_info.owner then - return false - end - end - return true -end \ No newline at end of file diff --git a/aux-addon/core/shortcut.lua b/aux-addon/core/shortcut.lua deleted file mode 100644 index 84ac6fa..0000000 --- a/aux-addon/core/shortcut.lua +++ /dev/null @@ -1,31 +0,0 @@ -module 'aux.core.shortcut' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' - -do - local orig = SetItemRef - _G.SetItemRef = T.vararg-function(arg) - if arg[3] ~= 'RightButton' or not aux.index(aux.get_tab(), 'CLICK_LINK') or not strfind(arg[1], '^item:%d+') then - return orig(unpack(arg)) - end - local item_info = info.item(tonumber(aux.select(3, strfind(arg[1], '^item:(%d+)')))) - if item_info then - return aux.get_tab().CLICK_LINK(item_info) - end - end -end - -do - local orig = UseContainerItem - _G.UseContainerItem = T.vararg-function(arg) - if aux.modified() or not aux.get_tab() then - return orig(unpack(arg)) - end - local item_info = info.container_item(arg[1], arg[2]) - if item_info and aux.get_tab().USE_ITEM then - aux.get_tab().USE_ITEM(item_info) - end - end -end \ No newline at end of file diff --git a/aux-addon/core/slash.lua b/aux-addon/core/slash.lua deleted file mode 100644 index 5adb2f9..0000000 --- a/aux-addon/core/slash.lua +++ /dev/null @@ -1,74 +0,0 @@ -module 'aux.core.slash' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local post = require 'aux.tabs.post' - -function status(enabled) - return (enabled and aux.color.green'on' or aux.color.red'off') -end - -_G.SLASH_AUX1 = '/aux' -function SlashCmdList.AUX(command) - if not command then return end - local arguments = aux.tokenize(command) - local tooltip_settings = aux.character_data.tooltip - if arguments[1] == 'scale' and tonumber(arguments[2]) then - local scale = tonumber(arguments[2]) - aux.frame:SetScale(scale) - aux.account_data.scale = scale - elseif arguments[1] == 'ignore' and arguments[2] == 'owner' then - aux.account_data.ignore_owner = not aux.account_data.ignore_owner - aux.print('ignore owner ' .. status(aux.account_data.ignore_owner)) - elseif arguments[1] == 'post' and arguments[2] == 'bid' then - aux.account_data.post_bid = not aux.account_data.post_bid - aux.print('post bid ' .. status(aux.account_data.post_bid)) - elseif arguments[1] == 'post' and arguments[2] == 'duration' and T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]] then - aux.account_data.post_duration = T.map('2', post.DURATION_2, '8', post.DURATION_8, '24', post.DURATION_24)[arguments[3]] - aux.print('post duration ' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h')) - elseif arguments[1] == 'crafting' and arguments[2] == 'cost' then - aux.account_data.crafting_cost = not aux.account_data.crafting_cost - aux.print('crafting cost ' .. status(aux.account_data.crafting_cost)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'value' then - tooltip_settings.value = not tooltip_settings.value - aux.print('tooltip value ' .. status(tooltip_settings.value)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'daily' then - tooltip_settings.daily = not tooltip_settings.daily - aux.print('tooltip daily ' .. status(tooltip_settings.daily)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'buy' then - tooltip_settings.merchant_buy = not tooltip_settings.merchant_buy - aux.print('tooltip merchant buy ' .. status(tooltip_settings.merchant_buy)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'merchant' and arguments[3] == 'sell' then - tooltip_settings.merchant_sell = not tooltip_settings.merchant_sell - aux.print('tooltip merchant sell ' .. status(tooltip_settings.merchant_sell)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'value' then - tooltip_settings.disenchant_value = not tooltip_settings.disenchant_value - aux.print('tooltip disenchant value ' .. status(tooltip_settings.disenchant_value)) - elseif arguments[1] == 'tooltip' and arguments[2] == 'disenchant' and arguments[3] == 'distribution' then - tooltip_settings.disenchant_distribution = not tooltip_settings.disenchant_distribution - aux.print('tooltip disenchant distribution ' .. status(tooltip_settings.disenchant_distribution)) - elseif arguments[1] == 'clear' and arguments[2] == 'item' and arguments[3] == 'cache' then - aux.account_data.items = {} - aux.account_data.item_ids = {} - aux.account_data.auctionable_items = {} - aux.print('Item cache cleared.') - elseif arguments[1] == 'populate' and arguments[2] == 'wdb' then - info.populate_wdb() - else - aux.print('Usage:') - aux.print('- scale [' .. aux.color.blue(aux.account_data.scale) .. ']') - aux.print('- ignore owner [' .. status(aux.account_data.ignore_owner) .. ']') - aux.print('- post bid [' .. status(aux.account_data.post_bid) .. ']') - aux.print('- post duration [' .. aux.color.blue(aux.account_data.post_duration / 60 .. 'h') .. ']') - aux.print('- crafting cost [' .. status(aux.account_data.crafting_cost) .. ']') - aux.print('- tooltip value [' .. status(tooltip_settings.value) .. ']') - aux.print('- tooltip daily [' .. status(tooltip_settings.daily) .. ']') - aux.print('- tooltip merchant buy [' .. status(tooltip_settings.merchant_buy) .. ']') - aux.print('- tooltip merchant sell [' .. status(tooltip_settings.merchant_sell) .. ']') - aux.print('- tooltip disenchant value [' .. status(tooltip_settings.disenchant_value) .. ']') - aux.print('- tooltip disenchant distribution [' .. status(tooltip_settings.disenchant_distribution) .. ']') - aux.print('- clear item cache') - aux.print('- populate wdb') - end -end \ No newline at end of file diff --git a/aux-addon/core/stack.lua b/aux-addon/core/stack.lua deleted file mode 100644 index c954e38..0000000 --- a/aux-addon/core/stack.lua +++ /dev/null @@ -1,130 +0,0 @@ -module 'aux.core.stack' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' - -local state - -function aux.handle.CLOSE() - stop() -end - -function stack_size(slot) - local container_item_info = T.temp-info.container_item(unpack(slot)) - return container_item_info and container_item_info.count or 0 -end - -function charges(slot) - local container_item_info = T.temp-info.container_item(unpack(slot)) - return container_item_info and container_item_info.charges -end - -function max_stack(slot) - local container_item_info = T.temp-info.container_item(unpack(slot)) - return container_item_info and container_item_info.max_stack -end - -function locked(slot) - local container_item_info = T.temp-info.container_item(unpack(slot)) - return container_item_info and container_item_info.locked -end - -function find_item_slot(partial) - for slot in info.inventory() do - if matching_item(slot, partial) and not aux.eq(slot, state.target_slot) then - return slot - end - end -end - -function matching_item(slot, partial) - local item_info = T.temp-info.container_item(unpack(slot)) - return item_info and item_info.item_key == state.item_key and info.auctionable(item_info.tooltip, nil, true) and (not partial or item_info.count < item_info.max_stack) -end - -function find_empty_slot() - for slot, type in info.inventory() do - if type == 1 and not GetContainerItemInfo(unpack(slot)) then - return slot - end - end -end - -function find_charge_item_slot() - for slot in info.inventory() do - if matching_item(slot) and charges(slot) == state.target_size then - return slot - end - end -end - -function move_item(from_slot, to_slot, amount, k) - if locked(from_slot) or locked(to_slot) then - return aux.wait(k) - end - - amount = min(max_stack(from_slot) - stack_size(to_slot), stack_size(from_slot), amount) - local expected_size = stack_size(to_slot) + amount - - ClearCursor() - SplitContainerItem(from_slot[1], from_slot[2], amount) - PickupContainerItem(unpack(to_slot)) - - return aux.when(function() return stack_size(to_slot) == expected_size end, k) -end - -function process() - if not state.target_slot or not matching_item(state.target_slot) then - state.target_slot = find_item_slot() - if not state.target_slot then - return stop() - end - end - if charges(state.target_slot) then - state.target_slot = find_charge_item_slot() - return stop() - end - if stack_size(state.target_slot) > state.target_size then - local slot = find_item_slot(true) or find_empty_slot() - if slot then - return move_item( - state.target_slot, - slot, - stack_size(state.target_slot) - state.target_size, - process - ) - end - elseif stack_size(state.target_slot) < state.target_size then - local slot = find_item_slot() - if slot then - return move_item( - slot, - state.target_slot, - state.target_size - stack_size(state.target_slot), - process - ) - end - end - return stop() -end - -function M.stop() - if state then - aux.kill_thread(state.thread_id) - local callback, slot = state.callback, state.target_slot - slot = slot and matching_item(slot) and slot or nil - state = nil - do (callback or pass)(slot) end - end -end - -function M.start(item_key, size, callback) - stop() - state = { - thread_id = aux.thread(process), - item_key = item_key, - target_size = size, - callback = callback, - } -end \ No newline at end of file diff --git a/aux-addon/core/tooltip.lua b/aux-addon/core/tooltip.lua deleted file mode 100644 index 353e893..0000000 --- a/aux-addon/core/tooltip.lua +++ /dev/null @@ -1,223 +0,0 @@ -module 'aux.core.tooltip' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local money = require 'aux.util.money' -local disenchant = require 'aux.core.disenchant' -local history = require 'aux.core.history' -local gui = require 'aux.gui' - -local UNKNOWN = GRAY_FONT_COLOR_CODE .. '?' .. FONT_COLOR_CODE_CLOSE - -local game_tooltip_hooks, game_tooltip_money = {}, 0 - -function aux.handle.LOAD() - settings = aux.character_data.tooltip - do - local inside_hook = false - for name, f in game_tooltip_hooks do - local name, f = name, f - aux.hook(name, GameTooltip, T.vararg-function(arg) - game_tooltip_money = 0 - inside_hook = true - local tmp = T.list(aux.orig[GameTooltip][name](unpack(arg))) - inside_hook = false - f(unpack(arg)) - return T.unpack(tmp) - end) - end - SetTooltipMoney = SetTooltipMoney - _G.SetTooltipMoney = T.vararg-function(arg) - if inside_hook then - game_tooltip_money = arg[2] - else - return SetTooltipMoney(unpack(arg)) - end - end - end - local orig = SetItemRef - setglobal('SetItemRef', T.vararg-function(arg) - local name, _, quality = GetItemInfo(arg[1]) - local tmp = T.list(orig(unpack(arg))) - if not IsShiftKeyDown() and not IsControlKeyDown() and name then - local color_code = aux.select(4, GetItemQualityColor(quality)) - local link = color_code .. '|H' .. arg[1] .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE - extend_tooltip(ItemRefTooltip, link, 1) - end - return T.unpack(tmp) - end) -end - -function M.extend_tooltip(tooltip, link, quantity) - local item_id, suffix_id = info.parse_link(link) - quantity = IsShiftKeyDown() and quantity or 1 - local item_info = T.temp-info.item(item_id) - if item_info then - local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level, item_id) - if getn(distribution) > 0 then - if settings.disenchant_distribution then - tooltip:AddLine('Disenchants into:', aux.color.tooltip.disenchant.distribution()) - sort(distribution, function(a,b) return a.probability > b.probability end) - for _, event in ipairs(distribution) do - tooltip:AddLine(format(' %s%% %s (%s-%s)', event.probability * 100, info.display_name(event.item_id, true) or 'item:' .. event.item_id, event.min_quantity, event.max_quantity), aux.color.tooltip.disenchant.distribution()) - end - end - if settings.disenchant_value then - local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level, item_id) - tooltip:AddLine('Disenchant: ' .. (disenchant_value and money.to_string2(disenchant_value) or UNKNOWN), aux.color.tooltip.disenchant.value()) - end - end - end - if settings.merchant_buy then - local _, price, limited = info.merchant_info(item_id) - if price then - tooltip:AddLine('Vendor Buy ' .. (limited and '(limited): ' or ': ') .. money.to_string2(price * quantity), aux.color.tooltip.merchant()) - end - end - if settings.merchant_sell then - local price = info.merchant_info(item_id) - if price == nil and ShaguTweaks.SellValueDB[item_id] ~= nil then - local charges = 1 - if info.max_item_charges(item_id) ~= nil then - charges=info.max_item_charges(item_id) - end - price = ShaguTweaks.SellValueDB[item_id] / charges - end - if price ~= 0 then - tooltip:AddLine('Vendor: ' .. (price and money.to_string2(price * quantity) or UNKNOWN), aux.color.tooltip.merchant()) - end - end - local auctionable = not item_info or info.auctionable(T.temp-info.tooltip('link', item_info.itemstring), item_info.quality) - local item_key = (item_id or 0) .. ':' .. (suffix_id or 0) - local value = history.value(item_key) - if auctionable then - if settings.value then - tooltip:AddLine('Value: ' .. (value and money.to_string2(value * quantity) or UNKNOWN), aux.color.tooltip.value()) - end - if settings.daily then - local market_value = history.market_value(item_key) - tooltip:AddLine('Today: ' .. (market_value and money.to_string2(market_value * quantity) .. ' (' .. gui.percentage_historical(aux.round(market_value / value * 100)) .. ')' or UNKNOWN), aux.color.tooltip.value()) - end - end - - if tooltip == GameTooltip and game_tooltip_money > 0 then - SetTooltipMoney(tooltip, game_tooltip_money) - end - tooltip:Show() -end - -function game_tooltip_hooks:SetHyperlink(itemstring) - local name, _, quality = GetItemInfo(itemstring) - if name then - local hex = aux.select(4, GetItemQualityColor(quality)) - local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE - extend_tooltip(GameTooltip, link, 1) - end -end - -function game_tooltip_hooks:SetAuctionItem(type, index) - local link = GetAuctionItemLink(type, index) - if link then - extend_tooltip(GameTooltip, link, aux.select(3, GetAuctionItemInfo(type, index))) - end -end - -function game_tooltip_hooks:SetLootItem(slot) - local link = GetLootSlotLink(slot) - if link then - extend_tooltip(GameTooltip, link, aux.select(3, GetLootSlotInfo(slot))) - end -end - -function game_tooltip_hooks:SetQuestItem(qtype, slot) - local link = GetQuestItemLink(qtype, slot) - if link then - extend_tooltip(GameTooltip, link, aux.select(3, GetQuestItemInfo(qtype, slot))) - end -end - -function game_tooltip_hooks:SetQuestLogItem(qtype, slot) - local link = GetQuestLogItemLink(qtype, slot) - if link then - extend_tooltip(GameTooltip, link, aux.select(3, GetQuestLogRewardInfo(slot))) - end -end - -function game_tooltip_hooks:SetBagItem(bag, slot) - local link = GetContainerItemLink(bag, slot) - if link then - extend_tooltip(GameTooltip, link, aux.select(2, GetContainerItemInfo(bag, slot))) - end -end - -function game_tooltip_hooks:SetInboxItem(index) - local name, _, quantity = GetInboxItem(index) - local id = name and info.item_id(name) - if id then - local _, itemstring, quality = GetItemInfo(id) - local hex = aux.select(4, GetItemQualityColor(tonumber(quality))) - local link = hex .. '|H' .. itemstring .. '|h[' .. name .. ']|h' .. FONT_COLOR_CODE_CLOSE - extend_tooltip(GameTooltip, link, quantity) - end -end - -function game_tooltip_hooks:SetInventoryItem(unit, slot) - local link = GetInventoryItemLink(unit, slot) - if link then - extend_tooltip(GameTooltip, link, 1) - end -end - -function game_tooltip_hooks:SetMerchantItem(slot) - local link = GetMerchantItemLink(slot) - if link then - local quantity = aux.select(4, GetMerchantItemInfo(slot)) - extend_tooltip(GameTooltip, link, quantity) - end -end - -function game_tooltip_hooks:SetCraftItem(skill, slot) - local link, quantity - if slot then - link, quantity = GetCraftReagentItemLink(skill, slot), aux.select(3, GetCraftReagentInfo(skill, slot)) - else - link, quantity = GetCraftItemLink(skill), 1 - end - if link then - extend_tooltip(GameTooltip, link, quantity) - end -end - -function game_tooltip_hooks:SetCraftSpell(slot) - local link = GetCraftItemLink(slot) - if link then - extend_tooltip(GameTooltip, link, 1) - end -end - -function game_tooltip_hooks:SetTradeSkillItem(skill, slot) - local link, quantity - if slot then - link, quantity = GetTradeSkillReagentItemLink(skill, slot), aux.select(3, GetTradeSkillReagentInfo(skill, slot)) - else - link, quantity = GetTradeSkillItemLink(skill), 1 - end - if link then - extend_tooltip(GameTooltip, link, quantity) - end -end - -function game_tooltip_hooks:SetAuctionSellItem() - local name, _, quantity = GetAuctionSellItemInfo() - if name then - for slot in info.inventory() do - T.temp(slot) - local link = GetContainerItemLink(unpack(slot)) - if link and aux.select(5, info.parse_link(link)) == name then - extend_tooltip(GameTooltip, link, quantity) - return - end - end - end -end \ No newline at end of file diff --git a/aux-addon/frame.lua b/aux-addon/frame.lua deleted file mode 100644 index 819912f..0000000 --- a/aux-addon/frame.lua +++ /dev/null @@ -1,51 +0,0 @@ -module 'aux' - -local gui = require 'aux.gui' - -function handle.LOAD() - for _, v in ipairs(tab_info) do - tabs:create_tab(v.name) - end -end - -do - local frame = CreateFrame('Frame', 'aux_frame', UIParent) - tinsert(UISpecialFrames, 'aux_frame') - gui.set_window_style(frame) - gui.set_size(frame, 768, 447) - frame:SetPoint('LEFT', 100, 0) - frame:SetToplevel(true) - frame:SetMovable(true) - frame:EnableMouse(true) - frame:SetClampedToScreen(true) - frame:CreateTitleRegion():SetAllPoints() - frame:SetScript('OnShow', function() PlaySound('AuctionWindowOpen') end) - frame:SetScript('OnHide', function() PlaySound('AuctionWindowClose'); CloseAuctionHouse() end) - frame.content = CreateFrame('Frame', nil, frame) - frame.content:SetPoint('TOPLEFT', 4, -80) - frame.content:SetPoint('BOTTOMRIGHT', -4, 35) - frame:Hide() - M.frame = frame -end -do - tabs = gui.tabs(frame, 'DOWN') - tabs._on_select = on_tab_click - function M.set_tab(id) tabs:select(id) end -end -do - local btn = gui.button(frame) - btn:SetPoint('BOTTOMRIGHT', -5, 5) - gui.set_size(btn, 60, 24) - btn:SetText('Close') - btn:SetScript('OnClick', function() frame:Hide() end) - close_button = btn -end -do - local btn = gui.button(frame, gui.font_size.small) - btn:SetPoint('RIGHT', close_button, 'LEFT' , -5, 0) - gui.set_size(btn, 60, 24) - btn:SetText(color.blizzard'Blizzard UI') - btn:SetScript('OnClick',function() - if AuctionFrame:IsVisible() then HideUIPanel(AuctionFrame) else ShowUIPanel(AuctionFrame) end - end) -end \ No newline at end of file diff --git a/aux-addon/gui/auction_listing.lua b/aux-addon/gui/auction_listing.lua deleted file mode 100644 index 6b40ccf..0000000 --- a/aux-addon/gui/auction_listing.lua +++ /dev/null @@ -1,1046 +0,0 @@ -module 'aux.gui.auction_listing' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local sort_util = require 'aux.util.sort' -local money = require 'aux.util.money' -local history = require 'aux.core.history' -local gui = require 'aux.gui' -local tooltip = require 'aux.core.tooltip' - -local price_per_unit = false - -local HEAD_HEIGHT = 27 -local HEAD_SPACE = 2 - -local TIME_LEFT_STRINGS = { - aux.color.red'30m', -- Short - aux.color.orange'2h', -- Medium - aux.color.yellow'8h', -- Long - aux.color.blue'24h', -- Very Long -} - -function item_column_init(rt, cell) - local spacer = CreateFrame('Frame', nil, cell) - spacer:SetPoint('TOPLEFT', 0, 0) - spacer:SetHeight(rt.ROW_HEIGHT) - spacer:SetWidth(1) - cell.spacer = spacer - - local iconBtn = CreateFrame('Button', nil, cell) - iconBtn:SetPoint('TOPLEFT', spacer, 'TOPRIGHT') - iconBtn:SetHeight(rt.ROW_HEIGHT) - iconBtn:SetWidth(rt.ROW_HEIGHT) - iconBtn:SetScript('OnEnter', rt.OnIconEnter) - iconBtn:SetScript('OnLeave', rt.OnIconLeave) - iconBtn:SetScript('OnClick', rt.OnIconClick) - iconBtn:SetScript('OnDoubleClick', rt.OnIconDoubleClick) - local icon = iconBtn:CreateTexture(nil, 'ARTWORK') - icon:SetPoint('TOPLEFT', 2, -2) - icon:SetPoint('BOTTOMRIGHT', -2, 2) - icon:SetTexCoord(.08, .92, .08, .92) - - cell.iconBtn = iconBtn - cell.icon = icon - - cell.text:ClearAllPoints() - cell.text:SetPoint('TOPLEFT', iconBtn, 'TOPRIGHT', 2, 0) - cell.text:SetPoint('BOTTOMRIGHT', 0, 0) -end - -function item_column_fill(cell, record, _, _, _, indented) - cell.icon:SetTexture(record.texture) - if indented then - cell.spacer:SetWidth(10) - cell.icon:SetAlpha(.5) - cell.text:SetAlpha(.7) - else - cell.spacer:SetWidth(1) - cell.icon:SetAlpha(1) - cell.text:SetAlpha(1) - end - cell.text:SetText(gsub(record.link, '[%[%]]', '')) -end - -M.search_columns = { - { - title = 'Item', - width = .35, - init = item_column_init, - fill = item_column_fill, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.name, record_b.name, desc) - end, - }, - { - title = 'Lvl', - width = .035, - align = 'CENTER', - fill = function(cell, record) - local display_level = max(record.level, 1) - display_level = UnitLevel'player' < record.level and aux.color.red(display_level) or display_level - cell.text:SetText(display_level) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.level, record_b.level, desc) - end, - }, - { - title = 'Auctions', - width = .06, - align = 'CENTER', - fill = function(cell, record, count, own, expandable) - local numAuctionsText = expandable and aux.color.link(count) or count - if own > 0 then - numAuctionsText = numAuctionsText .. (' ' .. aux.color.yellow('(' .. own .. ')')) - end - cell.text:SetText(numAuctionsText) - end, - cmp = function(record_a, record_b, desc) - return sort_util.EQ --- if sortKey == 'numAuctions' then --- if a.children then --- aVal = a.totalAuctions --- bVal = b.totalAuctions --- else --- aVal = a.numAuctions --- bVal = b.numAuctions --- end --- end - end, - }, - { - title = 'Stack\nSize', - width = .055, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(record.aux_quantity) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) - end, - }, - { - title = 'Time\nLeft', - width = .04, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.duration, record_b.duration, desc) - end, - }, - { - title = 'Seller', - width = .13, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(info.is_player(record.owner) and (aux.color.yellow(record.owner)) or (record.owner or '?')) - end, - cmp = function(record_a, record_b, desc) - if not record_a.owner and not record_b.owner then - return sort_util.EQ - elseif not record_a.owner then - return sort_util.GT - elseif not record_b.owner then - return sort_util.LT - else - return sort_util.compare(record_a.owner, record_b.owner, desc) - end - end, - }, - { - title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price_color - if record.high_bidder then - price_color = aux.color.green - elseif record.high_bid ~= 0 then - price_color = aux.color.orange - end - local price - if record.high_bidder then - price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid - else - price = price_per_unit and ceil(record.unit_bid_price) or record.bid_price - end - cell.text:SetText(money.to_string(price, true, false, price_color)) - end, - cmp = function(record_a, record_b, desc) - local price_a - if record_a.high_bidder then - price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid - else - price_a = price_per_unit and record_a.unit_bid_price or record_a.bid_price - end - local price_b - if record_b.high_bidder then - price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid - else - price_b = price_per_unit and record_b.unit_bid_price or record_b.bid_price - end - if record_a.high_bidder and not record_b.high_bidder then - return sort_util.GT - elseif record_b.high_bidder and not record_a.high_bidder then - return sort_util.LT - end - if price_a == price_b then - if record_a.high_bid == 0 and record_b.high_bid ~= 0 then - return sort_util.GT - elseif record_b.high_bid == 0 and record_a.high_bid ~= 0 then - return sort_util.LT - end - end - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price - cell.text:SetText(price > 0 and money.to_string(price, true) or '---') - end, - cmp = function(record_a, record_b, desc) - local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price - local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price - price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) - price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) - - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = '% Hist.\nValue', - width = .08, - align = 'CENTER', - fill = function(cell, record) - local pct, bidPct = record_percentage(record) - cell.text:SetText((pct or bidPct) and gui.percentage_historical(pct or bidPct, not pct) or '?') - end, - cmp = function(record_a, record_b, desc) - local pct_a = record_percentage(record_a) or (desc and -aux.huge or aux.huge) - local pct_b = record_percentage(record_b) or (desc and -aux.huge or aux.huge) - return sort_util.compare(pct_a, pct_b, desc) - end, - }, -} - -M.auctions_columns = { - { - title = 'Item', - width = .35, - init = item_column_init, - fill = item_column_fill, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.name, record_b.name, desc) - end, - }, - { - title = 'Lvl', - width = .035, - align = 'CENTER', - fill = function(cell, record) - local display_level = max(record.level, 1) - display_level = UnitLevel('player') < record.level and aux.color.red(display_level) or display_level - cell.text:SetText(display_level) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.level, record_b.level, desc) - end, - }, - { - title = 'Auctions', - width = .06, - align = 'CENTER', - fill = function(cell, record, count, own, expandable) - local numAuctionsText = expandable and aux.color.link(count) or count - cell.text:SetText(numAuctionsText) - end, - cmp = function(record_a, record_b, desc) - return sort_util.EQ - -- if sortKey == 'numAuctions' then - -- if a.children then - -- aVal = a.totalAuctions - -- bVal = b.totalAuctions - -- else - -- aVal = a.numAuctions - -- bVal = b.numAuctions - -- end - -- end - end, - }, - { - title = 'Stack\nSize', - width = .055, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(record.aux_quantity) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) - end, - }, - { - title = 'Time\nLeft', - width = .04, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.duration, record_b.duration, desc) - end, - }, - { - title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price - if record.high_bidder then - price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid - else - price = price_per_unit and ceil(record.start_price / record.aux_quantity) or record.start_price - end - cell.text:SetText(money.to_string(price, true)) - end, - cmp = function(record_a, record_b, desc) - local price_a - if record_a.high_bidder then - price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid - else - price_a = price_per_unit and record_a.start_price / record_b.aux_quantity or record_a.start_price - end - local price_b - if record_b.high_bidder then - price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid - else - price_b = price_per_unit and record_b.start_price / record_b.aux_quantity or record_b.start_price - end - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price - cell.text:SetText(price > 0 and money.to_string(price, true) or '---') - end, - cmp = function(record_a, record_b, desc) - local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price - local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price - price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) - price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) - - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = 'High Bidder', - width = .21, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(record.high_bidder or aux.color.red 'No Bids') - end, - cmp = function(record_a, record_b, desc) - if not record_a.high_bidder and not record_b.high_bidder then - return sort_util.EQ - elseif not record_a.high_bidder then - return sort_util.GT - elseif not record_b.high_bidder then - return sort_util.LT - else - return sort_util.compare(record_a.high_bidder, record_b.high_bidder, desc) - end - end, - }, -} - -M.bids_columns = { - { - title = 'Item', - width = .35, - init = item_column_init, - fill = item_column_fill, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.name, record_b.name, desc) - end, - }, - { - title = 'Auctions', - width = .06, - align = 'CENTER', - fill = function(cell, record, count, own, expandable) - local numAuctionsText = expandable and aux.color.link(count) or count - cell.text:SetText(numAuctionsText) - end, - cmp = function(record_a, record_b, desc) - return sort_util.EQ - -- if sortKey == 'numAuctions' then - -- if a.children then - -- aVal = a.totalAuctions - -- bVal = b.totalAuctions - -- else - -- aVal = a.numAuctions - -- bVal = b.numAuctions - -- end - -- end - end, - }, - { - title = 'Stack\nSize', - width = .055, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(record.aux_quantity) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.aux_quantity, record_b.aux_quantity, desc) - end, - }, - { - title = 'Time\nLeft', - width = .04, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(TIME_LEFT_STRINGS[record.duration or 0] or '?') - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.duration, record_b.duration, desc) - end, - }, - { - title = 'Seller', - width = .13, - align = 'CENTER', - fill = function(cell, record) - cell.text:SetText(info.is_player(record.owner) and (aux.color.yellow(record.owner)) or (record.owner or '?')) - end, - cmp = function(record_a, record_b, desc) - if not record_a.owner and not record_b.owner then - return sort_util.EQ - elseif not record_a.owner then - return sort_util.GT - elseif not record_b.owner then - return sort_util.LT - else - return sort_util.compare(record_a.owner, record_b.owner, desc) - end - end, - }, - { - title = {'Auction Bid\n(per item)', 'Auction Bid\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price - if record.high_bidder then - price = price_per_unit and ceil(record.high_bid / record.aux_quantity) or record.high_bid - else - price = price_per_unit and ceil(record.unit_bid_price) or record.bid_price - end - cell.text:SetText(money.to_string(price)) - end, - cmp = function(record_a, record_b, desc) - local price_a - if record_a.high_bidder then - price_a = price_per_unit and record_a.high_bid / record_a.aux_quantity or record_a.high_bid - else - price_a = price_per_unit and record_a.unit_bid_price or record_a.bid_price - end - local price_b - if record_b.high_bidder then - price_b = price_per_unit and record_b.high_bid / record_b.aux_quantity or record_b.high_bid - else - price_b = price_per_unit and record_b.unit_bid_price or record_b.bid_price - end - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = {'Auction Buyout\n(per item)', 'Auction Buyout\n(per stack)'}, - width = .125, - align = 'RIGHT', - isPrice = true, - fill = function(cell, record) - local price = price_per_unit and ceil(record.unit_buyout_price) or record.buyout_price - cell.text:SetText(price > 0 and money.to_string(price, true) or '---') - end, - cmp = function(record_a, record_b, desc) - local price_a = price_per_unit and record_a.unit_buyout_price or record_a.buyout_price - local price_b = price_per_unit and record_b.unit_buyout_price or record_b.buyout_price - price_a = price_a > 0 and price_a or (desc and -aux.huge or aux.huge) - price_b = price_b > 0 and price_b or (desc and -aux.huge or aux.huge) - - return sort_util.compare(price_a, price_b, desc) - end, - }, - { - title = 'Status', - width = .115, - align = 'CENTER', - fill = function(cell, record) - local status - if record.high_bidder then - status = aux.color.yellow'High Bidder' - else - status = aux.color.red'Outbid' - end - cell.text:SetText(status) - end, - cmp = function(record_a, record_b, desc) - return sort_util.compare(record_a.high_bidder and 1 or 0, record_b.high_bidder and 1 or 0, desc) - end, - }, -} - -function record_percentage(record) - if not record then return end - - local historical_value = history.value(record.item_key) or 0 - if historical_value > 0 then - if record.unit_buyout_price > 0 then - return aux.round(100 * record.unit_buyout_price / historical_value) - end - return nil, aux.round(100 * record.unit_bid_price / historical_value) - end -end - -function M.time_left(code) - return TIME_LEFT_STRINGS[code] -end - -local methods = { - - ResizeColumns = function(self) - local weight = 0 - for _, cell in self.headCells do - weight = weight + cell.info.width - end - weight = (self.contentFrame:GetRight() - self.contentFrame:GetLeft()) / weight - for i, cell in self.headCells do - local width = cell.info.width * weight - cell:SetWidth(width) - for _, row in self.rows do - row.cells[i]:SetWidth(width) - end - end - end, - - OnHeadColumnClick = function() - local button = arg1 - local rt = this.rt - - if button == 'RightButton' and rt.headCells[this.columnIndex].info.isPrice then - price_per_unit = not price_per_unit - for _, cell in rt.headCells do - if cell.info.isPrice then - cell:SetText(cell.info.title[price_per_unit and 1 or 2]) - end - end - rt:SetSort() - return - end - - local descending = false - if getn(rt.sorts) > 0 and rt.sorts[1].index == this.columnIndex then - descending = not rt.sorts[1].descending - end - rt:SetSort((descending and -1 or 1) * this.columnIndex) - end, - - OnIconEnter = function() - local rt = this:GetParent().row.rt - local row = this:GetParent().row - if row.record then - GameTooltip:SetOwner(this, 'ANCHOR_RIGHT') - info.load_tooltip(GameTooltip, row.record.tooltip) - tooltip.extend_tooltip(GameTooltip, row.record.link, row.record.aux_quantity) - info.set_shopping_tooltip(row.record.slot) - end - end, - - OnIconLeave = function() - GameTooltip:Hide() - end, - - OnEnter = function() - local rt = this.rt - if rt.expanded[this.expandKey] then - GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) - GameTooltip:AddLine('Double-click to collapse this item and show only the cheapest auction.', 1, 1, 1, true) - GameTooltip:Show() - elseif this.expandable then - GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) - GameTooltip:AddLine('Double-click to expand this item and show all the auctions.', 1, 1, 1, true) - GameTooltip:Show() - end - - this.highlight:Show() - end, - - OnLeave = function() - GameTooltip:Hide() - if not this.rt.selected or this.rt.selected.search_signature ~= this.record.search_signature then - this.highlight:Hide() - end - end, - - OnClick = function() - local button = arg1 - if IsControlKeyDown() then - DressUpItemLink(this.record.link) - elseif IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then - ChatFrameEditBox:Insert(this.record.link) - else - local selection = this.rt:GetSelection() - if not selection or selection.record ~= this.record then - this.rt:SetSelectedRecord(this.record) - end - do (this.rt.handlers.OnClick or pass)(this, button) end - end - end, - - OnDoubleClick = function() - local rt = this.rt - local expand = not rt.expanded[this.expandKey] - - rt.expanded[this.expandKey] = expand - rt:UpdateRowInfo() - rt:UpdateRows() - if not this.indented then - rt:SetSelectedRecord(this.record) - end - end, - - UpdateRowInfo = function(self) - for _, v in ipairs(self.rowInfo) do - if type(v) == 'table' then - for _, child in v.children do - T.release(child) - end - T.release(v.children) - T.release(v) - end - end - T.wipe(self.rowInfo) - self.rowInfo.numDisplayRows = 0 - self.isSorted = nil - self:SetSelectedRecord(nil, true) - - local records = self.records - - local single_item = aux.all(records, function(record) return record.item_key == records[1].item_key end) - - sort(records, function(a, b) return a.search_signature < b.search_signature or a.search_signature == b.search_signature and tostring(a) < tostring(b) end) - - for i = 1, getn(records) do - local record = records[i] - local prevRecord = records[i - 1] - if prevRecord and record.search_signature == prevRecord.search_signature then - -- it's an identical auction to the previous row so increment the number of auctions - self.rowInfo[getn(self.rowInfo)].children[getn(self.rowInfo[getn(self.rowInfo)].children)].count = self.rowInfo[getn(self.rowInfo)].children[getn(self.rowInfo[getn(self.rowInfo)].children)].count + 1 - elseif not single_item and prevRecord and record.item_key == prevRecord.item_key then - -- it's the same base item as the previous row so insert a new auction - tinsert(self.rowInfo[getn(self.rowInfo)].children, T.map('count', 1, 'record', record)) - if self.expanded[self.rowInfo[getn(self.rowInfo)].expandKey] then - self.rowInfo.numDisplayRows = self.rowInfo.numDisplayRows + 1 - end - else - -- it's a different base item from the previous row - tinsert(self.rowInfo, T.map('item_key', record.item_key, 'expandKey', record.item_key, 'children', T.list(T.map('count', 1, 'record', record)))) - self.rowInfo.numDisplayRows = self.rowInfo.numDisplayRows + 1 - end - end - - for _, v in ipairs(self.rowInfo) do - local totalAuctions, totalPlayerAuctions = 0, 0 - for _, childInfo in v.children do - totalAuctions = totalAuctions + childInfo.count - if info.is_player(childInfo.record.owner) then - totalPlayerAuctions = totalPlayerAuctions + childInfo.count - end - end - v.totalAuctions = totalAuctions - v.totalPlayerAuctions = totalPlayerAuctions - end - end, - - UpdateRows = function(self) - if self.rowInfo.numDisplayRows > getn(self.rows) then - self.contentFrame:SetPoint('BOTTOMRIGHT', -15, 0) - else - self.contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) - end - self:ResizeColumns() - - FauxScrollFrame_Update(self.scrollFrame, self.rowInfo.numDisplayRows, getn(self.rows), self.ROW_HEIGHT) - - local maxOffset = max(self.rowInfo.numDisplayRows - getn(self.rows), 0) - if FauxScrollFrame_GetOffset(self.scrollFrame) > maxOffset then - FauxScrollFrame_SetOffset(self.scrollFrame, maxOffset) - end - - for _, cell in self.headCells do - local tex = cell:GetNormalTexture() - tex:SetTexture[[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]] - tex:SetTexCoord(.017, 1, .083, .909) - tex:SetAlpha(.5) - end - - if getn(self.sorts) > 0 then - local last_sort = self.sorts[1] - if last_sort.descending then - self.headCells[last_sort.index]:GetNormalTexture():SetTexture(.8, .6, 1, .8) - else - self.headCells[last_sort.index]:GetNormalTexture():SetTexture(.6, .8, 1, .8) - end - end - - if not self.isSorted then - local function sort_helper(a, b) - - local record_a, record_b - if a.children then - record_a = a.children[1].record - record_b = b.children[1].record - else - record_a = a.record - record_b = b.record - end - - for _, sort in self.sorts do - local ordering = self.columns[sort.index].cmp and self.columns[sort.index].cmp(record_a, record_b, sort.descending) or sort_util.EQ - - if ordering == sort_util.LT then - return true - elseif ordering == sort_util.GT then - return false - end - end - - return tostring(a) < tostring(b) - end - - for _, v in ipairs(self.rowInfo) do - sort(v.children, sort_helper) - end - sort(self.rowInfo, sort_helper) - self.isSorted = true - end - - for _, row in self.rows do - row:Hide() - end - local rowIndex = 1 - FauxScrollFrame_GetOffset(self.scrollFrame) - for _, v in ipairs(self.rowInfo) do - if self.expanded[v.expandKey] then - for j, childInfo in ipairs(v.children) do - self:SetRowInfo(rowIndex, childInfo.record, childInfo.count, 0, j > 1, false, v.expandKey) - rowIndex = rowIndex + 1 - end - else - self:SetRowInfo(rowIndex, v.children[1].record, v.totalAuctions, getn(v.children) > 1 and v.totalPlayerAuctions or 0, false, getn(v.children) > 1, v.expandKey) - rowIndex = rowIndex + 1 - end - end - end, - - SetRowInfo = function(self, rowIndex, record, totalAuctions, totalPlayerAuctions, indented, expandable, expandKey) - if rowIndex <= 0 or rowIndex > getn(self.rows) then return end - local row = self.rows[rowIndex] - row:Show() - if self.selected and record.search_signature == self.selected.search_signature then - row.highlight:Show() - else - row.highlight:Hide() - end - - row.record = record - row.expandable = expandable - row.indented = indented - row.expandKey = expandKey - - for i, column in self.columns do - column.fill(row.cells[i], record, totalAuctions, totalPlayerAuctions, expandable, indented) - end - end, - - SetSelectedRecord = function(self, record, silent) - self.selected = record - local selectedData = self:GetSelection() - self.selected = selectedData and self.selected or nil - - for _, row in self.rows do - if self.selected and row.record and row.record.search_signature == self.selected.search_signature then - row.highlight:Show() - else - row.highlight:Hide() - end - end - - if not silent and self.handlers.OnSelectionChanged then - self.handlers.OnSelectionChanged(self, selectedData or nil) - end - end, - - Reset = function(self) - T.wipe(self.expanded) - self:UpdateRowInfo() - self:UpdateRows() - self:SetSelectedRecord() - end, - - SetDatabase = function(self, database) - if database and database ~= self.records then - self.records = database - end - - local prevSelectedIndex - if self.selected then - for i, row in self.rows do - if row:IsVisible() and row.record == self.selected then - prevSelectedIndex = i - end - end - end - - self:UpdateRowInfo() - self:UpdateRows() - - if not self.selected and prevSelectedIndex then - -- try to select the same row - local row = self.rows[prevSelectedIndex] - if row and row:IsVisible() and row.record then - self:SetSelectedRecord(row.record) - end - if not self.selected then - -- select the first row - row = self.rows[1] - if row and row:IsVisible() and row.record then - self:SetSelectedRecord(row.record) - end - end - end - end, - - RemoveAuctionRecord = function(self, record) - local index = aux.key(self.records, record) - if index then - tremove(self.records, index) - end - self:SetDatabase() - end, - - ContainsRecord = function(self, record) - if aux.key(self.records, record) then - return true - end - end, - - SetSort = T.vararg-function(arg) - local self = tremove(arg, 1) - for _, v in ipairs(arg) do - for i, sort in self.sorts do - if sort.index == abs(v) then - tremove(self.sorts, i) - break - end - end - tinsert(self.sorts, 1, {index=abs(v), descending=v < 0}) - end - - self.isSorted = nil - self:UpdateRows() - end, - - SetHandler = function(self, event, handler) - self.handlers[event] = handler - end, - - GetSelection = function(self) - if not self.selected then return end - local selectedData - for _, v in ipairs(self.rowInfo) do - for _, childInfo in v.children do - if childInfo.record.search_signature == self.selected.search_signature then - selectedData = childInfo - break - end - end - end - return selectedData - end, -} - -function M.new(parent, rows, columns) - local rt = CreateFrame('Frame', nil, parent) - rt.columns = columns - rt.ROW_HEIGHT = (parent:GetHeight() - HEAD_HEIGHT - HEAD_SPACE) / rows - rt.expanded = {} - rt.handlers = {} - rt.sorts = {} - rt.records = {} - rt.rowInfo = {numDisplayRows=0} - - for name, func in methods do - rt[name] = func - end - - rt:SetScript('OnShow', function() - for _, cell in this.headCells do - if cell.info.isPrice then - cell:SetText(cell.info.title[price_per_unit and 1 or 2]) - end - end - end) - - local contentFrame = CreateFrame('Frame', nil, rt) - contentFrame:SetPoint('TOPLEFT', 0, 0) - contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) - rt.contentFrame = contentFrame - - local scrollFrame = CreateFrame('ScrollFrame', gui.unique_name(), rt, 'FauxScrollFrameTemplate') - scrollFrame:SetScript('OnVerticalScroll', function() - FauxScrollFrame_OnVerticalScroll(rt.ROW_HEIGHT, function() rt:UpdateRows() end) - end) - scrollFrame:SetAllPoints(contentFrame) - rt.scrollFrame = scrollFrame - FauxScrollFrame_Update(rt.scrollFrame, 0, rows, rt.ROW_HEIGHT) - - local scrollBar = _G[scrollFrame:GetName() .. 'ScrollBar'] - scrollBar:ClearAllPoints() - scrollBar:SetPoint('TOPRIGHT', rt, -4, -HEAD_HEIGHT) - scrollBar:SetPoint('BOTTOMRIGHT', rt, -4, 4) - scrollBar:SetWidth(10) - local thumbTex = scrollBar:GetThumbTexture() - thumbTex:SetPoint('CENTER', 0, 0) - thumbTex:SetTexture(aux.color.content.background()) - thumbTex:SetHeight(150) - thumbTex:SetWidth(scrollBar:GetWidth()) - _G[scrollBar:GetName() .. 'ScrollUpButton']:Hide() - _G[scrollBar:GetName() .. 'ScrollDownButton']:Hide() - - rt.headCells = {} - for i, column in ipairs(rt.columns) do - local cell = CreateFrame('Button', nil, rt.contentFrame) - cell:SetHeight(HEAD_HEIGHT) - if i == 1 then - cell:SetPoint('TOPLEFT', 0, 0) - else - cell:SetPoint('TOPLEFT', rt.headCells[i - 1], 'TOPRIGHT') - end - cell.info = column - cell.rt = rt - cell.columnIndex = i - cell:RegisterForClicks('LeftButtonUp', 'RightButtonUp') - - cell:SetScript('OnClick', rt.OnHeadColumnClick) - - local text = cell:CreateFontString() - text:SetJustifyH('CENTER') - text:SetFont(gui.font, 12) - text:SetTextColor(aux.color.label.enabled()) - cell:SetFontString(text) - if not column.isPrice then cell:SetText(column.title or '') end -- TODO - text:SetAllPoints() - - local tex = cell:CreateTexture() - tex:SetAllPoints() - tex:SetTexture([[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]]) - tex:SetTexCoord(.017, 1, .083, .909) - tex:SetAlpha(.5) - cell:SetNormalTexture(tex) - - local tex = cell:CreateTexture() - tex:SetAllPoints() - tex:SetTexture([[Interface\Buttons\UI-Listbox-Highlight]]) - tex:SetTexCoord(.025, .957, .087, .931) - tex:SetAlpha(.2) - cell:SetHighlightTexture(tex) - - tinsert(rt.headCells, cell) - end - - rt.rows = {} - for i = 1, rows do - local row = CreateFrame('Button', nil, rt.contentFrame) - row.rt = rt - row:SetHeight(rt.ROW_HEIGHT) - row:RegisterForClicks('LeftButtonUp', 'RightButtonUp') - row:SetScript('OnEnter', rt.OnEnter) - row:SetScript('OnLeave', rt.OnLeave) - row:SetScript('OnClick', rt.OnClick) - row:SetScript('OnDoubleClick', rt.OnDoubleClick) - if i == 1 then - row:SetPoint('TOPLEFT', 0, -(HEAD_HEIGHT + HEAD_SPACE)) - row:SetPoint('TOPRIGHT', 0, -(HEAD_HEIGHT + HEAD_SPACE)) - else - row:SetPoint('TOPLEFT', 0, -(HEAD_HEIGHT + HEAD_SPACE + (i - 1) * rt.ROW_HEIGHT)) - row:SetPoint('TOPRIGHT', 0, -(HEAD_HEIGHT + HEAD_SPACE + (i - 1) * rt.ROW_HEIGHT)) - end - local highlight = row:CreateTexture() - highlight:SetAllPoints() - highlight:SetTexture(1, .9, 0, .5) - highlight:Hide() - row.highlight = highlight - - row.cells = {} - for j, column in ipairs(rt.columns) do - local cell = CreateFrame('Frame', nil, row) - local text = cell:CreateFontString() - cell.text = text - text:SetFont(gui.font, min(14, rt.ROW_HEIGHT)) - text:SetJustifyH(column.align or 'LEFT') - text:SetJustifyV('CENTER') - text:SetPoint('TOPLEFT', 1, -1) - text:SetPoint('BOTTOMRIGHT', -1, 1) - cell:SetHeight(rt.ROW_HEIGHT) - cell.rt = rt - cell.row = row - - if j == 1 then - cell:SetPoint('TOPLEFT', 0, 0) - else - cell:SetPoint('TOPLEFT', row.cells[j - 1], 'TOPRIGHT') - end - - if mod(j, 2) == 1 then - local tex = cell:CreateTexture() - tex:SetAllPoints() - tex:SetTexture(.3, .3, .3, .2) - end - - if column.init then - column.init(rt, cell) - end - - tinsert(row.cells, cell) - end - - if mod(i, 2) == 0 then - local tex = row:CreateTexture() - tex:SetAllPoints() - tex:SetTexture(.3, .3, .3, .3) - end - - tinsert(rt.rows, row) - end - - rt:SetAllPoints() - rt:ResizeColumns() - return rt -end \ No newline at end of file diff --git a/aux-addon/gui/core.lua b/aux-addon/gui/core.lua deleted file mode 100644 index fdc0964..0000000 --- a/aux-addon/gui/core.lua +++ /dev/null @@ -1,569 +0,0 @@ -module 'aux.gui' - -local T = require 'T' -local aux = require 'aux' - -M.font = [[Fonts\ARIALN.TTF]] - -M.font_size = aux.immutable-{ - small = 13, - medium = 15, - large = 18, -} - ---function aux.handle.LOAD() --- do --- local blizzard_backdrop, aux_background, aux_border --- --- aux_border = DropDownList1:CreateTexture() --- aux_border:SetTexture(1, 1, 1, .02) --- aux_border:SetPoint('TOPLEFT', DropDownList1Backdrop, 'TOPLEFT', -2, 2) --- aux_border:SetPoint('BOTTOMRIGHT', DropDownList1Backdrop, 'BOTTOMRIGHT', 1.5, -1.5) --- aux_border:SetBlendMode('ADD') --- aux_background = DropDownList1:CreateTexture(nil, 'OVERLAY') --- aux_background:SetTexture(aux.color.content.background()) --- aux_background:SetAllPoints(DropDownList1Backdrop) --- blizzard_backdrop = DropDownList1Backdrop:GetBackdrop() --- aux.hook('ToggleDropDownMenu', function(...) --- T.temp(arg) --- local ret = T.temp-T.list(aux.orig.ToggleDropDownMenu(unpack(arg))) --- local dropdown = _G[arg[4] or ''] or this:GetParent() --- if strfind(dropdown:GetName() or '', '^aux.frame%d+$') then --- set_aux_dropdown_style(dropdown) --- else --- set_blizzard_dropdown_style() --- end --- return unpack(ret) --- end) --- --- function set_aux_dropdown_style(dropdown) --- DropDownList1Backdrop:SetBackdrop(T.empty) --- aux_border:Show() --- aux_background:Show() --- DropDownList1:SetWidth(dropdown:GetWidth() * .9) --- DropDownList1:SetHeight(DropDownList1:GetHeight() - 10) --- DropDownList1:ClearAllPoints() --- DropDownList1:SetPoint('TOPLEFT', dropdown, 'BOTTOMLEFT', -2, -2) --- for i = 1, UIDROPDOWNMENU_MAXBUTTONS do --- local button = _G['DropDownList1Button' .. i] --- button:SetPoint('TOPLEFT', 0, -((button:GetID() - 1) * UIDROPDOWNMENU_BUTTON_HEIGHT) - 7) --- button:SetPoint('TOPRIGHT', 0, -((button:GetID() - 1) * UIDROPDOWNMENU_BUTTON_HEIGHT) - 7) --- local text = button:GetFontString() --- text:SetFont(font, 14) --- text:SetPoint('TOPLEFT', 18, 0) --- text:SetPoint('BOTTOMRIGHT', -8, 0) --- local highlight = _G['DropDownList1Button' .. i .. 'Highlight'] --- highlight:ClearAllPoints() --- highlight:SetDrawLayer('OVERLAY') --- highlight:SetHeight(14) --- highlight:SetPoint('LEFT', 5, 0) --- highlight:SetPoint('RIGHT', -3, 0) --- local check = _G['DropDownList1Button' .. i .. 'Check'] --- check:SetWidth(16) --- check:SetHeight(16) --- check:SetPoint('LEFT', 3, -1) --- end --- end --- --- function set_blizzard_dropdown_style() --- DropDownList1Backdrop:SetBackdrop(blizzard_backdrop) --- aux_border:Hide() --- aux_background:Hide() --- for i = 1, UIDROPDOWNMENU_MAXBUTTONS do --- local button = _G['DropDownList1Button' .. i] --- local text = button:GetFontString() --- text:SetFont([[Fonts\FRIZQT__.ttf]], 10) --- text:SetShadowOffset(1, -1) --- local highlight = _G['DropDownList1Button' .. i .. 'Highlight'] --- highlight:SetAllPoints() --- highlight:SetDrawLayer('BACKGROUND') --- local check = _G['DropDownList1Button' .. i .. 'Check'] --- check:SetWidth(24) --- check:SetHeight(24) --- check:SetPoint('LEFT', 0, 0) --- end --- end --- end ---end - -do - local id = 1 - function M.unique_name() - id = id + 1 - return 'aux.frame' .. id - end -end - -do - local menu = CreateFrame('Frame', unique_name(), UIParent, 'UIDropDownMenuTemplate') - M.menu = T.vararg-function(arg) - HideDropDownMenu(1) - UIDropDownMenu_Initialize(menu, function() - for i = 1, getn(arg), 2 do - UIDropDownMenu_AddButton(T.map('text', arg[i], 'notCheckable', true, 'func', arg[i + 1])) - end - end, 'MENU') - ToggleDropDownMenu(1, nil, menu, 'cursor') - end -end - -function M.set_size(frame, width, height) - frame:SetWidth(width) - frame:SetHeight(height or width) -end - -function M.set_frame_style(frame, backdrop_color, border_color, left, right, top, bottom) - frame:SetBackdrop{bgFile=[[Interface\Buttons\WHITE8X8]], edgeFile=[[Interface\Buttons\WHITE8X8]], edgeSize=1.5, tile=true, insets={left=left, right=right, top=top, bottom=bottom}} - frame:SetBackdropColor(backdrop_color()) - frame:SetBackdropBorderColor(border_color()) -end - -function M.set_window_style(frame, left, right, top, bottom) - set_frame_style(frame, aux.color.window.background, aux.color.window.border, left, right, top, bottom) -end - -function M.set_panel_style(frame, left, right, top, bottom) - set_frame_style(frame, aux.color.panel.background, aux.color.panel.border, left, right, top, bottom) -end - -function M.set_content_style(frame, left, right, top, bottom) - set_frame_style(frame, aux.color.content.background, aux.color.content.border, left, right, top, bottom) -end - -function M.panel(parent) - local panel = CreateFrame('Frame', nil, parent) - set_panel_style(panel) - return panel -end - -function M.checkbutton(parent, text_height) - local button = button(parent, text_height) - button.state = false - button:SetBackdropColor(aux.color.state.disabled()) - function button:SetChecked(state) - if state then - self:SetBackdropColor(aux.color.state.enabled()) - self.state = true - else - self:SetBackdropColor(aux.color.state.disabled()) - self.state = false - end - end - function button:GetChecked() - return self.state - end - return button -end - -function M.button(parent, text_height) - text_height = text_height or font_size.large - local button = CreateFrame('Button', nil, parent) - set_size(button, 80, 24) - set_content_style(button) - local highlight = button:CreateTexture(nil, 'HIGHLIGHT') - highlight:SetAllPoints() - highlight:SetTexture(1, 1, 1, .2) - button.highlight = highlight - do - local label = button:CreateFontString() - label:SetFont(font, text_height) - label:SetAllPoints(button) - label:SetJustifyH('CENTER') - label:SetJustifyV('CENTER') - label:SetTextColor(aux.color.text.enabled()) - button:SetFontString(label) - end - button.default_Enable = button.Enable - function button:Enable() - if self:IsEnabled() == 1 then return end - self:GetFontString():SetTextColor(aux.color.text.enabled()) - return self:default_Enable() - end - button.default_Disable = button.Disable - function button:Disable() - if self:IsEnabled() == 0 then return end - self:GetFontString():SetTextColor(aux.color.text.disabled()) - return self:default_Disable() - end - - return button -end - -do - local mt = {__index=T.acquire()} - function mt.__index:create_tab(text) - local id = getn(self._tabs) + 1 - - local tab = CreateFrame('Button', unique_name(), self._frame) - tab.id = id - tab.group = self - tab:SetHeight(24) - set_panel_style(tab) - local dock = tab:CreateTexture(nil, 'OVERLAY') - dock:SetHeight(3) - if self._orientation == 'UP' then - dock:SetPoint('BOTTOMLEFT', 1, -1) - dock:SetPoint('BOTTOMRIGHT', -1, -1) - elseif self._orientation == 'DOWN' then - dock:SetPoint('TOPLEFT', 1, 1) - dock:SetPoint('TOPRIGHT', -1, 1) - end - dock:SetTexture(aux.color.panel.background()) - tab.dock = dock - local highlight = tab:CreateTexture(nil, 'HIGHLIGHT') - highlight:SetAllPoints() - highlight:SetTexture(1, 1, 1, .2) - tab.highlight = highlight - - tab.text = tab:CreateFontString() - tab.text:SetAllPoints() - tab.text:SetJustifyH('CENTER') - tab.text:SetJustifyV('CENTER') - tab.text:SetFont(font, font_size.large) - tab:SetFontString(tab.text) - - tab:SetText(text) - - tab:SetScript('OnClick', function() - if this.id ~= this.group.selected then - PlaySound('igCharacterInfoTab') - this.group:select(this.id) - end - end) - - if getn(self._tabs) == 0 then - if self._orientation == 'UP' then - tab:SetPoint('BOTTOMLEFT', self._frame, 'TOPLEFT', 4, -1) - elseif self._orientation == 'DOWN' then - tab:SetPoint('TOPLEFT', self._frame, 'BOTTOMLEFT', 4, 1) - end - else - if self._orientation == 'UP' then - tab:SetPoint('BOTTOMLEFT', self._tabs[getn(self._tabs)], 'BOTTOMRIGHT', 4, 0) - elseif self._orientation == 'DOWN' then - tab:SetPoint('TOPLEFT', self._tabs[getn(self._tabs)], 'TOPRIGHT', 4, 0) - end - end - - tab:SetWidth(tab:GetFontString():GetStringWidth() + 14) - - tinsert(self._tabs, tab) - end - function mt.__index:select(id) - self._selected = id - self:update() - do (self._on_select or pass)(id) end - end - function mt.__index:update() - for _, tab in self._tabs do - if tab.group._selected == tab.id then - tab.text:SetTextColor(aux.color.label.enabled()) - tab:Disable() - tab:SetBackdropColor(aux.color.panel.background()) - tab.dock:Show() - tab:SetHeight(29) - else - tab.text:SetTextColor(aux.color.text.enabled()) - tab:Enable() - tab:SetBackdropColor(aux.color.content.background()) - tab.dock:Hide() - tab:SetHeight(24) - end - end - end - function M.tabs(parent, orientation) - local self = { - _frame = parent, - _orientation = orientation, - _tabs = T.acquire(), - } - return setmetatable(self, mt) - end -end - -function M.editbox(parent) - local editbox = CreateFrame('EditBox', nil, parent) - editbox:SetAutoFocus(false) - editbox:SetTextInsets(1.5, 1.5, 3, 3) - editbox:SetMaxLetters(nil) - editbox:SetHeight(24) - editbox:SetTextColor(0, 0, 0, 0) - set_content_style(editbox) - editbox:SetScript('OnEscapePressed', function() - this:ClearFocus() - do (this.escape or pass)() end - end) - editbox:SetScript('OnEnterPressed', function() (this.enter or pass)() end) - editbox:SetScript('OnEditFocusGained', function() - if this.block_focus then - this.block_focus = false - this:ClearFocus() - return - end - this.overlay:Hide() - this:SetTextColor(aux.color.text.enabled()) - this.focused = true - this:HighlightText() - do (this.focus_gain or pass)() end - end) - editbox:SetScript('OnEditFocusLost', function() - this.overlay:Show() - this:SetTextColor(0, 0, 0, 0) - this.focused = false - this:HighlightText(0, 0) - this:SetScript('OnUpdate', nil) - do (this.focus_loss or pass)() end - end) - editbox:SetScript('OnTextChanged', function() - this.overlay:SetText(this.formatter and this.formatter(this:GetText()) or this:GetText()) - do (this.change or pass)() end - end) - editbox:SetScript('OnChar', function() (this.char or pass)() end) - do - local last_click = T.map('t', 0) - editbox:SetScript('OnMouseDown', function() - if arg1 == 'RightButton' then - this:SetText('') - this:ClearFocus() - this.block_focus = true - else - local x, y = GetCursorPosition() - -- local offset = x - editbox:GetLeft()*editbox:GetEffectiveScale() TODO use a fontstring to measure getstringwidth for structural highlighting - -- or use an overlay with itemlinks - if GetTime() - last_click.t < .5 and x == last_click.x and y == last_click.y then - aux.thread(function() editbox:HighlightText() end) - end - T.wipe(last_click) - last_click.t = GetTime() - last_click.x = x - last_click.y = y - end - end) - end - function editbox:SetAlignment(alignment) - self:SetJustifyH(alignment) - self.overlay:SetJustifyH(alignment) - end - function editbox:SetFontSize(size) - self:SetFont(font, size) - self.overlay:SetFont(font, size) - end - local overlay = label(editbox) - overlay:SetPoint('LEFT', 1.5, 0) - overlay:SetPoint('RIGHT', -1.5, 0) - overlay:SetTextColor(aux.color.text.enabled()) - editbox.overlay = overlay - editbox:SetAlignment('LEFT') - editbox:SetFontSize(font_size.medium) - return editbox -end - -do - local function update_bar() - if this:GetValue() < 1 then - this:SetAlpha(1 - (sin(GetTime() * 180) + 1) / 4) - else - this:SetAlpha(1) - end - end - function M.status_bar(parent) - local self = CreateFrame('Frame', nil, parent) - local level = parent:GetFrameLevel() - self:SetFrameLevel(level + 1) - do - local status_bar = CreateFrame('StatusBar', nil, self, 'TextStatusBar') - status_bar:SetOrientation('HORIZONTAL') - status_bar:SetMinMaxValues(0, 1) - status_bar:SetAllPoints() - status_bar:SetStatusBarTexture([[Interface\Buttons\WHITE8X8]]) - status_bar:SetStatusBarColor(.42, .42, .42, .7) - status_bar:SetFrameLevel(level + 2) - status_bar:SetScript('OnUpdate', update_bar) - self.secondary_status_bar = status_bar - end - do - local status_bar = CreateFrame('StatusBar', nil, self, 'TextStatusBar') - status_bar:SetOrientation('HORIZONTAL') - status_bar:SetMinMaxValues(0, 1) - status_bar:SetAllPoints() - status_bar:SetStatusBarTexture([[Interface\Buttons\WHITE8X8]]) - status_bar:SetStatusBarColor(.19, .22, .33, .9) - status_bar:SetFrameLevel(level + 3) - status_bar:SetScript('OnUpdate', update_bar) - self.primary_status_bar = status_bar - end - do - local text_frame = CreateFrame('Frame', nil, self) - text_frame:SetFrameLevel(level + 4) - text_frame:SetAllPoints(self) - local text = label(text_frame, font_size.medium) - text:SetTextColor(aux.color.text.enabled()) - text:SetPoint('CENTER', 0, 0) - self.text = text - end - function self:update_status(primary_status, secondary_status) - if primary_status then - self.primary_status_bar:SetValue(primary_status) - end - if secondary_status then - self.secondary_status_bar:SetValue(secondary_status) - end - end - function self:set_text(text) - self.text:SetText(text) - end - return self - end -end - -function M.item(parent) - local item = CreateFrame('Frame', nil, parent) - set_size(item, 260, 40) - local btn = CreateFrame('CheckButton', unique_name(), item, 'ActionButtonTemplate') - item.button = btn - btn:SetPoint('LEFT', 2, .5) - btn:SetHighlightTexture(nil) - btn:RegisterForClicks() - item.texture = _G[btn:GetName() .. 'Icon'] - item.texture:SetTexCoord(.06, .94, .06, .94) - item.name = label(btn, font_size.medium) - item.name:SetJustifyH('LEFT') - item.name:SetPoint('LEFT', btn, 'RIGHT', 10, 0) - item.name:SetPoint('RIGHT', item, 'RIGHT', -10, .5) - item.count = _G[btn:GetName() .. 'Count'] - item.count:SetTextHeight(17) - return item -end - -function M.label(parent, size) - local label = parent:CreateFontString() - label:SetFont(font, size or font_size.small) - label:SetTextColor(aux.color.label.enabled()) - return label -end - -function M.horizontal_line(parent, y_offset, inverted_color) - local texture = parent:CreateTexture() - texture:SetPoint('TOPLEFT', parent, 'TOPLEFT', 2, y_offset) - texture:SetPoint('TOPRIGHT', parent, 'TOPRIGHT', -2, y_offset) - texture:SetHeight(2) - if inverted_color then - texture:SetTexture(aux.color.panel.background()) - else - texture:SetTexture(aux.color.content.background()) - end - return texture -end - -function M.vertical_line(parent, x_offset, top_offset, bottom_offset, inverted_color) - local texture = parent:CreateTexture() - texture:SetPoint('TOPLEFT', parent, 'TOPLEFT', x_offset, top_offset or -2) - texture:SetPoint('BOTTOMLEFT', parent, 'BOTTOMLEFT', x_offset, bottom_offset or 2) - texture:SetWidth(2) - if inverted_color then - texture:SetTexture(aux.color.panel.background()) - else - texture:SetTexture(aux.color.content.background()) - end - return texture -end - -function M.dropdown(parent) - local dropdown = CreateFrame('Frame', unique_name(), parent, 'UIDropDownMenuTemplate') - set_content_style(dropdown, 0, 0, 4, 4) - - _G[dropdown:GetName() .. 'Left']:Hide() - _G[dropdown:GetName() .. 'Middle']:Hide() - _G[dropdown:GetName() .. 'Right']:Hide() - - local button = _G[dropdown:GetName() .. 'Button'] - button:ClearAllPoints() - button:SetScale(.9) - button:SetPoint('RIGHT', dropdown, 0, 0) - dropdown.button = button - - local text = _G[dropdown:GetName() .. 'Text'] - text:ClearAllPoints() - text:SetPoint('RIGHT', button, 'LEFT', -2, 0) - text:SetPoint('LEFT', 8, 0) - text:SetFont(font, font_size.medium) - text:SetShadowColor(0, 0, 0, 0) - - return dropdown -end - -function M.slider(parent) - - local slider = CreateFrame('Slider', nil, parent) - slider:SetOrientation('HORIZONTAL') - slider:SetHeight(6) - slider:SetHitRectInsets(0, 0, -12, -12) - slider:SetValue(0) - - set_panel_style(slider) - local thumb_texture = slider:CreateTexture(nil, 'ARTWORK') - thumb_texture:SetPoint('CENTER', 0, 0) - thumb_texture:SetTexture(aux.color.content.background()) - thumb_texture:SetHeight(18) - thumb_texture:SetWidth(8) - set_size(thumb_texture, 8, 18) - slider:SetThumbTexture(thumb_texture) - - local label = slider:CreateFontString(nil, 'OVERLAY') - label:SetPoint('BOTTOMLEFT', slider, 'TOPLEFT', -3, 8) - label:SetPoint('BOTTOMRIGHT', slider, 'TOPRIGHT', 6, 8) - label:SetJustifyH('LEFT') - label:SetHeight(13) - label:SetFont(font, font_size.small) - label:SetTextColor(aux.color.label.enabled()) - - local editbox = editbox(slider) - editbox:SetPoint('LEFT', slider, 'RIGHT', 5, 0) - set_size(editbox, 45, 18) - editbox:SetAlignment('CENTER') - editbox:SetFontSize(17) - - slider.label = label - slider.editbox = editbox - return slider -end - -function M.checkbox(parent) - local checkbox = CreateFrame('CheckButton', nil, parent, 'UICheckButtonTemplate') - checkbox:SetWidth(16) - checkbox:SetHeight(16) - set_content_style(checkbox) - checkbox:SetNormalTexture(nil) - checkbox:SetPushedTexture(nil) - checkbox:GetHighlightTexture():SetAllPoints() - checkbox:GetHighlightTexture():SetTexture(1, 1, 1, .2) - checkbox:GetCheckedTexture():SetTexCoord(.12, .88, .12, .88) - checkbox:GetHighlightTexture('BLEND') - return checkbox -end - -do - local editbox = CreateFrame'EditBox' - editbox:SetAutoFocus(false) - function M.clear_focus() - editbox:SetFocus() - editbox:ClearFocus() - end -end - -function M.percentage_historical(pct, bid) - local text = (pct > 10000 and '>10000' or pct) .. '%' - if bid then - return aux.color.gray(text) - elseif pct < 50 then - return aux.color.blue(text) - elseif pct < 80 then - return aux.color.green(text) - elseif pct < 110 then - return aux.color.yellow(text) - elseif pct < 135 then - return aux.color.orange(text) - else - return aux.color.red(text) - end -end \ No newline at end of file diff --git a/aux-addon/gui/item_listing.lua b/aux-addon/gui/item_listing.lua deleted file mode 100644 index 4b22728..0000000 --- a/aux-addon/gui/item_listing.lua +++ /dev/null @@ -1,129 +0,0 @@ -module 'aux.gui.item_listing' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local gui = require 'aux.gui' - -local ROW_HEIGHT = 39 - -function M:render() - - if getn(self.item_records or T.empty) > getn(self.rows) then - self.content_frame:SetPoint('BOTTOMRIGHT', -15, 0) - else - self.content_frame:SetPoint('BOTTOMRIGHT', 0, 0) - end - - FauxScrollFrame_Update(self.scroll_frame, getn(self.item_records), getn(self.rows), ROW_HEIGHT) - local offset = FauxScrollFrame_GetOffset(self.scroll_frame) - - local rows = self.rows - - for i, row in rows do - local item_record = self.item_records[i + offset] - - if item_record then - row.item_record = item_record - if self.selected and self.selected(item_record) or row.mouseover then - row.highlight:Show() - elseif not row.mouse_over then - row.highlight:Hide() - end - row.item.texture:SetTexture(item_record.texture) - row.item.name:SetText('[' .. item_record.name .. ']') - local color = ITEM_QUALITY_COLORS[item_record.quality] - row.item.name:SetTextColor(color.r, color.g, color.b) - if item_record.aux_quantity > 1 then - row.item.count:SetText(item_record.aux_quantity) - else - row.item.count:SetText() - end - row:Show() - else - row:Hide() - end - end -end - -function M.new(parent, on_click, selected) - local content_frame = CreateFrame('Frame', nil, parent) - content_frame:SetAllPoints() - - local scroll_frame = CreateFrame('ScrollFrame', gui.unique_name(), parent, 'FauxScrollFrameTemplate') - scroll_frame:SetScript('OnVerticalScroll', function() - FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, function() render(this.item_listing) end) - end) - scroll_frame:SetPoint('TOPLEFT', content_frame, 'TOPLEFT', 0, 29) - scroll_frame:SetPoint('BOTTOMRIGHT', content_frame, 'BOTTOMRIGHT', 0, 0) - - local scroll_bar = _G[scroll_frame:GetName() .. 'ScrollBar'] - scroll_bar:ClearAllPoints() - scroll_bar:SetPoint('TOPRIGHT', parent, -4, 2) - scroll_bar:SetPoint('BOTTOMRIGHT', parent, -4, 4) - scroll_bar:SetWidth(10) - local thumbTex = scroll_bar:GetThumbTexture() - thumbTex:SetPoint('CENTER', 0, 0) - thumbTex:SetTexture(aux.color.content.background()) - thumbTex:SetHeight(150) - thumbTex:SetWidth(scroll_bar:GetWidth()) - _G[scroll_bar:GetName() .. 'ScrollUpButton']:Hide() - _G[scroll_bar:GetName() .. 'ScrollDownButton']:Hide() - - local rows = T.acquire() - local row_index = 1 - local max_height = content_frame:GetHeight() - local total_height = 0 - while total_height + ROW_HEIGHT < max_height do - local row = CreateFrame('Frame', nil, content_frame) - row:SetHeight(ROW_HEIGHT) - row:SetPoint('TOPLEFT', content_frame, 0, -((row_index - 1) * ROW_HEIGHT)) - row:SetPoint('TOPRIGHT', content_frame, 0, -((row_index - 1) * ROW_HEIGHT)) - row:EnableMouse(true) - row:SetScript('OnMouseUp', on_click) - row:SetScript('OnEnter', function() - row.mouseover = true - row.highlight:Show() - end) - row:SetScript('OnLeave', function() - row.mouseover = false - if not selected(row.item_record) then - row.highlight:Hide() - end - end) - - row.item = gui.item(row) - row.item:SetScale(.9) - row.item:SetPoint('LEFT', 2.5, 0) - row.item:SetPoint('RIGHT', -2.5, 0) - row.item.button:SetScript('OnEnter', function() - info.set_tooltip(row.item_record.itemstring, this, 'ANCHOR_RIGHT') - end) - row.item.button:SetScript('OnLeave', function() GameTooltip:Hide() end) - - local highlight = row:CreateTexture() - highlight:SetAllPoints(row) - highlight:Hide() - highlight:SetTexture(1, .9, 0, .4) - row.highlight = highlight - - rows[row_index] = row - row_index = row_index + 1 - total_height = total_height + ROW_HEIGHT - end - - local item_listing = { - selected = selected, - content_frame = content_frame, - scroll_frame = scroll_frame, - rows = rows, - } - scroll_frame.item_listing = item_listing - - return item_listing -end - -function M.populate(item_listing, item_records) - item_listing.item_records = item_records - render(item_listing) -end diff --git a/aux-addon/gui/listing.lua b/aux-addon/gui/listing.lua deleted file mode 100644 index 544b234..0000000 --- a/aux-addon/gui/listing.lua +++ /dev/null @@ -1,300 +0,0 @@ -module 'aux.gui.listing' - -local T = require 'T' -local aux = require 'aux' -local gui = require 'aux.gui' - -local ROW_HEIGHT = 15 -local ROW_TEXT_SIZE = 14 -local HEAD_HEIGHT = 27 -local HEAD_SPACE = 2 -local DEFAULT_COL_INFO = {{width=1}} - -local handlers = { - OnEnter = function() - this.mouseover = true - if not this.data then return end - if not this.st.highlightDisabled then - this.highlight:Show() - end - - local handler = this.st.handlers.OnEnter - if handler then - handler(this.st, this.data, this) - end - end, - - OnLeave = function() - this.mouseover = false - if not this.data then return end - if not (this.st.selected and this.st.selected(this.data)) then - this.highlight:Hide() - end - - local handler = this.st.handlers.OnLeave - if handler then - handler(this.st, this.data, this) - end - end, - - OnClick = function() - if not this.data then return end - local handler = this.st.handlers.OnClick - if handler then - handler(this.st, this.data, this, arg1) - end - end, - - OnDoubleClick = function() - if not this.data then return end - - local handler = this.st.handlers.OnDoubleClick - if handler then - handler(this.st, this.data, this, arg1) - end - end, -} - -local methods = { - Update = function(self) - if getn(self.colInfo) > 1 or self.colInfo[1].name then - self.headHeight = HEAD_HEIGHT - else - self.headHeight = 0 - end - - if getn(self.rowData or T.empty) > self.numRows then - self.contentFrame:SetPoint('BOTTOMRIGHT', -15, 0) - else - self.contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) - end - - local width = self.contentFrame:GetRight() - self.contentFrame:GetLeft() - - while getn(self.headCols) < getn(self.colInfo) do - self:AddColumn() - end - - for i, col in self.headCols do - if self.colInfo[i] then - col:Show() - col:SetWidth(self.colInfo[i].width * width) - col:SetHeight(self.headHeight) - col.text:SetText(self.colInfo[i].name or '') - col.text:SetJustifyH(self.colInfo[i].headAlign or 'CENTER') - else - col:Hide() - end - end - - while getn(self.rows) < self.numRows do - self:AddRow() - end - - for i, row in self.rows do - if i > self.numRows then - row.data = nil - row:Hide() - else - row:Show() - while getn(row.cols) < getn(self.colInfo) do - self:AddCell(i) - end - for j, col in row.cols do - if self.headCols[j] and self.colInfo[j] then - col:Show() - col:SetWidth(self.colInfo[j].width * width) - col.text:SetJustifyH(self.colInfo[j].align or 'LEFT') - else - col:Hide() - end - end - end - end - - if not self.rowData then return end - FauxScrollFrame_Update(self.scrollFrame, getn(self.rowData), self.numRows, ROW_HEIGHT) - local offset = FauxScrollFrame_GetOffset(self.scrollFrame) - self.offset = offset - - for i = 1, self.numRows do - local row = self.rows[i] - row.data = nil - if i > getn(self.rowData) then - row:Hide() - else - row:Show() - local data = self.rowData[i + offset] - if not data then break end - row.data = data - - if row.mouseover or self.selected and self.selected(data) then - row.highlight:Show() - else - row.highlight:Hide() - end - - for j, col in row.cols do - if self.colInfo[j] then - local colData = data.cols[j] - if type(colData.value) == 'function' then - col.text:SetText(colData.value(unpack(colData.args))) - else - col.text:SetText(colData.value) - end - end - end - end - end - end, - - SetData = function(self, rowData) - for _, row in self.rowData or T.empty do - for _, col in row.cols do T.release(col) end - T.release(row.cols) - T.release(row) - end - self.rowData = rowData - self.updateSort = true - self:Update() - end, - - AddColumn = function(self) - local colNum = getn(self.headCols) + 1 - local col = CreateFrame('Frame', nil, self.contentFrame) - if colNum == 1 then - col:SetPoint('TOPLEFT', 0, 0) - else - col:SetPoint('TOPLEFT', self.headCols[colNum - 1], 'TOPRIGHT') - end - col.st = self - col.colNum = colNum - - local text = col:CreateFontString() - text:SetAllPoints() - text:SetFont(gui.font, 12) - text:SetTextColor(aux.color.label.enabled()) - col.text = text - - local tex = col:CreateTexture() - tex:SetAllPoints() - tex:SetTexture([[Interface\AddOns\aux-AddOn\WorldStateFinalScore-Highlight]]) - tex:SetTexCoord(.017, 1, .083, .909) - tex:SetAlpha(.5) - - tinsert(self.headCols, col) - - for i, row in self.rows do - while getn(row.cols) < getn(self.headCols) do - self:AddCell(i) - end - end - end, - - AddCell = function(self, rowNum) - local row = self.rows[rowNum] - local colNum = getn(row.cols) + 1 - local cell = CreateFrame('Frame', nil, row) - local text = cell:CreateFontString() - cell.text = text - text:SetFont(gui.font, ROW_TEXT_SIZE) - text:SetJustifyV('CENTER') - text:SetPoint('TOPLEFT', 1, -1) - text:SetPoint('BOTTOMRIGHT', -1, 1) - cell:SetHeight(ROW_HEIGHT) - cell.st = self - - if colNum == 1 then - cell:SetPoint('TOPLEFT', 0, 0) - else - cell:SetPoint('TOPLEFT', row.cols[colNum - 1], 'TOPRIGHT') - end - tinsert(row.cols, cell) - end, - - AddRow = function(self) - local row = CreateFrame('Button', nil, self.contentFrame) - row:SetHeight(ROW_HEIGHT) - row:RegisterForClicks('LeftButtonUp', 'RightButtonUp') - for name, func in handlers do - row:SetScript(name, func) - end - local rowNum = getn(self.rows) + 1 - if rowNum == 1 then - row:SetPoint('TOPLEFT', 0, -(self.headHeight + HEAD_SPACE)) - row:SetPoint('TOPRIGHT', 0, -(self.headHeight + HEAD_SPACE)) - else - row:SetPoint('TOPLEFT', 0, -(self.headHeight + HEAD_SPACE + (rowNum - 1) * ROW_HEIGHT)) - row:SetPoint('TOPRIGHT', 0, -(self.headHeight + HEAD_SPACE + (rowNum - 1) * ROW_HEIGHT)) - end - local highlight = row:CreateTexture() - highlight:SetAllPoints() - highlight:SetTexture(1, .9, 0, .4) - highlight:Hide() - row.highlight = highlight - row.st = self - - row.cols = T.acquire() - self.rows[rowNum] = row - for _ = 1, getn(self.colInfo) do - self:AddCell(rowNum) - end - end, - - SetSelection = function(self, f) - self.selected = f - end, - - SetHandler = function(self, event, handler) - self.handlers[event] = handler - end, - - SetColInfo = function(self, colInfo) - colInfo = colInfo or DEFAULT_COL_INFO - self.colInfo = colInfo - self:Update() - end, -} - -function M.new(parent) - local st = CreateFrame('Frame', gui.unique_name(), parent) - st:SetAllPoints() - - st.numRows = max(floor((parent:GetHeight() - HEAD_HEIGHT - HEAD_SPACE) / ROW_HEIGHT), 0) - - local contentFrame = CreateFrame('Frame', nil, st) - contentFrame:SetPoint('TOPLEFT', 0, 0) - contentFrame:SetPoint('BOTTOMRIGHT', 0, 0) - st.contentFrame = contentFrame - - local scrollFrame = CreateFrame('ScrollFrame', st:GetName() .. 'ScrollFrame', st, 'FauxScrollFrameTemplate') - scrollFrame:SetScript('OnVerticalScroll', function() - FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, function() st:Update() end) - end) - scrollFrame:SetAllPoints(contentFrame) - st.scrollFrame = scrollFrame - - local scroll_bar = _G[scrollFrame:GetName() .. 'ScrollBar'] - scroll_bar:ClearAllPoints() - scroll_bar:SetPoint('TOPRIGHT', st, -4, -HEAD_HEIGHT) - scroll_bar:SetPoint('BOTTOMRIGHT', st, -4, 4) - scroll_bar:SetWidth(10) - local thumbTex = scroll_bar:GetThumbTexture() - thumbTex:SetPoint('CENTER', 0, 0) - thumbTex:SetTexture(aux.color.content.background()) - thumbTex:SetHeight(150) - thumbTex:SetWidth(scroll_bar:GetWidth()) - _G[scroll_bar:GetName() .. 'ScrollUpButton']:Hide() - _G[scroll_bar:GetName() .. 'ScrollDownButton']:Hide() - - for name, func in methods do - st[name] = func - end - - st.headCols = T.acquire() - st.rows = T.acquire() - st.handlers = T.acquire() - st.colInfo = DEFAULT_COL_INFO - - return st -end \ No newline at end of file diff --git a/aux-addon/libs/T.lua b/aux-addon/libs/T.lua deleted file mode 100644 index 738b613..0000000 --- a/aux-addon/libs/T.lua +++ /dev/null @@ -1,149 +0,0 @@ -if module 'T' then return end - -local next, getn, setn, tremove, setmetatable = next, getn, table.setn, tremove, setmetatable - -local wipe, acquire, release -local pool, pool_size, overflow_pool, auto_release = {}, 0, setmetatable({}, {__mode='k'}), {} - -function wipe(t) - setmetatable(t, nil) - for k in t do - t[k] = nil - end - t.reset, t.reset = nil, 1 - setn(t, 0) -end -M.wipe = wipe - -CreateFrame'Frame':SetScript('OnUpdate', function() - for t in auto_release do - release(t) - end - wipe(auto_release) -end) - -function acquire() - if pool_size > 0 then - pool_size = pool_size - 1 - return pool[pool_size + 1] - end - local t = next(overflow_pool) - if t then - overflow_pool[t] = nil - return t - end - return {} -end -M.acquire = acquire - -function release(t) - wipe(t) - auto_release[t] = nil - if pool_size < 50 then - pool_size = pool_size + 1 - pool[pool_size] = t - else - overflow_pool[t] = true - end -end -M.release = release - -do - local function f(_, v) - if v then - auto_release[v] = true - return v - end - end - M.temp = setmetatable({}, {__metatable=false, __newindex=pass, __call=f, __sub=f}) -end -do - local function f(_, v) - if v then - auto_release[v] = nil - return v - end - end - M.static = setmetatable({}, {__metatable=false, __newindex=pass, __call=f, __sub=f}) -end - -do - local function unpack(t) - if getn(t) > 0 then - return tremove(t, 1), unpack(t) - else - release(t) - end - end - M.unpack = unpack -end - -M.empty = setmetatable({}, {__metatable=false, __newindex=pass}) - -local vararg -do - local MAXPARAMS = 100 - - local code = [[ - local f, setn, acquire, auto_release = f, setn, acquire, auto_release - return function( - ]] - for i = 1, MAXPARAMS - 1 do - code = code .. format('a%d,', i) - end - code = code .. [[ - overflow) - if overflow ~= nil then error("T-vararg overflow.", 2) end - local n = 0 - repeat - ]] - for i = MAXPARAMS - 1, 1, -1 do - code = code .. format('if a%1$d ~= nil then n = %1$d; break end;', i) - end - code = code .. [[ - until true - local t = acquire() - auto_release[t] = true - setn(t, n) - repeat - ]] - for i = 1, MAXPARAMS - 1 do - code = code .. format('if %1$d > n then break end; t[%1$d] = a%1$d;', i) - end - code = code .. [[ - until true - return f(t) - end - ]] - - function vararg(f) - local chunk = loadstring(code) - setfenv(chunk, {f=f, setn=setn, acquire=acquire, auto_release=auto_release}) - return chunk() - end - M.vararg = setmetatable({}, { - __metatable = false, - __sub = function(_, v) - return vararg(v) - end, - }) -end - -M.list = vararg(function(arg) - auto_release[arg] = nil - return arg -end) -M.set = vararg(function(arg) - local t = acquire() - for _, v in arg do - t[v] = true - end - return t -end) -M.map = vararg(function(arg) - local t = acquire() - for i = 1, getn(arg), 2 do - t[arg[i]] = arg[i + 1] - end - return t -end) \ No newline at end of file diff --git a/aux-addon/libs/package.lua b/aux-addon/libs/package.lua deleted file mode 100644 index 1339a82..0000000 --- a/aux-addon/libs/package.lua +++ /dev/null @@ -1,37 +0,0 @@ -if module then return end -local _G, setfenv, setmetatable = getfenv(0), setfenv, setmetatable -local environments, interfaces = {}, {} - -local function pass() end - -local environment_mt = {__index=_G} - -local function create_module(name) - local environment = setmetatable({_G=_G, pass=pass}, environment_mt) - local exports = {} - environment.M = setmetatable({}, { - __metatable=false, - __newindex=function(_, k, v) - environment[k], exports[k] = v, v - end, - }) - environment._M = environment - environments[name] = environment - interfaces[name] = setmetatable({}, {__metatable=false, __index=exports, __newindex=pass}) -end - -function module(name) - local defined = not not environments[name] - if not defined then - create_module(name) - end - setfenv(2, environments[name]) - return defined -end - -function require(name) - if not interfaces[name] then - create_module(name) - end - return interfaces[name] -end \ No newline at end of file diff --git a/aux-addon/localization.lua b/aux-addon/localization.lua deleted file mode 100644 index 37e3b3a..0000000 --- a/aux-addon/localization.lua +++ /dev/null @@ -1,7 +0,0 @@ -BINDING_HEADER_AUX = "aux"; - -local post = require 'aux.tabs.post' - -function post_auctions_bind() - post.post_auctions_bind() -end \ No newline at end of file diff --git a/aux-addon/tabs/auctions/core.lua b/aux-addon/tabs/auctions/core.lua deleted file mode 100644 index ec54346..0000000 --- a/aux-addon/tabs/auctions/core.lua +++ /dev/null @@ -1,101 +0,0 @@ -module 'aux.tabs.auctions' - -local T = require 'T' -local aux = require 'aux' -local scan_util = require 'aux.util.scan' -local scan = require 'aux.core.scan' - -local tab = aux.tab 'Auctions' - -auction_records = T.acquire() - -function tab.OPEN() - frame:Show() - scan_auctions() -end - -function tab.CLOSE() - frame:Hide() -end - -function update_listing() - listing:SetDatabase(auction_records) -end - -function M.scan_auctions() - - status_bar:update_status(0, 0) - status_bar:set_text('Scanning auctions...') - - T.wipe(auction_records) - update_listing() - scan.start{ - type = 'owner', - queries = {{blizzard_query = T.acquire()}}, - on_page_loaded = function(page, total_pages) - status_bar:update_status(page / total_pages, 0) - status_bar:set_text(format('Scanning (Page %d / %d)', page, total_pages)) - end, - on_auction = function(auction_record) - tinsert(auction_records, auction_record) - end, - on_complete = function() - status_bar:update_status(1, 1) - status_bar:set_text('Scan complete') - update_listing() - end, - on_abort = function() - status_bar:update_status(1, 1) - status_bar:set_text('Scan aborted') - end, - } -end - -do - local scan_id = 0 - local IDLE, SEARCHING, FOUND = aux.enum(3) - local state = IDLE - local found_index - - function find_auction(record) - if not listing:ContainsRecord(record) then return end - - scan.abort(scan_id) - state = SEARCHING - scan_id = scan_util.find( - record, - status_bar, - function() state = IDLE end, - function() state = IDLE; listing:RemoveAuctionRecord(record) end, - function(index) - state = FOUND - found_index = index - - cancel_button:SetScript('OnClick', function() - if scan_util.test(record, index) and listing:ContainsRecord(record) then - aux.cancel_auction(index, function() listing:RemoveAuctionRecord(record) end) - end - end) - cancel_button:Enable() - end - ) - end - - function on_update() - if state == IDLE or state == SEARCHING then - cancel_button:Disable() - end - - if state == SEARCHING then return end - - local selection = listing:GetSelection() - if not selection then - state = IDLE - elseif selection and state == IDLE then - find_auction(selection.record) - elseif state == FOUND and not scan_util.test(selection.record, found_index) then - cancel_button:Disable() - if not aux.cancel_in_progress() then state = IDLE end - end - end -end \ No newline at end of file diff --git a/aux-addon/tabs/auctions/frame.lua b/aux-addon/tabs/auctions/frame.lua deleted file mode 100644 index 689663e..0000000 --- a/aux-addon/tabs/auctions/frame.lua +++ /dev/null @@ -1,62 +0,0 @@ -module 'aux.tabs.auctions' - -local aux = require 'aux' -local info = require 'aux.util.info' -local gui = require 'aux.gui' -local auction_listing = require 'aux.gui.auction_listing' -local search_tab = require 'aux.tabs.search' - -frame = CreateFrame('Frame', nil, aux.frame) -frame:SetAllPoints() -frame:SetScript('OnUpdate', on_update) -frame:Hide() - -frame.listing = gui.panel(frame) -frame.listing:SetPoint('TOP', frame, 'TOP', 0, -8) -frame.listing:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) -frame.listing:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) - -listing = auction_listing.new(frame.listing, 20, auction_listing.auctions_columns) -listing:SetSort(1, 2, 3, 4, 5, 6, 7, 8) -listing:Reset() -listing:SetHandler('OnClick', function(row, button) - if IsAltKeyDown() then - if listing:GetSelection().record == row.record then - if IsAltKeyDown() and listing:GetSelection().record == row.record then - cancel_button:Click() - end - end - elseif button == 'RightButton' then - aux.set_tab(1) - search_tab.set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') - search_tab.execute(nil, false) - end -end) -listing:SetHandler('OnSelectionChanged', function(rt, datum) - if not datum then return end - find_auction(datum.record) -end) - -do - status_bar = gui.status_bar(frame) - status_bar:SetWidth(265) - status_bar:SetHeight(25) - status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) - status_bar:update_status(1, 1) - status_bar:set_text('') -end -do - local btn = gui.button(frame) - btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) - btn:SetText('Cancel') - btn:Disable() - cancel_button = btn -end -do - local btn = gui.button(frame) - btn:SetPoint('TOPLEFT', cancel_button, 'TOPRIGHT', 5, 0) - btn:SetText('Refresh') - btn:SetScript('OnClick', function() - scan_auctions() - end) -end \ No newline at end of file diff --git a/aux-addon/tabs/bids/core.lua b/aux-addon/tabs/bids/core.lua deleted file mode 100644 index b52cc1b..0000000 --- a/aux-addon/tabs/bids/core.lua +++ /dev/null @@ -1,125 +0,0 @@ -module 'aux.tabs.bids' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local scan_util = require 'aux.util.scan' -local scan = require 'aux.core.scan' - -local tab = aux.tab 'Bids' - -auction_records = {} - -function tab.OPEN() - frame:Show() - scan_bids() -end - -function tab.CLOSE() - frame:Hide() -end - -function update_listing() - listing:SetDatabase(auction_records) -end - -function M.scan_bids() - - status_bar:update_status(0, 0) - status_bar:set_text('Scanning auctions...') - - T.wipe(auction_records) - update_listing() - scan.start{ - type = 'bidder', - queries = T.list(T.map('blizzard_query', T.acquire())), - on_page_loaded = function(page, total_pages) - status_bar:update_status(page / total_pages, 0) - status_bar:set_text(format('Scanning (Page %d / %d)', page, total_pages)) - end, - on_auction = function(auction_record) - tinsert(auction_records, auction_record) - end, - on_complete = function() - status_bar:update_status(1, 1) - status_bar:set_text('Scan complete') - update_listing() - end, - on_abort = function() - status_bar:update_status(1, 1) - status_bar:set_text('Scan aborted') - end, - } -end - -do - local scan_id = 0 - local IDLE, SEARCHING, FOUND = aux.enum(3) - local state = IDLE - local found_index - - function find_auction(record) - if not listing:ContainsRecord(record) then return end - - scan.abort(scan_id) - state = SEARCHING - scan_id = scan_util.find( - record, - status_bar, - function() state = IDLE end, - function() - state = IDLE - listing:RemoveAuctionRecord(record) - end, - function(index) - state = FOUND - found_index = index - - if not record.high_bidder then - bid_button:SetScript('OnClick', function() - if scan_util.test(record, index) and listing:ContainsRecord(record) then - aux.place_bid('bidder', index, record.bid_price, record.bid_price < record.buyout_price and function() - info.bid_update(record) - listing:SetDatabase() - end or function() listing:RemoveAuctionRecord(record) end) - end - end) - bid_button:Enable() - else - bid_button:Disable() - end - - if record.buyout_price > 0 then - buyout_button:SetScript('OnClick', function() - if scan_util.test(record, index) and listing:ContainsRecord(record) then - aux.place_bid('bidder', index, record.buyout_price, function() listing:RemoveAuctionRecord(record) end) - end - end) - buyout_button:Enable() - else - buyout_button:Disable() - end - end - ) - end - - function on_update() - if state == IDLE or state == SEARCHING then - buyout_button:Disable() - bid_button:Disable() - end - - if state == SEARCHING then return end - - local selection = listing:GetSelection() - if not selection then - state = IDLE - elseif selection and state == IDLE then - find_auction(selection.record) - elseif state == FOUND and not scan_util.test(selection.record, found_index) then - buyout_button:Disable() - bid_button:Disable() - if not aux.bid_in_progress() then state = IDLE end - end - end -end \ No newline at end of file diff --git a/aux-addon/tabs/bids/frame.lua b/aux-addon/tabs/bids/frame.lua deleted file mode 100644 index c7f3b47..0000000 --- a/aux-addon/tabs/bids/frame.lua +++ /dev/null @@ -1,71 +0,0 @@ -module 'aux.tabs.bids' - -local aux = require 'aux' -local info = require 'aux.util.info' -local gui = require 'aux.gui' -local auction_listing = require 'aux.gui.auction_listing' -local search_tab = require 'aux.tabs.search' - -frame = CreateFrame('Frame', nil, aux.frame) -frame:SetAllPoints() -frame:SetScript('OnUpdate', on_update) -frame:Hide() - -frame.listing = gui.panel(frame) -frame.listing:SetPoint('TOP', frame, 'TOP', 0, -8) -frame.listing:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) -frame.listing:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) - -listing = auction_listing.new(frame.listing, 20, auction_listing.bids_columns) -listing:SetSort(1, 2, 3, 4, 5, 6, 7, 8) -listing:Reset() -listing:SetHandler('OnClick', function(row, button) - if IsAltKeyDown() then - if listing:GetSelection().record == row.record then - if button == 'LeftButton' then - buyout_button:Click() - elseif button == 'RightButton' then - bid_button:Click() - end - end - elseif button == 'RightButton' then - aux.set_tab(1) - search_tab.set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') - search_tab.execute(nil, false) - end -end) -listing:SetHandler('OnSelectionChanged', function(rt, datum) - if not datum then return end - find_auction(datum.record) -end) - -do - status_bar = gui.status_bar(frame) - status_bar:SetWidth(265) - status_bar:SetHeight(25) - status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) - status_bar:update_status(1, 0) - status_bar:set_text('') -end -do - local btn = gui.button(frame) - btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) - btn:SetText('Bid') - btn:Disable() - bid_button = btn -end -do - local btn = gui.button(frame) - btn:SetPoint('TOPLEFT', bid_button, 'TOPRIGHT', 5, 0) - btn:SetText('Buyout') - btn:Disable() - buyout_button = btn -end -do - local btn = gui.button(frame) - btn:SetPoint('TOPLEFT', buyout_button, 'TOPRIGHT', 5, 0) - btn:SetText('Refresh') - btn:SetScript('OnClick', function() - scan_bids() - end) -end \ No newline at end of file diff --git a/aux-addon/tabs/post/core.lua b/aux-addon/tabs/post/core.lua deleted file mode 100644 index 08a3581..0000000 --- a/aux-addon/tabs/post/core.lua +++ /dev/null @@ -1,617 +0,0 @@ -module 'aux.tabs.post' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local sort_util = require 'aux.util.sort' -local persistence = require 'aux.util.persistence' -local money = require 'aux.util.money' -local scan_util = require 'aux.util.scan' -local post = require 'aux.core.post' -local scan = require 'aux.core.scan' -local history = require 'aux.core.history' -local item_listing = require 'aux.gui.item_listing' -local al = require 'aux.gui.auction_listing' -local gui = require 'aux.gui' - -local tab = aux.tab 'Post' - -local settings_schema = {'tuple', '#', {duration='number'}, {start_price='number'}, {buyout_price='number'}, {hidden='boolean'}} - -local scan_id, inventory_records, bid_records, buyout_records = 0, {}, {}, {} - -M.DURATION_2, M.DURATION_8, M.DURATION_24 = 120, 480, 1440 - -refresh = true - -selected_item = nil - -function get_default_settings() - return T.map('duration', aux.account_data.post_duration, 'start_price', 0, 'buyout_price', 0, 'hidden', false) -end - -function aux.handle.LOAD2() - data = aux.faction_data.post -end - -function read_settings(item_key) - item_key = item_key or selected_item.key - return data[item_key] and persistence.read(settings_schema, data[item_key]) or get_default_settings() -end -function write_settings(settings, item_key) - item_key = item_key or selected_item.key - data[item_key] = persistence.write(settings_schema, settings) -end - -do - local bid_selections, buyout_selections = {}, {} - function get_bid_selection() - return bid_selections[selected_item.key] - end - function set_bid_selection(record) - bid_selections[selected_item.key] = record - end - function get_buyout_selection() - return buyout_selections[selected_item.key] - end - function set_buyout_selection(record) - buyout_selections[selected_item.key] = record - end -end - -function refresh_button_click() - scan.abort(scan_id) - refresh_entries() - refresh = true -end - -function tab.OPEN() - frame:Show() - update_inventory_records() - refresh = true -end - -function tab.CLOSE() - selected_item = nil - frame:Hide() -end - -function tab.USE_ITEM(item_info) - select_item(item_info.item_key) -end - -function get_unit_start_price() - return selected_item and read_settings().start_price or 0 -end - -function set_unit_start_price(amount) - local settings = read_settings() - settings.start_price = amount - write_settings(settings) -end - -function get_unit_buyout_price() - return selected_item and read_settings().buyout_price or 0 -end - -function set_unit_buyout_price(amount) - local settings = read_settings() - settings.buyout_price = amount - write_settings(settings) -end - -function update_inventory_listing() - local records = aux.values(aux.filter(aux.copy(inventory_records), function(record) - local settings = read_settings(record.key) - return record.aux_quantity > 0 and (not settings.hidden or show_hidden_checkbox:GetChecked()) - end)) - sort(records, function(a, b) return a.name < b.name end) - item_listing.populate(inventory_listing, records) -end - -function update_auction_listing(listing, records, reference) - local rows = T.acquire() - if selected_item then - local historical_value = history.value(selected_item.key) - local stack_size = stack_size_slider:GetValue() - for _, record in records[selected_item.key] or T.empty do - local price_color = undercut(record, stack_size_slider:GetValue(), listing == 'bid') < reference and aux.color.red - local price = record.unit_price * (listing == 'bid' and record.stack_size / stack_size_slider:GetValue() or 1) - tinsert(rows, T.map( - 'cols', T.list( - T.map('value', record.own and aux.color.green(record.count) or record.count), - T.map('value', al.time_left(record.duration)), - T.map('value', record.stack_size == stack_size and aux.color.green(record.stack_size) or record.stack_size), - T.map('value', money.to_string(price, true, nil, price_color)), - T.map('value', historical_value and gui.percentage_historical(aux.round(price / historical_value * 100)) or '---') - ), - 'record', record - )) - end - if historical_value then - tinsert(rows, T.map( - 'cols', T.list( - T.map('value', '---'), - T.map('value', '---'), - T.map('value', '---'), - T.map('value', money.to_string(historical_value, true, nil, aux.color.green)), - T.map('value', historical_value and gui.percentage_historical(100) or '---') - ), - 'record', T.map('historical_value', true, 'stack_size', stack_size, 'unit_price', historical_value, 'own', true) - )) - end - sort(rows, function(a, b) - return sort_util.multi_lt( - a.record.unit_price * (listing == 'bid' and a.record.stack_size or 1), - b.record.unit_price * (listing == 'bid' and b.record.stack_size or 1), - - a.record.historical_value and 1 or 0, - b.record.historical_value and 1 or 0, - - b.record.own and 0 or 1, - a.record.own and 0 or 1, - - a.record.stack_size, - b.record.stack_size, - - a.record.duration, - b.record.duration - ) - end) - end - if listing == 'bid' then - bid_listing:SetData(rows) - elseif listing == 'buyout' then - buyout_listing:SetData(rows) - end -end - -function update_auction_listings() - update_auction_listing('bid', bid_records, get_unit_start_price()) - update_auction_listing('buyout', buyout_records, get_unit_buyout_price()) -end - -function M.select_item(item_key) - for _, inventory_record in aux.filter(aux.copy(inventory_records), function(record) return record.aux_quantity > 0 end) do - if inventory_record.key == item_key then - update_item(inventory_record) - return - end - end -end - -function price_update() - if selected_item then - local historical_value = history.value(selected_item.key) - if get_bid_selection() or get_buyout_selection() then - set_unit_start_price(undercut(get_bid_selection() or get_buyout_selection(), stack_size_slider:GetValue(), get_bid_selection())) - unit_start_price_input:SetText(money.to_string(get_unit_start_price(), true, nil, nil, true)) - end - if get_buyout_selection() then - set_unit_buyout_price(undercut(get_buyout_selection(), stack_size_slider:GetValue())) - unit_buyout_price_input:SetText(money.to_string(get_unit_buyout_price(), true, nil, nil, true)) - end - start_price_percentage:SetText(historical_value and gui.percentage_historical(aux.round(get_unit_start_price() / historical_value * 100)) or '---') - buyout_price_percentage:SetText(historical_value and gui.percentage_historical(aux.round(get_unit_buyout_price() / historical_value * 100)) or '---') - end -end - -function post_auctions() - if selected_item then - local unit_start_price = get_unit_start_price() - local unit_buyout_price = get_unit_buyout_price() - local stack_size = stack_size_slider:GetValue() - local stack_count - stack_count = stack_count_slider:GetValue() - local duration = UIDropDownMenu_GetSelectedValue(duration_dropdown) - local key = selected_item.key - - local duration_code - if duration == DURATION_2 then - duration_code = 2 - elseif duration == DURATION_8 then - duration_code = 3 - elseif duration == DURATION_24 then - duration_code = 4 - end - - post.start( - key, - stack_size, - duration, - unit_start_price, - unit_buyout_price, - stack_count, - function(posted) - if not frame:IsShown() then - return - end - for i = 1, posted do - record_auction(key, stack_size, unit_start_price * stack_size, unit_buyout_price, duration_code, UnitName'player') - end - update_inventory_records() - local same - for _, record in inventory_records do - if record.key == key then - same = record - break - end - end - if same then - update_item(same) - else - selected_item = nil - end - refresh = true - end - ) - end -end - -function M.post_auctions_bind() - if selected_item then - local unit_start_price = get_unit_start_price() - local unit_buyout_price = get_unit_buyout_price() - local stack_size = stack_size_slider:GetValue() - local stack_count - stack_count = stack_count_slider:GetValue() - local duration = UIDropDownMenu_GetSelectedValue(duration_dropdown) - local key = selected_item.key - - local duration_code - if duration == DURATION_2 then - duration_code = 2 - elseif duration == DURATION_8 then - duration_code = 3 - elseif duration == DURATION_24 then - duration_code = 4 - end - - post.start( - key, - stack_size, - duration, - unit_start_price, - unit_buyout_price, - stack_count, - function(posted) - if not frame:IsShown() then - return - end - for i = 1, posted do - record_auction(key, stack_size, unit_start_price * stack_size, unit_buyout_price, duration_code, UnitName'player') - end - update_inventory_records() - local same - for _, record in inventory_records do - if record.key == key then - same = record - break - end - end - if same then - update_item(same) - else - selected_item = nil - end - refresh = true - end - ) - end -end - -function validate_parameters() - if not selected_item then - post_button:Disable() - return - end - if get_unit_buyout_price() > 0 and get_unit_start_price() > get_unit_buyout_price() then - post_button:Disable() - return - end - if get_unit_start_price() == 0 then - post_button:Enable() - return - end - if stack_count_slider:GetValue() == 0 then - post_button:Disable() - return - end - post_button:Enable() -end - -function update_item_configuration() - if not selected_item then - refresh_button:Disable() - - item.texture:SetTexture(nil) - item.count:SetText() - item.name:SetTextColor(aux.color.label.enabled()) - item.name:SetText('No item selected') - - unit_start_price_input:Hide() - unit_buyout_price_input:Hide() - stack_size_slider:Hide() - stack_count_slider:Hide() - deposit:Hide() - duration_dropdown:Hide() - hide_checkbox:Hide() - else - unit_start_price_input:Show() - unit_buyout_price_input:Show() - stack_size_slider:Show() - stack_count_slider:Show() - deposit:Show() - duration_dropdown:Show() - hide_checkbox:Show() - - item.texture:SetTexture(selected_item.texture) - item.name:SetText('[' .. selected_item.name .. ']') - do - local color = ITEM_QUALITY_COLORS[selected_item.quality] - item.name:SetTextColor(color.r, color.g, color.b) - end - if selected_item.aux_quantity > 1 then - item.count:SetText(selected_item.aux_quantity) - else - item.count:SetText() - end - - stack_size_slider.editbox:SetNumber(stack_size_slider:GetValue()) - stack_count_slider.editbox:SetNumber(stack_count_slider:GetValue()) - - do - local deposit_factor = UnitFactionGroup'npc' and .05 or .25 - local duration_factor = UIDropDownMenu_GetSelectedValue(duration_dropdown) / 120 - local stack_size, stack_count = selected_item.max_charges and 1 or stack_size_slider:GetValue(), stack_count_slider:GetValue() - local amount = floor(selected_item.unit_vendor_price * deposit_factor * stack_size) * stack_count * duration_factor - deposit:SetText('Deposit: ' .. money.to_string(amount, nil, nil, aux.color.text.enabled)) - end - - refresh_button:Enable() - end -end - -function undercut(record, stack_size, stack) - local price = ceil(record.unit_price * (stack and record.stack_size or stack_size)) - if not record.own then - price = price - 1 - end - return price / stack_size -end - -function quantity_update(maximize_count) - if selected_item then - local max_stack_count = selected_item.max_charges and selected_item.availability[stack_size_slider:GetValue()] or floor(selected_item.availability[0] / stack_size_slider:GetValue()) - stack_count_slider:SetMinMaxValues(1, max_stack_count) - if maximize_count then - stack_count_slider:SetValue(max_stack_count) - end - end - refresh = true -end - -function unit_vendor_price(item_key) - for slot in info.inventory() do - T.temp(slot) - local item_info = T.temp-info.container_item(unpack(slot)) - if item_info and item_info.item_key == item_key then - if info.auctionable(item_info.tooltip, nil, true) and not item_info.lootable then - ClearCursor() - PickupContainerItem(unpack(slot)) - ClickAuctionSellItemButton() - local auction_sell_item = T.temp-info.auction_sell_item() - ClearCursor() - ClickAuctionSellItemButton() - ClearCursor() - if auction_sell_item then - return auction_sell_item.vendor_price / auction_sell_item.count - end - end - end - end -end - -function update_item(item) - local settings = read_settings(item.key) - - item.unit_vendor_price = unit_vendor_price(item.key) - if not item.unit_vendor_price then - settings.hidden = true - write_settings(settings, item.key) - refresh = true - return - end - - scan.abort(scan_id) - - selected_item = item - - UIDropDownMenu_Initialize(duration_dropdown, initialize_duration_dropdown) - UIDropDownMenu_SetSelectedValue(duration_dropdown, settings.duration) - - hide_checkbox:SetChecked(settings.hidden) - local ii = 1 - if selected_item.max_charges then - for i = selected_item.max_charges, 1, -1 do - if selected_item.availability[i] > 0 then - stack_size_slider:SetMinMaxValues(1, i) - ii=i - break - end - end - else - ii = min(selected_item.max_stack, selected_item.aux_quantity) - stack_size_slider:SetMinMaxValues(1, min(selected_item.max_stack, selected_item.aux_quantity)) - end - - stack_size_slider:SetValue(math.random(1,ii)) - --stack_size_slider:SetValue(aux.huge) - --stack_size_slider:SetValue(1) - quantity_update(true) - - unit_start_price_input:SetText(money.to_string(settings.start_price, true, nil, nil, true)) - unit_buyout_price_input:SetText(money.to_string(settings.buyout_price, true, nil, nil, true)) - - if not bid_records[selected_item.key] then - refresh_entries() - end - - write_settings(settings, item.key) - - refresh = true -end - -function update_inventory_records() - local auctionable_map = T.temp-T.acquire() - for slot in info.inventory() do - T.temp(slot) - local item_info = T.temp-info.container_item(unpack(slot)) - if item_info then - local charge_class = item_info.charges or 0 - if info.auctionable(item_info.tooltip, nil, true) and not item_info.lootable then - if not auctionable_map[item_info.item_key] then - local availability = T.acquire() - for i = 0, 10 do - availability[i] = 0 - end - availability[charge_class] = item_info.count - auctionable_map[item_info.item_key] = T.map( - 'item_id', item_info.item_id, - 'suffix_id', item_info.suffix_id, - 'key', item_info.item_key, - 'itemstring', item_info.itemstring, - 'name', item_info.name, - 'texture', item_info.texture, - 'quality', item_info.quality, - 'aux_quantity', item_info.charges or item_info.count, - 'max_stack', item_info.max_stack, - 'max_charges', item_info.max_charges, - 'availability', availability - ) - else - local auctionable = auctionable_map[item_info.item_key] - auctionable.availability[charge_class] = (auctionable.availability[charge_class] or 0) + item_info.count - auctionable.aux_quantity = auctionable.aux_quantity + (item_info.charges or item_info.count) - end - end - end - end - T.release(inventory_records) - inventory_records = aux.values(auctionable_map) - refresh = true -end - -function refresh_entries() - if selected_item then - local item_key = selected_item.key - set_bid_selection() - set_buyout_selection() - bid_records[item_key], buyout_records[item_key] = nil, nil - local query = scan_util.item_query(selected_item.item_id) - status_bar:update_status(0, 0) - status_bar:set_text('Scanning auctions...') - - scan_id = scan.start{ - type = 'list', - ignore_owner = true, - queries = T.list(query), - on_page_loaded = function(page, total_pages) - status_bar:update_status(page / total_pages, 0) -- TODO - status_bar:set_text(format('Scanning Page %d / %d', page, total_pages)) - end, - on_auction = function(auction_record) - if auction_record.item_key == item_key then - record_auction( - auction_record.item_key, - auction_record.aux_quantity, - auction_record.unit_blizzard_bid, - auction_record.unit_buyout_price, - auction_record.duration, - auction_record.owner - ) - end - end, - on_abort = function() - bid_records[item_key], buyout_records[item_key] = nil, nil - status_bar:update_status(1, 1) - status_bar:set_text('Scan aborted') - end, - on_complete = function() - bid_records[item_key] = bid_records[item_key] or T.acquire() - buyout_records[item_key] = buyout_records[item_key] or T.acquire() - refresh = true - status_bar:update_status(1, 1) - status_bar:set_text('Scan complete') - end, - } - end -end - -function record_auction(key, aux_quantity, unit_blizzard_bid, unit_buyout_price, duration, owner) - bid_records[key] = bid_records[key] or T.acquire() - do - local entry - for _, record in bid_records[key] do - if unit_blizzard_bid == record.unit_price and aux_quantity == record.stack_size and duration == record.duration and info.is_player(owner) == record.own then - entry = record - end - end - if not entry then - entry = T.map('stack_size', aux_quantity, 'unit_price', unit_blizzard_bid, 'duration', duration, 'own', info.is_player(owner), 'count', 0) - tinsert(bid_records[key], entry) - end - entry.count = entry.count + 1 - end - buyout_records[key] = buyout_records[key] or T.acquire() - if unit_buyout_price == 0 then return end - do - local entry - for _, record in buyout_records[key] do - if unit_buyout_price == record.unit_price and aux_quantity == record.stack_size and duration == record.duration and info.is_player(owner) == record.own then - entry = record - end - end - if not entry then - entry = T.map('stack_size', aux_quantity, 'unit_price', unit_buyout_price, 'duration', duration, 'own', info.is_player(owner), 'count', 0) - tinsert(buyout_records[key], entry) - end - entry.count = entry.count + 1 - end -end - -function on_update() - if refresh then - refresh = false - price_update() - update_item_configuration() - update_inventory_listing() - update_auction_listings() - end - validate_parameters() -end - -function initialize_duration_dropdown() - local function on_click() - UIDropDownMenu_SetSelectedValue(duration_dropdown, this.value) - local settings = read_settings() - settings.duration = this.value - write_settings(settings) - refresh = true - end - UIDropDownMenu_AddButton{ - text = '2 Hours', - value = DURATION_2, - func = on_click, - } - UIDropDownMenu_AddButton{ - text = '8 Hours', - value = DURATION_8, - func = on_click, - } - UIDropDownMenu_AddButton{ - text = '24 Hours', - value = DURATION_24, - func = on_click, - } -end diff --git a/aux-addon/tabs/post/frame.lua b/aux-addon/tabs/post/frame.lua deleted file mode 100644 index 60bebbf..0000000 --- a/aux-addon/tabs/post/frame.lua +++ /dev/null @@ -1,326 +0,0 @@ -module 'aux.tabs.post' - -local aux = require 'aux' -local info = require 'aux.util.info' -local money = require 'aux.util.money' -local gui = require 'aux.gui' -local listing = require 'aux.gui.listing' -local item_listing = require 'aux.gui.item_listing' -local search_tab = require 'aux.tabs.search' - -frame = CreateFrame('Frame', nil, aux.frame) -frame:SetAllPoints() -frame:SetScript('OnUpdate', on_update) -frame:Hide() - -frame.content = CreateFrame('Frame', nil, frame) -frame.content:SetPoint('TOP', frame, 'TOP', 0, -8) -frame.content:SetPoint('BOTTOMLEFT', aux.frame.content, 'BOTTOMLEFT', 0, 0) -frame.content:SetPoint('BOTTOMRIGHT', aux.frame.content, 'BOTTOMRIGHT', 0, 0) - -frame.inventory = gui.panel(frame.content) -frame.inventory:SetWidth(212) -frame.inventory:SetPoint('TOPLEFT', 0, 0) -frame.inventory:SetPoint('BOTTOMLEFT', 0, 0) - -frame.parameters = gui.panel(frame.content) -frame.parameters:SetHeight(173) -frame.parameters:SetPoint('TOPLEFT', frame.inventory, 'TOPRIGHT', 2.5, 0) -frame.parameters:SetPoint('TOPRIGHT', 0, 0) - -frame.bid_listing = gui.panel(frame.content) -frame.bid_listing:SetHeight(228) -frame.bid_listing:SetWidth(271.5) -frame.bid_listing:SetPoint('BOTTOMLEFT', frame.inventory, 'BOTTOMRIGHT', 2.5, 0) - -frame.buyout_listing = gui.panel(frame.content) -frame.buyout_listing:SetHeight(228) -frame.buyout_listing:SetWidth(271.5) -frame.buyout_listing:SetPoint('BOTTOMRIGHT', 0, 0) - -do - local checkbox = gui.checkbox(frame.inventory) - checkbox:SetPoint('TOPLEFT', 49, -15) - checkbox:SetScript('OnClick', function() - refresh = true - end) - local label = gui.label(checkbox, gui.font_size.small) - label:SetPoint('LEFT', checkbox, 'RIGHT', 4, 1) - label:SetText('Show hidden items') - show_hidden_checkbox = checkbox -end - -gui.horizontal_line(frame.inventory, -45) - -do - local f = CreateFrame('Frame', nil, frame.inventory) - f:SetPoint('TOPLEFT', 0, -51) - f:SetPoint('BOTTOMRIGHT', 0, 0) - inventory_listing = item_listing.new( - f, - function() - if arg1 == 'LeftButton' then - update_item(this.item_record) - elseif arg1 == 'RightButton' then - aux.set_tab(1) - search_tab.set_filter(strlower(info.item(this.item_record.item_id).name) .. '/exact') - search_tab.execute(nil, false) - end - end, - function(item_record) - return item_record == selected_item - end - ) -end - -bid_listing = listing.new(frame.bid_listing) -bid_listing:SetColInfo{ - {name='Auctions', width=.17, align='CENTER'}, - {name='Time\nLeft', width=.11, align='CENTER'}, - {name='Stack\nSize', width=.11, align='CENTER'}, - {name='Auction Bid\n(per item)', width=.4, align='RIGHT'}, - {name='% Hist.\nValue', width=.21, align='CENTER'}, -} -bid_listing:SetSelection(function(data) - return data.record == get_bid_selection() or data.record.historical_value and get_bid_selection() and get_bid_selection().historical_value -end) -bid_listing:SetHandler('OnClick', function(table, row_data, column, button) - if button == 'RightButton' and row_data.record == get_bid_selection() or row_data.record.historical_value and get_bid_selection() and get_bid_selection().historical_value then - set_bid_selection() - else - set_bid_selection(row_data.record) - end - refresh = true -end) -bid_listing:SetHandler('OnDoubleClick', function(table, row_data, column, button) - stack_size_slider:SetValue(row_data.record.stack_size) - refresh = true -end) - -buyout_listing = listing.new(frame.buyout_listing) -buyout_listing:SetColInfo{ - {name='Auctions', width=.17, align='CENTER'}, - {name='Time\nLeft', width=.11, align='CENTER'}, - {name='Stack\nSize', width=.12, align='CENTER'}, - {name='Auction Buyout\n(per item)', width=.4, align='RIGHT'}, - {name='% Hist.\nValue', width=.20, align='CENTER'}, -} -buyout_listing:SetSelection(function(data) - return data.record == get_buyout_selection() or data.record.historical_value and get_buyout_selection() and get_buyout_selection().historical_value -end) -buyout_listing:SetHandler('OnClick', function(table, row_data, column, button) - if button == 'RightButton' and row_data.record == get_buyout_selection() or row_data.record.historical_value and get_buyout_selection() and get_buyout_selection().historical_value then - set_buyout_selection() - else - set_buyout_selection(row_data.record) - end - refresh = true -end) -buyout_listing:SetHandler('OnDoubleClick', function(table, row_data, column, button) - stack_size_slider:SetValue(row_data.record.stack_size) - refresh = true -end) - -do - status_bar = gui.status_bar(frame) - status_bar:SetWidth(265) - status_bar:SetHeight(25) - status_bar:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) - status_bar:update_status(1, 1) - status_bar:set_text('') -end -do - local btn = gui.button(frame.parameters) - btn:SetPoint('TOPLEFT', status_bar, 'TOPRIGHT', 5, 0) - btn:SetText('Post') - btn:SetScript('OnClick', post_auctions) - post_button = btn -end -do - local btn = gui.button(frame.parameters) - btn:SetPoint('TOPLEFT', post_button, 'TOPRIGHT', 5, 0) - btn:SetText('Refresh') - btn:SetScript('OnClick', refresh_button_click) - refresh_button = btn -end -do - item = gui.item(frame.parameters) - item:SetPoint('TOPLEFT', 10, -6) - item.button:SetScript('OnEnter', function() - if selected_item then - info.set_tooltip(selected_item.itemstring, this, 'ANCHOR_RIGHT') - end - end) - item.button:SetScript('OnLeave', function() - GameTooltip:Hide() - end) -end -do - local slider = gui.slider(frame.parameters) - slider:SetValueStep(1) - slider:SetPoint('TOPLEFT', 13, -73) - slider:SetWidth(190) - slider:SetScript('OnValueChanged', function() - quantity_update(true) - end) - slider.editbox.change = function() - slider:SetValue(this:GetNumber()) - quantity_update(true) - if selected_item then - local settings = read_settings() - write_settings(settings) - end - end - slider.editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - unit_buyout_price_input:SetFocus() - elseif stack_count_slider.editbox:IsVisible() then - stack_count_slider.editbox:SetFocus() - else - unit_start_price_input:SetFocus() - end - end) - slider.editbox:SetNumeric(true) - slider.editbox:SetMaxLetters(3) - slider.label:SetText('Stack Size') - stack_size_slider = slider -end -do - local slider = gui.slider(frame.parameters) - slider:SetValueStep(1) - slider:SetPoint('TOPLEFT', stack_size_slider, 'BOTTOMLEFT', 0, -32) - slider:SetWidth(190) - slider:SetScript('OnValueChanged', function() - quantity_update() - end) - slider.editbox.change = function() - slider:SetValue(this:GetNumber()) - quantity_update() - end - slider.editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - stack_size_slider.editbox:SetFocus() - else - unit_start_price_input:SetFocus() - end - end) - slider.editbox:SetNumeric(true) - slider.label:SetText('Stack Count') - stack_count_slider = slider -end -do - local dropdown = gui.dropdown(frame.parameters) - dropdown:SetPoint('TOPLEFT', stack_count_slider, 'BOTTOMLEFT', 0, -22) - dropdown:SetWidth(90) - local label = gui.label(dropdown, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) - label:SetText('Duration') - UIDropDownMenu_Initialize(dropdown, initialize_duration_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_duration_dropdown) - end) - duration_dropdown = dropdown -end -do - local checkbox = gui.checkbox(frame.parameters) - checkbox:SetPoint('TOPRIGHT', -83, -6) - checkbox:SetScript('OnClick', function() - local settings = read_settings() - settings.hidden = this:GetChecked() - write_settings(settings) - refresh = true - end) - local label = gui.label(checkbox, gui.font_size.small) - label:SetPoint('LEFT', checkbox, 'RIGHT', 4, 1) - label:SetText('Hide this item') - hide_checkbox = checkbox -end -do - local editbox = gui.editbox(frame.parameters) - editbox:SetPoint('TOPRIGHT', -71, -60) - editbox:SetWidth(180) - editbox:SetHeight(22) - editbox:SetAlignment('RIGHT') - editbox:SetFontSize(17) - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - stack_count_slider.editbox:SetFocus() - else - unit_buyout_price_input:SetFocus() - end - end) - editbox.formatter = function() return money.to_string(get_unit_start_price(), true) end - editbox.char = function() set_bid_selection(); set_buyout_selection(); set_unit_start_price(money.from_string(this:GetText())) end - editbox.change = function() refresh = true end - editbox.enter = function() this:ClearFocus() end - editbox.focus_loss = function() - this:SetText(money.to_string(get_unit_start_price(), true, nil, nil, true)) - end - do - local label = gui.label(editbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) - label:SetText('Unit Starting Price') - end - do - local label = gui.label(editbox, 14) - label:SetPoint('LEFT', editbox, 'RIGHT', 8, 0) - label:SetWidth(50) - label:SetJustifyH('CENTER') - start_price_percentage = label - end - unit_start_price_input = editbox -end -do - local editbox = gui.editbox(frame.parameters) - editbox:SetPoint('TOPRIGHT', unit_start_price_input, 'BOTTOMRIGHT', 0, -19) - editbox:SetWidth(180) - editbox:SetHeight(22) - editbox:SetAlignment('RIGHT') - editbox:SetFontSize(17) - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - unit_start_price_input:SetFocus() - else - stack_size_slider.editbox:SetFocus() - end - end) - editbox.formatter = function() return money.to_string(get_unit_buyout_price(), true) end - editbox.char = function() set_buyout_selection(); set_unit_buyout_price(money.from_string(this:GetText())) end - editbox.change = function() refresh = true end - editbox.enter = function() this:ClearFocus() end - editbox.focus_loss = function() - this:SetText(money.to_string(get_unit_buyout_price(), true, nil, nil, true)) - end - do - local label = gui.label(editbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) - label:SetText('Unit Buyout Price') - end - do - local label = gui.label(editbox, 14) - label:SetPoint('LEFT', editbox, 'RIGHT', 8, 0) - label:SetWidth(50) - label:SetJustifyH('CENTER') - buyout_price_percentage = label - end - unit_buyout_price_input = editbox -end -do - local label = gui.label(frame.parameters, gui.font_size.medium) - label:SetPoint('TOPLEFT', unit_buyout_price_input, 'BOTTOMLEFT', 0, -24) - deposit = label -end - -function aux.handle.LOAD() - if not aux.account_data.post_bid then - frame.bid_listing:Hide() - frame.buyout_listing:SetPoint('BOTTOMLEFT', frame.inventory, 'BOTTOMRIGHT', 2.5, 0) - buyout_listing:SetColInfo{ - {name='Auctions', width=.15, align='CENTER'}, - {name='Time Left', width=.15, align='CENTER'}, - {name='Stack Size', width=.15, align='CENTER'}, - {name='Auction Buyout (per item)', width=.4, align='RIGHT'}, - {name='% Hist. Value', width=.15, align='CENTER'}, - } - end -end \ No newline at end of file diff --git a/aux-addon/tabs/search/core.lua b/aux-addon/tabs/search/core.lua deleted file mode 100644 index fcf9650..0000000 --- a/aux-addon/tabs/search/core.lua +++ /dev/null @@ -1,85 +0,0 @@ -module 'aux.tabs.search' - -local aux = require 'aux' -local info = require 'aux.util.info' - -local tab = aux.tab 'Search' - -StaticPopupDialogs.AUX_SEARCH_TABLE_FULL = { - text = 'Table full!\nFurther results from this search will still be processed but no longer displayed in the table.', - button1 = 'Ok', - showAlert = 1, - timeout = 0, - hideOnEscape = 1, -} - -RESULTS, SAVED, FILTER = aux.enum(3) - -function aux.handle.LOAD() - set_subtab(SAVED) -end - -function tab.OPEN() - frame:Show() - update_search_listings() - update_filter_display() -end - -function tab.CLOSE() - current_search().table:SetSelectedRecord() - frame:Hide() -end - -function tab.CLICK_LINK(item_info) - set_filter(strlower(item_info.name) .. '/exact') - execute(nil, false) -end - -function tab.USE_ITEM(item_info) - set_filter(strlower(info.item(item_info.item_id).name) .. '/exact') - execute(nil, false) -end - -function set_subtab(tab) - CloseDropDownMenus() - search_results_button:UnlockHighlight() - saved_searches_button:UnlockHighlight() - new_filter_button:UnlockHighlight() - frame.results:Hide() - frame.saved:Hide() - frame.filter:Hide() - - if tab == RESULTS then - frame.results:Show() - search_results_button:LockHighlight() - elseif tab == SAVED then - frame.saved:Show() - saved_searches_button:LockHighlight() - elseif tab == FILTER then - frame.filter:Show() - new_filter_button:LockHighlight() - end -end - -function M.set_filter(filter_string) - search_box:SetFocus() - search_box:SetText(filter_string) -end - -function add_filter(filter_string) - local old_filter_string = search_box:GetText() - old_filter_string = aux.trim(old_filter_string) - - if old_filter_string ~= '' then - old_filter_string = old_filter_string .. ';' - end - - set_filter(old_filter_string .. filter_string) -end - -function blizzard_page_index(str) - if tonumber(str) then - return max(0, tonumber(str) - 1) - end -end - diff --git a/aux-addon/tabs/search/filter.lua b/aux-addon/tabs/search/filter.lua deleted file mode 100644 index eaac271..0000000 --- a/aux-addon/tabs/search/filter.lua +++ /dev/null @@ -1,395 +0,0 @@ -module 'aux.tabs.search' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local money = require 'aux.util.money' -local filter_util = require 'aux.util.filter' - -local post_filter = {} -local post_filter_index = 0 - -function valid_level(str) - local level = tonumber(str) - return level and aux.bounded(1, 60, level) -end - -blizzard_query = setmetatable(T.acquire(), { - __index = function(_, key) - if key == 'name' then - return name_input:GetText() - elseif key == 'exact' then - return exact_checkbox:GetChecked() - elseif key == 'min_level' then - return tonumber(min_level_input:GetText()) - elseif key == 'max_level' then - return tonumber(max_level_input:GetText()) - elseif key == 'usable' then - return usable_checkbox:GetChecked() - elseif key == 'class' then - local class_index = UIDropDownMenu_GetSelectedValue(class_dropdown) - return class_index ~= 0 and class_index or nil - elseif key == 'subclass' then - local subclass_index = UIDropDownMenu_GetSelectedValue(subclass_dropdown) - return subclass_index ~= 0 and subclass_index or nil - elseif key == 'slot' then - local slot_index = UIDropDownMenu_GetSelectedValue(slot_dropdown) - return (slot_index or 0) > 0 and slot_index or nil - elseif key == 'quality' then - local quality_code = UIDropDownMenu_GetSelectedValue(quality_dropdown) - return (quality_code or -1) >= 0 and quality_code or nil - end - end, - __newindex = function(_, key, value) - if key == 'name' then - name_input:SetText(value) - elseif key == 'exact' then - exact_checkbox:SetChecked(value) - elseif key == 'min_level' then - min_level_input:SetText(value) - elseif key == 'max_level' then - max_level_input:SetText(value) - elseif key == 'usable' then - usable_checkbox:SetChecked(value) - elseif key == 'class' then - UIDropDownMenu_Initialize(class_dropdown, initialize_class_dropdown) - UIDropDownMenu_SetSelectedValue(class_dropdown, value) - elseif key == 'subclass' then - UIDropDownMenu_Initialize(subclass_dropdown, initialize_subclass_dropdown) - UIDropDownMenu_SetSelectedValue(subclass_dropdown, value) - elseif key == 'slot' then - UIDropDownMenu_Initialize(slot_dropdown, initialize_slot_dropdown) - UIDropDownMenu_SetSelectedValue(slot_dropdown, value) - elseif key == 'quality' then - UIDropDownMenu_Initialize(quality_dropdown, initialize_quality_dropdown) - UIDropDownMenu_SetSelectedValue(quality_dropdown, value) - end - end, -}) - -function update_form() - if GetAuctionItemSubClasses(blizzard_query.class or 0) then - subclass_dropdown.button:Enable() - else - subclass_dropdown.button:Disable() - end - - if blizzard_query.class == 2 then - slot_dropdown.button:Enable() - else - slot_dropdown.button:Disable() - end - - if blizzard_query.exact then - usable_checkbox:Disable() - for key in T.temp-T.set('min_level', 'max_level') do - _M[key .. '_input']:EnableMouse(false) - _M[key .. '_input']:ClearFocus() - end - for key in T.temp-T.set('class', 'subclass', 'slot', 'quality') do - _M[key .. '_dropdown'].button:Disable() - end - CloseDropDownMenus() - else - usable_checkbox:Enable() - for key in T.temp-T.set('min_level', 'max_level') do - _M[key .. '_input']:EnableMouse(true) - end - class_dropdown.button:Enable() - quality_dropdown.button:Enable() - end - - if aux.any(T.temp-T.list('min_level', 'max_level', 'usable', 'class', 'subclass', 'slot', 'quality'), function(key) return blizzard_query[key] end) then - exact_checkbox:Disable() - else - exact_checkbox:Enable() - end -end - -function get_filter_builder_query() - local filter_string - - local function add(part) - if part then - filter_string = filter_string and filter_string .. '/' .. part or part - end - end - - local name = blizzard_query.name - if not aux.index(filter_util.parse_filter_string(name), 'blizzard', 'name') then - name = filter_util.quote(name) - end - add((name ~= '' or blizzard_query.exact) and name) - - add(blizzard_query.exact and 'exact') - add(blizzard_query.min_level or blizzard_query.max_level and 1) - add(blizzard_query.max_level) - add(blizzard_query.usable and 'usable') - - local classes = T.temp-T.list(GetAuctionItemClasses()) - if blizzard_query.class and blizzard_query.class > 0 then - add(strlower(classes[blizzard_query.class])) - end - local subclasses = T.temp-T.list(GetAuctionItemSubClasses(blizzard_query.class or 0)) - if blizzard_query.subclass and blizzard_query.subclass > 0 then - add(strlower(subclasses[blizzard_query.subclass])) - end - local slot_index = blizzard_query.slot - if slot_index then - local slots = T.temp-T.list(GetAuctionInvTypes(blizzard_query.class or 0, blizzard_query.subclass or 0)) - add(strlower(_G[slots[slot_index]])) - end - - local quality = blizzard_query.quality - if quality and quality >= 0 then - add(strlower(_G['ITEM_QUALITY' .. quality .. '_DESC'])) - end - - local post_filter_string = filter_util.filter_string(post_filter) - add(post_filter_string ~= '' and post_filter_string) - - return filter_string or '' -end - -function set_form(filter) - clear_form() - for _, component in ipairs(filter.components) do - if component[1] == 'blizzard' then - blizzard_query[component[2]] = component[4] - else - add_component(component) - end - end - update_filter_display() -end - -function clear_form() - blizzard_query.name = '' - name_input:ClearFocus() - blizzard_query.exact = false - blizzard_query.min_level = '' - min_level_input:ClearFocus() - blizzard_query.max_level = '' - max_level_input:ClearFocus() - blizzard_query.usable = false - UIDropDownMenu_ClearAll(class_dropdown) - UIDropDownMenu_ClearAll(subclass_dropdown) - UIDropDownMenu_ClearAll(slot_dropdown) - UIDropDownMenu_ClearAll(quality_dropdown) - filter_parameter_input:ClearFocus() - T.wipe(post_filter) - post_filter_index = 0 - update_filter_display() -end - -function import_filter_string() - local filter, error = filter_util.parse_filter_string(aux.select(3, strfind(search_box:GetText(), '^([^;]*)'))) - if filter or aux.print(error) then - set_form(filter) - end - update_form() -end - -function export_filter_string() - set_filter(get_filter_builder_query()) -end - -function formatted_post_filter(components) - local no_line_break - local stack = T.temp-T.acquire() - local str = '' - - for i, component in ipairs(components) do - local component = components[i] - if no_line_break then - str = str .. ' ' - end - str = str .. '

' - for _ = 1, getn(stack) + 1 do - str = str .. aux.color.content.background'----' - end - no_line_break = component[1] == 'operator' and component[2] == 'not' - - local filter_color = (post_filter_index == i and aux.color.gold or aux.color.orange) - local component_text = filter_color(component[2]) - if component[1] == 'operator' and component[2] ~= 'not' then - component_text = component_text .. filter_color(tonumber(component[3]) or '') - tinsert(stack, component[3]) - elseif component[1] == 'filter' then - local parameter = component[3] - if parameter then - if component[2] == 'item' then - parameter = info.display_name(info.item_id(parameter)) or '[' .. parameter .. ']' - elseif filter_util.filters[component[2]].input_type == 'money' then - parameter = money.to_string(money.from_string(parameter), nil, true) - end - component_text = component_text .. filter_color(': ') .. parameter - end - while getn(stack) > 0 and stack[getn(stack)] do - local top = tremove(stack) - if tonumber(top) and top > 1 then - tinsert(stack, top - 1) - break - end - end - end - str = str .. data_link(i, component_text) - end - - return '

' .. data_link(0, 'Post Filter:') .. '

' .. str .. '

' -end - -function data_link(id, str) - return format('%s', id, str) -end - -function data_link_click() - local button = arg3 - local index = tonumber(arg1) - if button == 'LeftButton' then - post_filter_index = index - elseif button == 'RightButton' and index > 0 then - remove_component(index) - end - update_filter_display() -end - -function remove_component(index) - tremove(post_filter, index) - if post_filter_index >= index then - post_filter_index = max(post_filter_index - 1, min(1, getn(post_filter))) - end -end - -function add_component(component) - post_filter_index = post_filter_index + 1 - tinsert(post_filter, post_filter_index, component) -end - -function add_form_component() - local str = filter_input:GetText() - if str then - local filter = filter_util.filters[str] - if filter then - if filter.input_type ~= '' then - str = str .. '/' .. filter_parameter_input:GetText() - end - end - local components, error = filter_util.parse_filter_string(str) - if components and getn(components.blizzard) == 0 and getn(components.post) == 1 then - add_component(components.post[1]) - update_filter_display() - filter_parameter_input:SetText('') - filter_input:HighlightText() - filter_input:SetFocus() - elseif error then - aux.print(error) - end - end -end - -do - local text = '' - function update_filter_display() - text = formatted_post_filter(post_filter) - filter_display:SetWidth(filter_display_size()) - set_filter_display_offset() - filter_display:SetText(text) - end - function filter_display_size() - local font, font_size = filter_display:GetFont() - filter_display.measure:SetFont(font, font_size) - local lines = 0 - local width = 0 - for line in string.gfind(text, '

(.-)

') do - lines = lines + 1 - filter_display.measure:SetText(line) - width = max(width, filter_display.measure:GetStringWidth()) - end - return width, lines * (font_size + .5) - end -end - -function set_filter_display_offset(x_offset, y_offset) - local scroll_frame = filter_display:GetParent() - x_offset, y_offset = x_offset or scroll_frame:GetHorizontalScroll(), y_offset or scroll_frame:GetVerticalScroll() - local width, height = filter_display_size() - local x_lower_bound = min(0, scroll_frame:GetWidth() - width - 10) - local x_upper_bound = 0 - local y_lower_bound = 0 - local y_upper_bound = max(0, height - scroll_frame:GetHeight()) - scroll_frame:SetHorizontalScroll(aux.bounded(x_lower_bound, x_upper_bound, x_offset)) - scroll_frame:SetVerticalScroll(aux.bounded(y_lower_bound, y_upper_bound, y_offset)) -end - -function initialize_filter_dropdown() - for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'disenchant-percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'bid-disenchant-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do - UIDropDownMenu_AddButton(T.map( - 'text', filter, - 'value', filter, - 'func', function() - filter_input:SetText(this.value) - if aux.index(filter_util.filters[this.value], 'input_type') == '' or this.value == 'not' then - add_form_component() - elseif filter_util.filters[this.value] then - filter_parameter_input:Show() - filter_parameter_input:SetFocus() - else - filter_input:SetFocus() - end - end - )) - end -end - -function initialize_class_dropdown() - local function on_click() - if this.value ~= blizzard_query.class then - UIDropDownMenu_SetSelectedValue(class_dropdown, this.value) - UIDropDownMenu_ClearAll(subclass_dropdown) - UIDropDownMenu_Initialize(subclass_dropdown, initialize_subclass_dropdown) - UIDropDownMenu_ClearAll(slot_dropdown) - UIDropDownMenu_Initialize(slot_dropdown, initialize_slot_dropdown) - update_form() - end - end - UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) - for i, class in ipairs(T.temp-T.list(GetAuctionItemClasses())) do - UIDropDownMenu_AddButton(T.map('text', class, 'value', i, 'func', on_click)) - end -end - -function initialize_subclass_dropdown() - local function on_click() - if this.value ~= blizzard_query.subclass then - UIDropDownMenu_SetSelectedValue(subclass_dropdown, this.value) - update_form() - end - end - UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) - for i, subclass in ipairs(T.temp-T.list(GetAuctionItemSubClasses(blizzard_query.class or 0))) do - UIDropDownMenu_AddButton(T.map('text', subclass, 'value', i, 'func', on_click)) - end -end - -function initialize_slot_dropdown() - local function on_click() - UIDropDownMenu_SetSelectedValue(slot_dropdown, this.value) - update_form() - end - UIDropDownMenu_AddButton(T.map('text', ALL, 'value', 0, 'func', on_click)) - for i, slot in ipairs(T.temp-T.list(GetAuctionInvTypes(blizzard_query.class == 2 and 2 or 0, blizzard_query.subclass or 0))) do - UIDropDownMenu_AddButton(T.map('text', _G[slot], 'value', i, 'func', on_click)) - end -end - -function initialize_quality_dropdown() - local function on_click() - UIDropDownMenu_SetSelectedValue(quality_dropdown, this.value) - update_form() - end - UIDropDownMenu_AddButton(T.map('text', ALL, 'value', -1, 'func', on_click)) - for i = 0, 4 do - UIDropDownMenu_AddButton(T.map('text', _G['ITEM_QUALITY' .. i .. '_DESC'], 'value', i, 'func', on_click)) - end -end diff --git a/aux-addon/tabs/search/frame.lua b/aux-addon/tabs/search/frame.lua deleted file mode 100644 index 24a5d3a..0000000 --- a/aux-addon/tabs/search/frame.lua +++ /dev/null @@ -1,559 +0,0 @@ -module 'aux.tabs.search' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local completion = require 'aux.util.completion' -local filter_util = require 'aux.util.filter' -local scan = require 'aux.core.scan' -local gui = require 'aux.gui' -local listing = require 'aux.gui.listing' -local auction_listing = require 'aux.gui.auction_listing' - -local FILTER_SPACING = 28.5 - -frame = CreateFrame('Frame', nil, aux.frame) -frame:SetAllPoints() -frame:SetScript('OnUpdate', on_update) -frame:Hide() - -frame.filter = gui.panel(frame) -frame.filter:SetAllPoints(aux.frame.content) - -frame.results = gui.panel(frame) -frame.results:SetAllPoints(aux.frame.content) - -frame.saved = CreateFrame('Frame', nil, frame) -frame.saved:SetAllPoints(aux.frame.content) - -frame.saved.favorite = gui.panel(frame.saved) -frame.saved.favorite:SetWidth(393) -frame.saved.favorite:SetPoint('TOPLEFT', 0, 0) -frame.saved.favorite:SetPoint('BOTTOMLEFT', 0, 0) - -frame.saved.recent = gui.panel(frame.saved) -frame.saved.recent:SetWidth(364.5) -frame.saved.recent:SetPoint('TOPRIGHT', 0, 0) -frame.saved.recent:SetPoint('BOTTOMRIGHT', 0, 0) -do - local btn = gui.button(frame, 25) - btn:SetPoint('TOPLEFT', 5, -8) - btn:SetWidth(30) - btn:SetHeight(25) - btn:SetText('<') - btn:SetScript('OnClick', previous_search) - previous_button = btn -end -do - local btn = gui.button(frame, 25) - btn:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) - btn:SetWidth(30) - btn:SetHeight(25) - btn:SetText('>') - btn:SetScript('OnClick', next_search) - next_button = btn -end -do - local btn = gui.button(frame, gui.font_size.small) - btn:SetHeight(25) - btn:SetWidth(60) - btn:SetText(aux.color.label.enabled'Range:') - btn:SetScript('OnClick', function() - update_real_time(true) - end) - range_button = btn -end -do - local btn = gui.button(frame, gui.font_size.small) - btn:SetHeight(25) - btn:SetWidth(60) - btn:Hide() - btn:SetText(aux.color.label.enabled'Real Time') - btn:SetScript('OnClick', function() - update_real_time(false) - end) - real_time_button = btn -end -do - local function change() - local page = tonumber(this:GetText()) - local valid_input = page and tostring(max(1, page)) or '' - if this:GetText() ~= valid_input then - this:SetText(valid_input) - end - end - do - local editbox = gui.editbox(range_button) - editbox:SetPoint('LEFT', range_button, 'RIGHT', 4, 0) - editbox:SetWidth(40) - editbox:SetHeight(25) - editbox:SetAlignment('CENTER') - editbox:SetNumeric(true) - editbox:SetScript('OnTabPressed', function() - if not IsShiftKeyDown() then - last_page_input:SetFocus() - end - end) - editbox.enter = execute - editbox.change = change - local label = gui.label(editbox, gui.font_size.medium) - label:SetPoint('LEFT', editbox, 'RIGHT', 0, 0) - label:SetTextColor(aux.color.label.enabled()) - label:SetText('-') - first_page_input = editbox - end - do - local editbox = gui.editbox(range_button) - editbox:SetPoint('LEFT', first_page_input, 'RIGHT', 5.8, 0) - editbox:SetWidth(40) - editbox:SetHeight(25) - editbox:SetAlignment('CENTER') - editbox:SetNumeric(true) - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - first_page_input:SetFocus() - else - search_box:SetFocus() - end - end) - editbox.enter = execute - editbox.change = change - last_page_input = editbox - end -end -do - local btn = gui.button(frame) - btn:SetHeight(25) - btn:SetPoint('TOPRIGHT', -5, -8) - btn:SetText('Search') - btn:RegisterForClicks('LeftButtonUp', 'RightButtonUp') - btn:SetScript('OnClick', function() - if arg1 == 'RightButton' then - set_filter(current_search().filter_string) - end - execute() - end) - start_button = btn -end -do - local btn = gui.button(frame) - btn:SetHeight(25) - btn:SetPoint('TOPRIGHT', -5, -8) - btn:SetText('Pause') - btn:SetScript('OnClick', function() - scan.abort(search_scan_id) - end) - stop_button = btn -end -do - local btn = gui.button(frame) - btn:SetHeight(25) - btn:SetPoint('RIGHT', start_button, 'LEFT', -4, 0) - btn:SetBackdropColor(aux.color.state.enabled()) - btn:SetText('Resume') - btn:SetScript('OnClick', function() - execute(true) - end) - resume_button = btn -end -do - local editbox = gui.editbox(frame) - editbox:EnableMouse(1) - editbox.formatter = function(str) - local queries = filter_util.queries(str) - return queries and aux.join(aux.map(aux.copy(queries), function(query) return query.prettified end), ';') or aux.color.red(str) - end - editbox.complete = completion.complete_filter - editbox.escape = function() this:SetText(current_search().filter_string or '') end - editbox:SetHeight(25) - editbox.char = function() - this:complete() - end - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - last_page_input:SetFocus() - else - this:HighlightText(0, 0) - end - end) - editbox.enter = execute - search_box = editbox -end -do - gui.horizontal_line(frame, -40) -end -do - local btn = gui.button(frame, gui.font_size.large) - btn:SetPoint('BOTTOMLEFT', aux.frame.content, 'TOPLEFT', 10, 8) - btn:SetWidth(243) - btn:SetHeight(22) - btn:SetText('Search Results') - btn:SetScript('OnClick', function() set_subtab(RESULTS) end) - search_results_button = btn -end -do - local btn = gui.button(frame, gui.font_size.large) - btn:SetPoint('TOPLEFT', search_results_button, 'TOPRIGHT', 5, 0) - btn:SetWidth(243) - btn:SetHeight(22) - btn:SetText('Saved Searches') - btn:SetScript('OnClick', function() set_subtab(SAVED) end) - saved_searches_button = btn -end -do - local btn = gui.button(frame, gui.font_size.large) - btn:SetPoint('TOPLEFT', saved_searches_button, 'TOPRIGHT', 5, 0) - btn:SetWidth(243) - btn:SetHeight(22) - btn:SetText('Filter Builder') - btn:SetScript('OnClick', function() set_subtab(FILTER) end) - new_filter_button = btn -end -do - local frame = CreateFrame('Frame', nil, frame) - frame:SetWidth(265) - frame:SetHeight(25) - frame:SetPoint('TOPLEFT', aux.frame.content, 'BOTTOMLEFT', 0, -6) - status_bar_frame = frame -end -do - local btn = gui.button(frame.results) - btn:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) - btn:SetText('Bid') - btn:Disable() - bid_button = btn -end -do - local btn = gui.button(frame.results) - btn:SetPoint('TOPLEFT', bid_button, 'TOPRIGHT', 5, 0) - btn:SetText('Buyout') - btn:Disable() - buyout_button = btn -end -do - local btn = gui.button(frame.results) - btn:SetPoint('TOPLEFT', buyout_button, 'TOPRIGHT', 5, 0) - btn:SetText('Clear') - btn:SetScript('OnClick', function() - while tremove(current_search().records) do end - current_search().table:SetDatabase() - end) -end -do - local btn = gui.button(frame.saved) - btn:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) - btn:SetText('Favorite') - btn:SetScript('OnClick', function() - add_favorite(search_box:GetText()) - end) -end -do - local btn1 = gui.button(frame.filter) - btn1:SetPoint('TOPLEFT', status_bar_frame, 'TOPRIGHT', 5, 0) - btn1:SetText('Search') - btn1:SetScript('OnClick', function() - export_filter_string() - execute() - end) - - local btn2 = gui.button(frame.filter) - btn2:SetPoint('LEFT', btn1, 'RIGHT', 5, 0) - btn2:SetText('Export') - btn2:SetScript('OnClick', export_filter_string) - - local btn3 = gui.button(frame.filter) - btn3:SetPoint('LEFT', btn2, 'RIGHT', 5, 0) - btn3:SetText('Import') - btn3:SetScript('OnClick', import_filter_string) -end -do - local editbox = gui.editbox(frame.filter) - editbox.complete_item = completion.complete(function() return aux.account_data.auctionable_items end) - editbox:SetPoint('TOPLEFT', 14, -FILTER_SPACING) - editbox:SetWidth(260) - editbox.char = function() - if blizzard_query.exact then - this:complete_item() - end - end - editbox:SetScript('OnTabPressed', function() - if blizzard_query.exact then - return - end - if IsShiftKeyDown() then - max_level_input:SetFocus() - else - min_level_input:SetFocus() - end - end) - editbox.change = update_form - editbox.enter = function() editbox:ClearFocus() end - local label = gui.label(editbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) - label:SetText('Name') - name_input = editbox -end -do - local checkbox = gui.checkbox(frame.filter) - checkbox:SetPoint('TOPLEFT', name_input, 'TOPRIGHT', 16, 0) - checkbox:SetScript('OnClick', update_form) - local label = gui.label(checkbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', checkbox, 'TOPLEFT', -2, 1) - label:SetText('Exact') - exact_checkbox = checkbox -end -do - local editbox = gui.editbox(frame.filter) - editbox:SetPoint('TOPLEFT', name_input, 'BOTTOMLEFT', 0, -FILTER_SPACING) - editbox:SetWidth(125) - editbox:SetAlignment('CENTER') - editbox:SetNumeric(true) - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - name_input:SetFocus() - else - max_level_input:SetFocus() - end - end) - editbox.enter = function() editbox:ClearFocus() end - editbox.change = function() - local valid_level = valid_level(this:GetText()) - if tostring(valid_level) ~= this:GetText() then - this:SetText(valid_level or '') - end - update_form() - end - local label = gui.label(editbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', editbox, 'TOPLEFT', -2, 1) - label:SetText('Level Range') - min_level_input = editbox -end -do - local editbox = gui.editbox(frame.filter) - editbox:SetPoint('TOPLEFT', min_level_input, 'TOPRIGHT', 10, 0) - editbox:SetWidth(125) - editbox:SetAlignment('CENTER') - editbox:SetNumeric(true) - editbox:SetScript('OnTabPressed', function() - if IsShiftKeyDown() then - min_level_input:SetFocus() - else - name_input:SetFocus() - end - end) - editbox.enter = function() editbox:ClearFocus() end - editbox.change = function() - local valid_level = valid_level(this:GetText()) - if tostring(valid_level) ~= this:GetText() then - this:SetText(valid_level or '') - end - update_form() - end - local label = gui.label(editbox, gui.font_size.medium) - label:SetPoint('RIGHT', editbox, 'LEFT', -3, 0) - label:SetText('-') - max_level_input = editbox -end -do - local checkbox = gui.checkbox(frame.filter) - checkbox:SetPoint('TOPLEFT', max_level_input, 'TOPRIGHT', 16, 0) - checkbox:SetScript('OnClick', update_form) - local label = gui.label(checkbox, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', checkbox, 'TOPLEFT', -2, 1) - label:SetText('Usable') - usable_checkbox = checkbox -end -do - local dropdown = gui.dropdown(frame.filter) - class_dropdown = dropdown - dropdown:SetPoint('TOPLEFT', min_level_input, 'BOTTOMLEFT', 0, 5 - FILTER_SPACING) - dropdown:SetWidth(300) - local label = gui.label(dropdown, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) - label:SetText('Item Class') - UIDropDownMenu_Initialize(dropdown, initialize_class_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_class_dropdown) - end) -end -do - local dropdown = gui.dropdown(frame.filter) - subclass_dropdown = dropdown - dropdown:SetPoint('TOPLEFT', class_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) - dropdown:SetWidth(300) - local label = gui.label(dropdown, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) - label:SetText('Item Subclass') - UIDropDownMenu_Initialize(dropdown, initialize_subclass_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_subclass_dropdown) - end) -end -do - local dropdown = gui.dropdown(frame.filter) - slot_dropdown = dropdown - dropdown:SetPoint('TOPLEFT', subclass_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) - dropdown:SetWidth(300) - local label = gui.label(dropdown, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) - label:SetText('Item Slot') - UIDropDownMenu_Initialize(dropdown, initialize_slot_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_slot_dropdown) - end) -end -do - local dropdown = gui.dropdown(frame.filter) - quality_dropdown = dropdown - dropdown:SetPoint('TOPLEFT', slot_dropdown, 'BOTTOMLEFT', 0, 10 - FILTER_SPACING) - dropdown:SetWidth(300) - local label = gui.label(dropdown, gui.font_size.small) - label:SetPoint('BOTTOMLEFT', dropdown, 'TOPLEFT', -2, -3) - label:SetText('Min Quality') - UIDropDownMenu_Initialize(dropdown, initialize_quality_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_quality_dropdown) - end) -end -gui.vertical_line(frame.filter, 332) -do - local dropdown = gui.dropdown(frame.filter) - dropdown:SetPoint('TOPRIGHT', -174.5, -10) - dropdown:SetWidth(150) - UIDropDownMenu_Initialize(dropdown, initialize_filter_dropdown) - dropdown:SetScript('OnShow', function() - UIDropDownMenu_Initialize(this, initialize_filter_dropdown) - end) - _G[dropdown:GetName() .. 'Text']:Hide() - local label = gui.label(dropdown, gui.font_size.medium) - label:SetPoint('RIGHT', dropdown, 'LEFT', -15, 0) - label:SetText('Component') - filter_dropdown = dropdown -end -do - local input = gui.editbox(frame.filter) - input:SetPoint('CENTER', filter_dropdown, 'CENTER', 0, 0) - input:SetWidth(150) - input:SetScript('OnTabPressed', function() filter_parameter_input:SetFocus() end) - input.complete = completion.complete(function() return T.temp-T.list('and', 'or', 'not', unpack(aux.keys(filter_util.filters))) end) - input.char = function() this:complete() end - input.change = function() - local text = this:GetText() - if filter_util.filters[text] and filter_util.filters[text].input_type ~= '' then - local _, _, suggestions = filter_util.parse_filter_string(text .. '/') - filter_parameter_input:SetNumeric(filter_util.filters[text].input_type == 'number') - filter_parameter_input.complete = completion.complete(function() return suggestions or T.empty end) - filter_parameter_input:Show() - else - filter_parameter_input:Hide() - end - end - input.enter = function() - if filter_parameter_input:IsVisible() then - filter_parameter_input:SetFocus() - else - add_form_component() - end - end - filter_input = input -end -do - local input = gui.editbox(frame.filter) - input:SetPoint('LEFT', filter_dropdown, 'RIGHT', 10, 0) - input:SetWidth(150) - input:SetScript('OnTabPressed', function() - filter_input:SetFocus() - end) - input.char = function() this:complete() end - input.enter = add_form_component - input:Hide() - filter_parameter_input = input -end -do - local scroll_frame = CreateFrame('ScrollFrame', nil, frame.filter) - scroll_frame:SetWidth(395) - scroll_frame:SetHeight(270) - scroll_frame:SetPoint('TOPLEFT', 348.5, -50) - scroll_frame:EnableMouse(true) - scroll_frame:EnableMouseWheel(true) - scroll_frame:SetScript('OnMouseWheel', function() - local child = this:GetScrollChild() - child:SetFont('p', [[Fonts\ARIALN.TTF]], aux.bounded(gui.font_size.small, gui.font_size.large, aux.select(2, child:GetFont()) + arg1*2)) - update_filter_display() - end) - scroll_frame:RegisterForDrag('LeftButton') - scroll_frame:SetScript('OnDragStart', function() - this.x, this.y = GetCursorPosition() - this.x_offset, this.y_offset = this:GetHorizontalScroll(), this:GetVerticalScroll() - this.x_extra, this.y_extra = 0, 0 - this:SetScript('OnUpdate', function() - local x, y = GetCursorPosition() - local new_x_offset = this.x_offset + x - this.x - local new_y_offset = this.y_offset + y - this.y - - set_filter_display_offset(new_x_offset - this.x_extra, new_y_offset - this.y_extra) - - this.x_extra = max(this.x_extra, new_x_offset) - this.y_extra = min(this.y_extra, new_y_offset) - end) - end) - scroll_frame:SetScript('OnDragStop', function() - this:SetScript('OnUpdate', nil) - end) - gui.set_content_style(scroll_frame, -2, -2, -2, -2) - local scroll_child = CreateFrame('SimpleHTML', nil, scroll_frame) - scroll_frame:SetScrollChild(scroll_child) - scroll_child:SetFont('p', [[Fonts\ARIALN.TTF]], gui.font_size.large) - scroll_child:SetTextColor('p', aux.color.label.enabled()) - scroll_child:SetWidth(1) - scroll_child:SetHeight(1) - scroll_child:SetScript('OnHyperlinkClick', data_link_click) - scroll_child.measure = scroll_child:CreateFontString() - filter_display = scroll_child -end - -status_bars = {} -tables = {} -for _ = 1, 5 do - local status_bar = gui.status_bar(frame) - status_bar:SetAllPoints(status_bar_frame) - status_bar:Hide() - tinsert(status_bars, status_bar) - - local table = auction_listing.new(frame.results, 16, auction_listing.search_columns) - table:SetHandler('OnClick', function(row, button) - if IsAltKeyDown() then - if current_search().table:GetSelection().record == row.record then - if button == 'LeftButton' then - buyout_button:Click() - elseif button == 'RightButton' then - bid_button:Click() - end - end - elseif button == 'RightButton' then - aux.set_tab(1) - set_filter(strlower(info.item(this.record.item_id).name) .. '/exact') - execute(nil, false) - end - end) - table:SetHandler('OnSelectionChanged', function(rt, datum) - bid_button:Disable() - buyout_button:Disable() - if not datum then return end - find_auction(datum.record) - end) - table:Hide() - tinsert(tables, table) -end - -favorite_searches_listing = listing.new(frame.saved.favorite) -favorite_searches_listing:SetColInfo{{name='Auto', width=.07, align='CENTER'}, {name='Favorite Searches', width=.93}} - -recent_searches_listing = listing.new(frame.saved.recent) -recent_searches_listing:SetColInfo{{name='Recent Searches', width=1}} - -for listing in T.temp-T.set(favorite_searches_listing, recent_searches_listing) do - for k, v in handlers do - listing:SetHandler(k, v) - end -end \ No newline at end of file diff --git a/aux-addon/tabs/search/results.lua b/aux-addon/tabs/search/results.lua deleted file mode 100644 index 6f9e863..0000000 --- a/aux-addon/tabs/search/results.lua +++ /dev/null @@ -1,443 +0,0 @@ -module 'aux.tabs.search' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local filter_util = require 'aux.util.filter' -local scan_util = require 'aux.util.scan' -local scan = require 'aux.core.scan' - -search_scan_id = 0 - -function aux.handle.LOAD() - new_search() -end - -function update_real_time(enable) - if enable then - range_button:Hide() - real_time_button:Show() - search_box:SetPoint('LEFT', real_time_button, 'RIGHT', 4, 0) - else - real_time_button:Hide() - range_button:Show() - search_box:SetPoint('LEFT', last_page_input, 'RIGHT', 4, 0) - end -end - -do - local searches = {} - local search_index = 1 - - function current_search() - return searches[search_index] - end - - function update_search(index) - searches[search_index].status_bar:Hide() - searches[search_index].table:Hide() - searches[search_index].table:SetSelectedRecord() - - search_index = index - - searches[search_index].status_bar:Show() - searches[search_index].table:Show() - - search_box:SetText(searches[search_index].filter_string or '') - first_page_input:SetText(searches[search_index].first_page and searches[search_index].first_page + 1 or '') - last_page_input:SetText(searches[search_index].last_page and searches[search_index].last_page + 1 or '') - if search_index == 1 then - previous_button:Disable() - else - previous_button:Enable() - end - if search_index == getn(searches) then - next_button:Hide() - range_button:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) - real_time_button:SetPoint('LEFT', previous_button, 'RIGHT', 4, 0) - else - next_button:Show() - range_button:SetPoint('LEFT', next_button, 'RIGHT', 4, 0) - real_time_button:SetPoint('LEFT', next_button, 'RIGHT', 4, 0) - end - update_real_time(searches[search_index].real_time) - update_start_stop() - update_continuation() - end - - function new_search(filter_string, first_page, last_page, real_time) - while getn(searches) > search_index do - tremove(searches) - end - local search = T.map('records', T.acquire(), 'filter_string', filter_string, 'first_page', first_page, 'last_page', last_page, 'real_time', real_time) - tinsert(searches, search) - if getn(searches) > 5 then - tremove(searches, 1) - tinsert(status_bars, tremove(status_bars, 1)) - tinsert(tables, tremove(tables, 1)) - search_index = 4 - end - - search.status_bar = status_bars[getn(searches)] - search.status_bar:update_status(1, 1) - search.status_bar:set_text('') - - search.table = tables[getn(searches)] - search.table:SetSort(1, 2, 3, 4, 5, 6, 7, 8, 9) - search.table:Reset() - search.table:SetDatabase(search.records) - - update_search(getn(searches)) - end - - function clear_control_focus() - search_box:ClearFocus() - first_page_input:ClearFocus() - last_page_input:ClearFocus() - end - - function previous_search() - clear_control_focus() - update_search(search_index - 1) - set_subtab(RESULTS) - end - - function next_search() - clear_control_focus() - update_search(search_index + 1) - set_subtab(RESULTS) - end -end - -function update_continuation() - if current_search().continuation then - resume_button:Show() - search_box:SetPoint('RIGHT', resume_button, 'LEFT', -4, 0) - else - resume_button:Hide() - search_box:SetPoint('RIGHT', start_button, 'LEFT', -4, 0) - end -end - -function discard_continuation() - scan.abort(search_scan_id) - current_search().continuation = nil - update_continuation() -end - -function update_start_stop() - if current_search().active then - stop_button:Show() - start_button:Hide() - else - start_button:Show() - stop_button:Hide() - end -end - -function start_real_time_scan(query, search, continuation) - - local ignore_page - if not search then - search = current_search() - query.blizzard_query.first_page = tonumber(continuation) or 0 - query.blizzard_query.last_page = tonumber(continuation) or 0 - ignore_page = not tonumber(continuation) - end - - local next_page - local new_records = T.acquire() - search_scan_id = scan.start{ - type = 'list', - queries = {query}, - auto_buy_validator = search.auto_buy_validator, - auto_bid_validator = search.auto_bid_validator, - on_scan_start = function() - search.status_bar:update_status(.9999, .9999) - search.status_bar:set_text('Scanning last page ...') - end, - on_page_loaded = function(_, _, last_page) - next_page = last_page - if last_page == 0 then - ignore_page = false - end - end, - on_auction = function(auction_record) - if not ignore_page then - tinsert(new_records, auction_record) - end - end, - on_complete = function() - local map = T.temp-T.acquire() - for _, record in search.records do - map[record.sniping_signature] = record - end - for _, record in new_records do - map[record.sniping_signature] = record - end - T.release(new_records) - new_records = aux.values(map) - - if getn(new_records) > 2000 then - StaticPopup_Show('AUX_SEARCH_TABLE_FULL') - else - search.records = new_records - search.table:SetDatabase(search.records) - end - - query.blizzard_query.first_page = next_page - query.blizzard_query.last_page = next_page - start_real_time_scan(query, search) - end, - on_abort = function() - search.status_bar:update_status(1, 1) - search.status_bar:set_text('Scan paused') - - search.continuation = next_page or not ignore_page and query.blizzard_query.first_page or true - - if current_search() == search then - update_continuation() - end - - search.active = false - update_start_stop() - end, - } -end - -function start_search(queries, continuation) - local current_query, current_page, total_queries, start_query, start_page - - local search = current_search() - - total_queries = getn(queries) - - if continuation then - start_query, start_page = unpack(continuation) - for i = 1, start_query - 1 do - tremove(queries, 1) - end - queries[1].blizzard_query.first_page = (queries[1].blizzard_query.first_page or 0) + start_page - 1 - search.table:SetSelectedRecord() - else - start_query, start_page = 1, 1 - end - - - search_scan_id = scan.start{ - type = 'list', - queries = queries, - auto_buy_validator = search.auto_buy_validator, - auto_bid_validator = search.auto_bid_validator, - on_scan_start = function() - search.status_bar:update_status(0, 0) - if continuation then - search.status_bar:set_text('Resuming scan...') - else - search.status_bar:set_text('Scanning auctions...') - end - end, - on_page_loaded = function(_, total_scan_pages) - current_page = current_page + 1 - total_scan_pages = total_scan_pages + (start_page - 1) - total_scan_pages = max(total_scan_pages, 1) - current_page = min(current_page, total_scan_pages) - search.status_bar:update_status((current_query - 1) / getn(queries), current_page / total_scan_pages) - search.status_bar:set_text(format('Scanning %d / %d (Page %d / %d)', current_query, total_queries, current_page, total_scan_pages)) - end, - on_page_scanned = function() - search.table:SetDatabase() - end, - on_start_query = function(query) - current_query = current_query and current_query + 1 or start_query - current_page = current_page and 0 or start_page - 1 - end, - on_auction = function(auction_record, ctrl) - if getn(search.records) < 2000 then - tinsert(search.records, auction_record) - if getn(search.records) == 2000 then - StaticPopup_Show('AUX_SEARCH_TABLE_FULL') - end - end - end, - on_complete = function() - search.status_bar:update_status(1, 1) - search.status_bar:set_text('Scan complete') - - if current_search() == search and frame.results:IsVisible() and getn(search.records) == 0 then - set_subtab(SAVED) - end - - search.active = false - update_start_stop() - end, - on_abort = function() - search.status_bar:update_status(1, 1) - search.status_bar:set_text('Scan paused') - - if current_query then - search.continuation = {current_query, current_page + 1} - else - search.continuation = {start_query, start_page} - end - if current_search() == search then - update_continuation() - end - - search.active = false - update_start_stop() - end, - } -end - -function M.execute(resume, real_time) - - if resume then - real_time = current_search().real_time - elseif real_time == nil then - real_time = real_time_button:IsShown() - end - - if resume then - search_box:SetText(current_search().filter_string) - end - local filter_string, first_page, last_page = search_box:GetText(), blizzard_page_index(first_page_input:GetText()), blizzard_page_index(last_page_input:GetText()) - - local queries, error = filter_util.queries(filter_string) - if not queries then - aux.print('Invalid filter:', error) - return - elseif real_time then - if getn(queries) > 1 then - aux.print('Error: The real time mode does not support multi-queries') - return - elseif queries[1].blizzard_query.first_page or queries[1].blizzard_query.last_page then - aux.print('Error: The real time mode does not support page ranges') - return - end - end - - if resume then - current_search().table:SetSelectedRecord() - else - if filter_string ~= current_search().filter_string then - if current_search().filter_string then - new_search(filter_string, first_page, last_page, real_time) - else - current_search().filter_string = filter_string - end - new_recent_search(filter_string, aux.join(aux.map(aux.copy(queries), function(filter) return filter.prettified end), ';')) - else - local search = current_search() - search.records = T.acquire() - search.table:Reset() - search.table:SetDatabase(search.records) - end - local search = current_search() - search.first_page = first_page - search.last_page = last_page - search.real_time = real_time - search.auto_buy_validator = get_auto_buy_validator() - search.auto_bid_validator = get_auto_bid_validator() - end - - local continuation = resume and current_search().continuation - discard_continuation() - current_search().active = true - update_start_stop() - clear_control_focus() - set_subtab(RESULTS) - if real_time then - start_real_time_scan(queries[1], nil, continuation) - else - for _, query in queries do - query.blizzard_query.first_page = current_search().first_page - query.blizzard_query.last_page = current_search().last_page - end - start_search(queries, continuation) - end -end - -do - local scan_id = 0 - local IDLE, SEARCHING, FOUND = aux.enum(3) - local state = IDLE - local found_index - - function find_auction(record) - local search = current_search() - - if not search.table:ContainsRecord(record) or info.is_player(record.owner) then - return - end - - scan.abort(scan_id) - state = SEARCHING - scan_id = scan_util.find( - record, - current_search().status_bar, - function() - state = IDLE - end, - function() - state = IDLE - search.table:RemoveAuctionRecord(record) - end, - function(index) - if search.table:GetSelection() and search.table:GetSelection().record ~= record then - return - end - - state = FOUND - found_index = index - - if not record.high_bidder then - bid_button:SetScript('OnClick', function() - if scan_util.test(record, index) and search.table:ContainsRecord(record) then - aux.place_bid('list', index, record.bid_price, record.bid_price < record.buyout_price and function() - info.bid_update(record) - search.table:SetDatabase() - end or function() search.table:RemoveAuctionRecord(record) end) - end - end) - bid_button:Enable() - else - bid_button:Disable() - end - - if record.buyout_price > 0 then - buyout_button:SetScript('OnClick', function() - if scan_util.test(record, index) and search.table:ContainsRecord(record) then - aux.place_bid('list', index, record.buyout_price, function() search.table:RemoveAuctionRecord(record) end) - end - end) - buyout_button:Enable() - else - buyout_button:Disable() - end - end - ) - end - - function on_update() - if state == IDLE or state == SEARCHING then - buyout_button:Disable() - bid_button:Disable() - end - - if state == SEARCHING then return end - - local selection = current_search().table:GetSelection() - if not selection then - state = IDLE - elseif selection and state == IDLE then - find_auction(selection.record) - elseif state == FOUND and not scan_util.test(selection.record, found_index) then - buyout_button:Disable() - bid_button:Disable() - if not aux.bid_in_progress() then - state = IDLE - end - end - end -end \ No newline at end of file diff --git a/aux-addon/tabs/search/saved.lua b/aux-addon/tabs/search/saved.lua deleted file mode 100644 index 8089e79..0000000 --- a/aux-addon/tabs/search/saved.lua +++ /dev/null @@ -1,181 +0,0 @@ -module 'aux.tabs.search' - -local T = require 'T' -local aux = require 'aux' -local filter_util = require 'aux.util.filter' -local gui = require 'aux.gui' - -function aux.handle.LOAD() - recent_searches, favorite_searches = aux.realm_data.recent_searches, aux.realm_data.favorite_searches -end - -function update_search_listings() - local favorite_search_rows = T.acquire() - for i = 1, getn(favorite_searches) do - local search = favorite_searches[i] - local name = strsub(search.prettified, 1, 250) - tinsert(favorite_search_rows, T.map( - 'cols', T.list(T.map('value', search.auto_buy and aux.color.red'X' or search.auto_bid and aux.color.red'Y' or ''), T.map('value', name)), - 'search', search, - 'index', i - )) - end - favorite_searches_listing:SetData(favorite_search_rows) - - local recent_search_rows = T.acquire() - for i = 1, getn(recent_searches) do - local search = recent_searches[i] - local name = strsub(search.prettified, 1, 250) - tinsert(recent_search_rows, T.map( - 'cols', T.list(T.map('value', name)), - 'search', search, - 'index', i - )) - end - recent_searches_listing:SetData(recent_search_rows) -end - -function new_recent_search(filter_string, prettified) - for i = getn(recent_searches), 1, -1 do - if recent_searches[i].filter_string == filter_string then - tremove(recent_searches, i) - end - end - tinsert(recent_searches, 1, T.map( - 'filter_string', filter_string, - 'prettified', prettified - )) - while getn(recent_searches) > 50 do - tremove(recent_searches) - end - update_search_listings() -end - -handlers = { - OnClick = function(st, data, _, button) - if not data then return end - if button == 'LeftButton' and IsShiftKeyDown() then - set_filter(data.search.filter_string) - elseif button == 'RightButton' and IsShiftKeyDown() then - add_filter(data.search.filter_string) - elseif button == 'LeftButton' then - set_filter(data.search.filter_string) - execute() - elseif button == 'RightButton' then - local u = update_search_listings - if st == recent_searches_listing then - tinsert(favorite_searches, 1, data.search) - u(d) - elseif st == favorite_searches_listing then - local auto_buy = data.search.auto_buy - local auto_bid = data.search.auto_bid - gui.menu( - (auto_buy and 'Disable' or 'Enable') .. ' Auto Buy', function() if auto_buy then data.search.auto_buy = nil else enable_auto_buy(data.search) end u() end, - (auto_bid and 'Disable' or 'Enable') .. ' Auto Bid', function() if auto_bid then data.search.auto_bid = nil else enable_auto_bid(data.search) end u() end, - 'Move Up', function() move_up(favorite_searches, data.index); u() end, - 'Move Down', function() move_down(favorite_searches, data.index); u() end, - 'Delete', function() tremove(favorite_searches, data.index); u() end - ) - end - end - end, - OnEnter = function(st, data, self) - if not data then return end - GameTooltip_SetDefaultAnchor(GameTooltip, UIParent) - GameTooltip:AddLine(gsub(data.search.prettified, ';', '\n\n'), 255/255, 254/255, 250/255, true) - GameTooltip:Show() - end, - OnLeave = function() - GameTooltip:ClearLines() - GameTooltip:Hide() - end -} - -function get_auto_buy_validator() - local validators = T.acquire() - for _, search in favorite_searches do - if search.auto_buy then - local queries, error = filter_util.queries(search.filter_string) - if queries then - tinsert(validators, queries[1].validator) - else - aux.print('Invalid auto buy filter:', error) - end - end - end - return function(record) - return aux.any(validators, function(validator) return validator(record) end) - end -end - -function get_auto_bid_validator() - local validators = T.acquire() - for _, search in favorite_searches do - if search.auto_bid then - local queries, error = filter_util.queries(search.filter_string) - if queries then - tinsert(validators, queries[1].validator) - else - aux.print('Invalid auto bid filter:', error) - end - end - end - return function(record) - return aux.any(validators, function(validator) return validator(record) end) - end -end - -function add_favorite(filter_string) - local queries, error = filter_util.queries(filter_string) - if queries then - tinsert(favorite_searches, 1, T.map( - 'filter_string', filter_string, - 'prettified', aux.join(aux.map(queries, function(query) return query.prettified end), ';') - )) - update_search_listings() - else - aux.print('Invalid filter:', error) - end -end - -function enable_auto_buy(search) - local queries, error = filter_util.queries(search.filter_string) - if queries then - if getn(queries) > 1 then - aux.print('Error: Auto Buy does not support multi-queries') - elseif aux.size(queries[1].blizzard_query) > 0 and not filter_util.parse_filter_string(search.filter_string).blizzard.exact then - aux.print('Error: Auto Buy does not support Blizzard filters') - else - search.auto_buy = true - end - else - aux.print('Invalid filter:', error) - end -end - -function enable_auto_bid(search) - local queries, error = filter_util.queries(search.filter_string) - if queries then - if getn(queries) > 1 then - aux.print('Error: Auto Bid does not support multi-queries') - elseif aux.size(queries[1].blizzard_query) > 0 and not filter_util.parse_filter_string(search.filter_string).blizzard.exact then - aux.print('Error: Auto Bid does not support Blizzard filters') - else - search.auto_bid = true - end - else - aux.print('Invalid filter:', error) - end -end - -function move_up(list, index) - if list[index - 1] then - list[index], list[index - 1] = list[index - 1], list[index] - end -end - -function move_down(list, index) - if list[index + 1] then - list[index], list[index + 1] = list[index + 1], list[index] - end -end \ No newline at end of file diff --git a/aux-addon/util.lua b/aux-addon/util.lua deleted file mode 100644 index 9de2094..0000000 --- a/aux-addon/util.lua +++ /dev/null @@ -1,188 +0,0 @@ -module 'aux' - -local T = require 'T' - -M.immutable = setmetatable(T.acquire(), { - __metatable = false, - __newindex = pass, - __sub = function(_, t) - return setmetatable(T.acquire(), T.map('__metatable', false, '__newindex', pass, '__index', t)) - end -}) - -function M.assign(t1, t2) - for k, v in t2 do - if t1[k] == nil then - t1[k] = v - end - end - return t1 -end - -function M.enum(n) - if n > 0 then return immutable-{}, enum(n - 1) end -end - -M.select = T.vararg-function(arg) - for _ = 1, arg[1] do - tremove(arg, 1) - end - if getn(arg) == 0 then - return nil - else - return unpack(arg) - end -end - -M.join = table.concat - -M.index = T.vararg-function(arg) - local t = tremove(arg, 1) - for _, v in ipairs(arg) do - t = t and t[v] - end - return t -end - -M.huge = 1/0 - -function M.modified() - return IsShiftKeyDown() or IsControlKeyDown() or IsAltKeyDown() -end - -function M.copy(t) - local copy = T.acquire() - for k, v in t do - copy[k] = v - end - table.setn(copy, getn(t)) - return setmetatable(copy, getmetatable(t)) -end - -function M.size(t) - local size = 0 - for _ in t do - size = size + 1 - end - return size -end - -function M.key(t, value) - for k, v in t do - if v == value then - return k - end - end -end - -function M.keys(t) - local keys = T.acquire() - for k in t do - tinsert(keys, k) - end - return keys -end - -function M.values(t) - local values = T.acquire() - for _, v in t do - tinsert(values, v) - end - return values -end - -function M.eq(t1, t2) - if not t1 or not t2 then return false end - for key, value in t1 do - if t2[key] ~= value then return false end - end - for key, value in t2 do - if t1[key] ~= value then return false end - end - return true -end - -function M.any(t, predicate) - for _, v in t do - if predicate then - if predicate(v) then return true end - elseif v then - return true - end - end - return false -end - -function M.all(t, predicate) - for _, v in t do - if predicate then - if not predicate(v) then return false end - elseif not v then - return false - end - end - return true -end - -function M.filter(t, predicate) - for k, v in t do - if not predicate(v, k) then t[k] = nil end - end - return t -end - -function M.map(t, f) - for k, v in t do - t[k] = f(v, k) - end - return t -end - -function M.trim(str) - return gsub(str, '^%s*(.-)%s*$', '%1') -end - -function M.split(str, separator) - local parts = T.acquire() - while true do - local start_index = strfind(str, separator, 1, true) - if start_index then - local part = strsub(str, 1, start_index - 1) - tinsert(parts, part) - str = strsub(str, start_index + 1) - else - local part = strsub(str, 1) - tinsert(parts, part) - return parts - end - end -end - -function M.tokenize(str) - local tokens = T.acquire() - for token in string.gfind(str, '%S+') do tinsert(tokens, token) end - return tokens -end - -function M.bounded(lower_bound, upper_bound, number) - return max(lower_bound, min(upper_bound, number)) -end - -function M.round(x) - return floor(x + .5) -end - -function M.later(t, t0) - t0 = t0 or GetTime() - return function() return GetTime() - t0 > t end -end - -function M.signal() - local params - return T.vararg-function(arg) - T.static(arg) - params = arg - end, function() - return params - end -end \ No newline at end of file diff --git a/aux-addon/util/completion.lua b/aux-addon/util/completion.lua deleted file mode 100644 index 40b9b78..0000000 --- a/aux-addon/util/completion.lua +++ /dev/null @@ -1,45 +0,0 @@ -module 'aux.util.completion' - -local aux = require 'aux' -local filter_util = require 'aux.util.filter' - -function M:complete_filter() - if IsControlKeyDown() then -- TODO problem is ctrl-v, maybe find a better solution - return - end - - local filter_string = this:GetText() - - local completed_filter_string = aux.select(3, strfind(filter_string, '([^;]*)/[^/;]*$')) - local _, suggestions = filter_util.query(completed_filter_string) - - local start_index, _, current_modifier = strfind(filter_string, '([^/;]*)$') - current_modifier = current_modifier or '' - - for _, suggestion in ipairs(suggestions) do - if strsub(strupper(suggestion), 1, strlen(current_modifier)) == strupper(current_modifier) then - this:SetText(strlower(strsub(filter_string, 1, start_index - 1) .. suggestion)) - this:HighlightText(strlen(filter_string), -1) - return - end - end -end - -function M.complete(candidates) - return function(self) - if IsControlKeyDown() then -- TODO problem is ctrl-v, maybe find a better solution - return - end - - local text = self:GetText() - - local t = candidates() - for i = 1, getn(t) do - if strsub(strupper(t[i]), 1, strlen(text)) == strupper(text) then - self:SetText(strlower(t[i])) - self:HighlightText(strlen(text), -1) - return - end - end - end -end \ No newline at end of file diff --git a/aux-addon/util/filter.lua b/aux-addon/util/filter.lua deleted file mode 100644 index e997192..0000000 --- a/aux-addon/util/filter.lua +++ /dev/null @@ -1,590 +0,0 @@ -module 'aux.util.filter' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local money = require 'aux.util.money' -local filter_util = require 'aux.util.filter' -local history = require 'aux.core.history' -local disenchant = require 'aux.core.disenchant' - -function default_filter(str) - return { - input_type = '', - validator = function() - return function(auction_record) - return aux.any(auction_record.tooltip, function(entry) - return strfind(strlower(entry.left_text or ''), str, 1, true) or strfind(strlower(entry.right_text or ''), str, 1, true) - end) - end - end, - } -end - -M.filters = { - - ['utilizable'] = { - input_type = '', - validator = function() - return function(auction_record) - return auction_record.usable and not info.tooltip_match(ITEM_SPELL_KNOWN, auction_record.tooltip) - end - end, - }, - - ['tooltip'] = { - input_type = 'string', - validator = function(str) - return default_filter(str).validator() - end, - }, - - ['item'] = { - input_type = 'string', - validator = function(name) - return function(auction_record) - return strlower(info.item(auction_record.item_id).name) == name - end - end - }, - - ['left'] = { - input_type = T.list('30m', '2h', '8h', '24h'), - validator = function(index) - return function(auction_record) - return auction_record.duration == index - end - end - }, - - ['rarity'] = { - input_type = T.list('poor', 'common', 'uncommon', 'rare', 'epic'), - validator = function(index) - return function(auction_record) - return auction_record.quality == index - 1 - end - end - }, - - ['min-level'] = { - input_type = 'number', - validator = function(level) - return function(auction_record) - return auction_record.level >= level - end - end - }, - - ['max-level'] = { - input_type = 'number', - validator = function(level) - return function(auction_record) - return auction_record.level <= level - end - end - }, - - ['bid-price'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - return auction_record.unit_bid_price <= amount - end - end - }, - - ['price'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - return auction_record.buyout_price > 0 and auction_record.unit_buyout_price <= amount - end - end - }, - - ['bid-percent'] = { - input_type = 'number', - validator = function(pct) - return function(auction_record) - return history.value(auction_record.item_key) and auction_record.unit_bid_price / history.value(auction_record.item_key) * 100 <= pct - end - end - }, - - ['bid-disenchant-percent'] = { - input_type = 'number', - validator = function(pct) - return function(auction_record) - return disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) - and auction_record.unit_bid_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct - end - end - }, - - ['percent'] = { - input_type = 'number', - validator = function(pct) - return function(auction_record) - return auction_record.unit_buyout_price > 0 - and history.value(auction_record.item_key) - and auction_record.unit_buyout_price / history.value(auction_record.item_key) * 100 <= pct - end - end - }, - - ['disenchant-percent'] = { - input_type = 'number', - validator = function(pct) - return function(auction_record) - return auction_record.unit_buyout_price > 0 - and disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) - and auction_record.unit_buyout_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct - end - end - }, - - ['bid-profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - return history.value(auction_record.item_key) and history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.bid_price >= amount - end - end - }, - - ['profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - return auction_record.buyout_price > 0 and history.value(auction_record.item_key) and history.value(auction_record.item_key) * auction_record.aux_quantity - auction_record.buyout_price >= amount - end - end - }, - - ['bid-disenchant-profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) - return disenchant_value and disenchant_value - auction_record.bid_price >= amount - end - end - }, - - ['disenchant-profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) - return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount - end - end - }, - - ['bid-vendor-profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - local vendor_price = info.merchant_info(auction_record.item_id) - if not vendor_price then - vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] - if vendor_price then - local charges = 1 - if info.max_item_charges(auction_record.item_id) ~= nil then - info.charges=info.max_item_charges(auction_record.item_id) - end - vendor_price= vendor_price/ charges - end - end - return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount - end - end - }, - - ['vendor-profit'] = { - input_type = 'money', - validator = function(amount) - return function(auction_record) - local vendor_price = info.merchant_info(auction_record.item_id) - if not vendor_price then - vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] - if vendor_price then - local charges = 1 - if info.max_item_charges(auction_record.item_id) ~= nil then - info.charges=info.max_item_charges(auction_record.item_id) - end - vendor_price= vendor_price/ charges - end - end - return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount - end - end - }, - - ['seller'] = { - input_type = 'string', - validator = function(name) - return function(auction_record) - return auction_record.owner and strupper(name) == strupper(auction_record.owner) - end - end - }, -} - -function operator(str) - local operator = str == 'not' and T.list('operator', 'not', 1) - for name in T.temp-T.set('and', 'or') do - local arity = aux.select(3, strfind(str, '^' .. name .. '(%d*)$')) - if arity then - arity = tonumber(arity) - operator = not (arity and arity < 2) and T.list('operator', name, arity) - end - end - return operator or nil -end - -do - local mt = { - __call = function(self, str, i) - if not str then - self.max_level = self.max_level or self.min_level - return self - end - if self.exact then return end - local number = tonumber(aux.select(3, strfind(str, '^(%d+)$'))) - if number then - if number >= 1 and number <= 60 then - for _, key in ipairs(T.temp-T.list('min_level', 'max_level')) do - if not self[key] then - self[key] = T.list(str, number) - return T.list('blizzard', key, str, number) - end - end - end - end - for _, parser in T.temp-T.list( - T.temp-T.list('class', info.item_class_index), - T.temp-T.list('subclass', T.vararg-function(arg) return info.item_subclass_index(aux.index(self.class, 2) or 0, unpack(arg)) end), - T.temp-T.list('slot', T.vararg-function(arg) return info.item_slot_index(aux.index(self.class, 2) == 2 and 2 or 0, aux.index(self.subclass, 2) or 0, unpack(arg)) end), - T.temp-T.list('quality', info.item_quality_index) - ) do - if not self[parser[1]] then - tinsert(parser, str) - local index, label = parser[2](aux.select(3, unpack(parser))) - if index then - self[parser[1]] = T.list(label, index) - return T.list('blizzard', parser[1], label, index) - end - end - end - if not self[str] and (str == 'usable' or str == 'exact' and self.name and aux.size(self) == 1) then - self[str] = T.list(str, 1) - return T.list('blizzard', str, str, 1) - elseif i == 1 and strlen(str) <= 63 then - self.name = unquote(str) - return T.list('blizzard', 'name', unquote(str), str) --- return nil, 'The name filter must not be longer than 63 characters' TODO - end - end, - } - function blizzard_filter_parser() - return setmetatable(T.acquire(), mt) - end -end - -function parse_parameter(input_type, str) - if input_type == 'money' then - local money = money.from_string(str) - return money and money > 0 and money or nil - elseif input_type == 'number' then - local number = tonumber(str) - return number and number > 0 and mod(number, 1) == 0 and number or nil - elseif input_type == 'string' then - return str ~= '' and str or nil - elseif type(input_type) == 'table' then - return aux.key(input_type, str) - end -end - -function M.parse_filter_string(str) - local filter, post_filter = T.acquire(), T.acquire() - local blizzard_filter_parser = blizzard_filter_parser() - - local parts = str and aux.map(aux.split(str, '/'), function(part) return strlower(aux.trim(part)) end) or T.acquire() - - local i = 1 - while parts[i] do - local operator = operator(parts[i]) - if operator then - tinsert(post_filter, operator) - tinsert(filter, operator) - elseif filters[parts[i]] then - local input_type = filters[parts[i]].input_type - if input_type ~= '' then - if not parts[i + 1] or not parse_parameter(input_type, parts[i + 1]) then - if parts[i] == 'item' then - return nil, 'Invalid item name', aux.account_data.auctionable_items - elseif type(input_type) == 'table' then - return nil, 'Invalid choice for ' .. parts[i], input_type - else - return nil, 'Invalid input for ' .. parts[i] .. '. Expecting: ' .. input_type - end - end - tinsert(post_filter, T.list('filter', parts[i], parts[i + 1])) - i = i + 1 - else - tinsert(post_filter, T.list('filter', parts[i])) - end - tinsert(filter, post_filter[getn(post_filter)]) - else - local part = blizzard_filter_parser(parts[i], i) - if part then - tinsert(filter, part) - elseif parts[i] ~= '' then - tinsert(post_filter, T.list('filter', 'tooltip', parts[i])) - tinsert(filter, post_filter[getn(post_filter)]) - else - return nil, 'Empty modifier' - end - end - i = i + 1 - end - - return T.map('components', filter, 'blizzard', blizzard_filter_parser(), 'post', post_filter) -end - -function M.query(filter_string) - local filter, error, suggestions = parse_filter_string(filter_string) - - if not filter then - return nil, suggestions or T.acquire(), error - end - - local polish_notation_counter = 0 - for _, component in ipairs(filter.post) do - if component[1] == 'operator' then - polish_notation_counter = max(polish_notation_counter, 1) - polish_notation_counter = polish_notation_counter + (tonumber(component[2]) or 1) - 1 - elseif component[1] == 'filter' then - polish_notation_counter = polish_notation_counter - 1 - end - end - - if polish_notation_counter > 0 then - local suggestions = T.acquire() - for key in filters do - tinsert(suggestions, strlower(key)) - end - tinsert(suggestions, 'and') - tinsert(suggestions, 'or') - tinsert(suggestions, 'not') - return nil, suggestions, 'Malformed expression' - end - - return { - blizzard_query = blizzard_query(filter), - validator = validator(filter), - prettified = prettified_filter_string(filter), - }, _M.suggestions(filter) -end - -function M.queries(filter_string) - local parts = aux.split(filter_string, ';') - local queries = T.acquire() - for _, str in ipairs(parts) do - local query, _, error = query(str) - if not query then - return nil, error - else - tinsert(queries, query) - end - end - return queries -end - -function suggestions(filter) - local suggestions = T.acquire() - - if filter.blizzard.name and aux.size(filter.blizzard) == 1 then tinsert(suggestions, 'exact') end - - tinsert(suggestions, 'and'); tinsert(suggestions, 'or'); tinsert(suggestions, 'not'); tinsert(suggestions, 'tooltip') - - for key in filters do tinsert(suggestions, key) end - - -- classes - if not filter.blizzard.class then - for _, class in ipairs(T.temp-T.list(GetAuctionItemClasses())) do tinsert(suggestions, class) end - end - - -- subclasses - if not filter.blizzard.subclass then - for _, subclass in ipairs(T.temp-T.list(GetAuctionItemSubClasses(aux.index(filter.blizzard.class, 2) or 0))) do - tinsert(suggestions, subclass) - end - end - - -- slots - if not filter.blizzard.slot then - for _, invtype in ipairs(T.temp-T.list(GetAuctionInvTypes(aux.index(filter.blizzard.class, 2) == 2 and 2 or 0, aux.index(filter.blizzard.subclass, 2) or 0))) do - tinsert(suggestions, _G[invtype]) - end - end - - -- usable - if not filter.blizzard.usable then tinsert(suggestions, 'usable') end - - -- rarities - if not filter.blizzard.quality then - for i = 0, 4 do tinsert(suggestions, _G['ITEM_QUALITY' .. i .. '_DESC']) end - end - - -- item names - if getn(filter.components) == 0 then - for _, name in ipairs(aux.account_data.auctionable_items) do - tinsert(suggestions, name .. '/exact') - end - end - - return suggestions -end - -function M.filter_string(components) - local query_builder = query_builder() - for _, component in ipairs(components) do - if component[1] == 'blizzard' then - query_builder.append(component[4] or component[3]) - elseif component[1] == 'operator' then - query_builder.append(component[2] .. (component[2] ~= 'not' and tonumber(component[3]) or '')) - elseif component[1] == 'filter' then - query_builder.append(component[2]) - local parameter = component[3] - if parameter then - if filter_util.filters[component[2]].input_type == 'money' then - parameter = money.to_string(money.from_string(parameter), nil, true, nil, true) - end - query_builder.append(parameter) - end - end - end - return query_builder.get() -end - -function prettified_filter_string(filter) - local prettified = query_builder() - for i, component in ipairs(filter.components) do - if component[1] == 'blizzard' then - if component[2] == 'name' then - if filter.blizzard.exact then - prettified.append(info.display_name(info.item_id(component[3])) or aux.color.orange('[' .. component[3] .. ']')) - elseif component[3] ~= '' then - prettified.append(aux.color.label.enabled(component[3])) - end - elseif component[2] ~= 'exact' then - prettified.append(aux.color.orange(component[3])) - end - elseif component[1] == 'operator' then - prettified.append(aux.color.orange(component[2] .. (component[2] ~= 'not' and tonumber(component[3]) or ''))) - elseif component[1] == 'filter' then - if i == 1 or component[2] ~= 'tooltip' then - prettified.append(aux.color.orange(component[2])) - end - local parameter = component[3] - if parameter then - if component[2] == 'item' then - prettified.append(info.display_name(info.item_id(parameter)) or aux.color.label.enabled('[' .. parameter .. ']')) - else - if filters[component[2]].input_type == 'money' then - prettified.append(money.to_string(money.from_string(parameter), nil, true, aux.color.label.enabled)) - else - prettified.append(aux.color.label.enabled(parameter)) - end - end - end - end - end - if prettified.get() == '' then - return aux.color.orange'<>' - else - return prettified.get() - end -end - -function M.quote(name) - return '<' .. name .. '>' -end - -function M.unquote(name) - return aux.select(3, strfind(name, '^<(.*)>$')) or name -end - -function blizzard_query(filter) - local filters = filter.blizzard - local query = T.map('name', filters.name) - local item_id = filters.name and info.item_id(filters.name) - local item_info = item_id and info.item(item_id) - if filters.exact and item_info then - local class_index = info.item_class_index(item_info.class) - local subclass_index = info.item_subclass_index(class_index or 0, item_info.subclass) - local slot_index = info.item_slot_index(class_index or 0, subclass_index or 0, item_info.slot) - query.min_level = item_info.level - query.max_level = item_info.level - query.usable = item_info.usable - query.class = class_index - query.subclass = subclass_index - query.slot = slot_index - query.quality = item_info.quality - else - for key in T.temp-T.set('min_level', 'max_level', 'class', 'subclass', 'slot', 'usable', 'quality') do - query[key] = aux.index(filters[key], 2) - end - end - return query -end - -function validator(filter) - local validators = T.acquire() - for i, component in filter.post do - local type, name, param = unpack(component) - if type == 'filter' then - validators[i] = filters[name].validator(parse_parameter(filters[name].input_type, param)) - end - end - return function(record) - if filter.blizzard.exact and strlower(info.item(record.item_id).name) ~= filter.blizzard.name then - return false - end - local stack = T.temp-T.acquire() - for i = getn(filter.post), 1, -1 do - local type, name, param = unpack(filter.post[i]) - if type == 'operator' then - local args = T.temp-T.acquire() - while (not param or param > 0) and getn(stack) > 0 do - tinsert(args, tremove(stack)) - param = param and param - 1 - end - if name == 'not' then - tinsert(stack, not args[1]) - elseif name == 'and' then - tinsert(stack, aux.all(args)) - elseif name == 'or' then - tinsert(stack, aux.any(args)) - end - elseif type == 'filter' then - tinsert(stack, not not validators[i](record)) - end - end - return aux.all(stack) - end -end - -function M.query_builder() - local filter - return T.map( - 'append', function(part) - filter = not filter and part or filter .. '/' .. part - end, - 'get', function() - return filter or '' - end - ) -end \ No newline at end of file diff --git a/aux-addon/util/info.lua b/aux-addon/util/info.lua deleted file mode 100644 index 76693dc..0000000 --- a/aux-addon/util/info.lua +++ /dev/null @@ -1,443 +0,0 @@ -module 'aux.util.info' - -local T = require 'T' -local aux = require 'aux' - -CreateFrame('GameTooltip', 'AuxTooltip', nil, 'GameTooltipTemplate') -AuxTooltip:SetScript('OnTooltipAddMoney', function() - this.money = arg1 -end) - -do - local inventory_index_map = { - INVTYPE_AMMO = {0}, - INVTYPE_HEAD = {1}, - INVTYPE_NECK = {2}, - INVTYPE_SHOULDER = {3}, - INVTYPE_BODY = {4}, - INVTYPE_CHEST = {5}, - INVTYPE_ROBE = {5}, - INVTYPE_WAIST = {6}, - INVTYPE_LEGS = {7}, - INVTYPE_FEET = {8}, - INVTYPE_WRIST = {9}, - INVTYPE_HAND = {10}, - INVTYPE_FINGER = {11, 12}, - INVTYPE_TRINKET = {13, 14}, - INVTYPE_CLOAK = {15}, - INVTYPE_2HWEAPON = {16, 17}, - INVTYPE_WEAPONMAINHAND = {16, 17}, - INVTYPE_WEAPON = {16, 17}, - INVTYPE_WEAPONOFFHAND = {16, 17}, - INVTYPE_HOLDABLE = {16, 17}, - INVTYPE_SHIELD = {16, 17}, - INVTYPE_RANGED = {18}, - INVTYPE_RANGEDRIGHT = {18}, - INVTYPE_TABARD = {19}, - } - function M.inventory_index(slot) - return unpack(inventory_index_map[slot] or T.temp-T.acquire()) - end -end - -function M.container_item(bag, slot) - local link = GetContainerItemLink(bag, slot) - if link then - local item_id, suffix_id, unique_id, enchant_id = parse_link(link) - local item_info = T.temp-item(item_id, suffix_id, unique_id, enchant_id) - - local texture, count, locked, quality, readable, lootable = GetContainerItemInfo(bag, slot) -- quality not working? - local tooltip, tooltip_money = tooltip('bag', bag, slot) - local max_charges = max_item_charges(item_id) - local charges = max_charges and item_charges(tooltip) - local aux_quantity = charges or count - return T.map( - 'item_id', item_id, - 'suffix_id', suffix_id, - 'unique_id', unique_id, - 'enchant_id', enchant_id, - - 'link', link, - 'itemstring', item_info.itemstring, - 'item_key', item_id .. ':' .. suffix_id, - - 'name', item_info.name, - 'texture', texture, - 'level', item_info.level, - 'type', item_info.type, - 'subtype', item_info.subtype, - 'slot', item_info.slot, - 'quality', item_info.quality, - 'max_stack', item_info.max_stack, - - 'count', count, - 'locked', locked, - 'readable', readable, - 'lootable', lootable, - - 'tooltip', tooltip, - 'tooltip_money', tooltip_money, - 'max_charges', max_charges, - 'charges', charges, - 'aux_quantity', aux_quantity - ) - end -end - -function M.auction_sell_item() - for name, texture, count, quality, usable, vendor_price in GetAuctionSellItemInfo do - return T.map( - 'name', name, - 'texture', texture, - 'quality', quality, - 'count', count, - 'usable', usable, - 'vendor_price', vendor_price -- it seems for charge items this is always the price for full charges - ) - end -end - -function M.auction(index, query_type) - query_type = query_type or 'list' - - local link = GetAuctionItemLink(query_type, index) - if link then - local item_id, suffix_id, unique_id, enchant_id = parse_link(link) - local item_info = T.temp-item(item_id, suffix_id, unique_id, enchant_id) - - local name, texture, count, quality, usable, level, start_price, min_increment, buyout_price, high_bid, high_bidder, owner, sale_status = GetAuctionItemInfo(query_type, index) - - local duration = GetAuctionItemTimeLeft(query_type, index) - local tooltip, tooltip_money = tooltip('auction', query_type, index) - local max_charges = max_item_charges(item_id) - local charges = max_charges and item_charges(tooltip) - local aux_quantity = charges or count - local blizzard_bid = high_bid > 0 and high_bid or start_price - local bid_price = high_bid > 0 and (high_bid + min_increment) or start_price - - return T.map( - 'item_id', item_id, - 'suffix_id', suffix_id, - 'unique_id', unique_id, - 'enchant_id', enchant_id, - - 'link', link, - 'itemstring', item_info.itemstring, - 'item_key', item_id .. ':' .. suffix_id, - 'search_signature', aux.join(T.temp-T.list(item_id, suffix_id, enchant_id, start_price, buyout_price, bid_price, aux_quantity, duration, query_type == 'owner' and high_bidder or (high_bidder and 1 or 0), aux.account_data.ignore_owner and (is_player(owner) and 0 or 1) or (owner or '?')), ':'), - 'sniping_signature', aux.join(T.temp-T.list(item_id, suffix_id, enchant_id, start_price, buyout_price, aux_quantity, aux.account_data.ignore_owner and (is_player(owner) and 0 or 1) or (owner or '?')), ':'), - - 'name', name, - 'texture', texture, - 'level', item_info.level, - 'type', item_info.type, - 'subtype', item_info.subtype, - 'slot', item_info.slot, - 'quality', quality, - 'max_stack', item_info.max_stack, - - 'count', count, - 'start_price', start_price, - 'high_bid', high_bid, - 'min_increment', min_increment, - 'blizzard_bid', blizzard_bid, - 'bid_price', bid_price, - 'buyout_price', buyout_price, - 'unit_blizzard_bid', blizzard_bid / aux_quantity, - 'unit_bid_price', bid_price / aux_quantity, - 'unit_buyout_price', buyout_price / aux_quantity, - 'high_bidder', high_bidder, - 'owner', owner, - 'sale_status', sale_status, - 'duration', duration, - 'usable', usable, - - 'tooltip', tooltip, - 'tooltip_money', tooltip_money, - 'max_charges', max_charges, - 'charges', charges, - 'aux_quantity', aux_quantity - ) - end -end - -function M.bid_update(auction_record) - auction_record.high_bid = auction_record.bid_price - auction_record.blizzard_bid = auction_record.bid_price - auction_record.min_increment = max(1, floor(auction_record.bid_price / 100) * 5) - auction_record.bid_price = auction_record.bid_price + auction_record.min_increment - auction_record.unit_blizzard_bid = auction_record.blizzard_bid / auction_record.aux_quantity - auction_record.unit_bid_price = auction_record.bid_price / auction_record.aux_quantity - auction_record.high_bidder = 1 - auction_record.search_signature = aux.join(T.temp-T.list(auction_record.item_id, auction_record.suffix_id, auction_record.enchant_id, auction_record.start_price, auction_record.buyout_price, auction_record.bid_price, auction_record.aux_quantity, auction_record.duration, 1, aux.account_data.ignore_owner and (is_player(auction_record.owner) and 0 or 1) or (auction_record.owner or '?')), ':') -end - -function M.set_tooltip(itemstring, owner, anchor) - GameTooltip:SetOwner(owner, anchor) - GameTooltip:SetHyperlink(itemstring) -end - -function M.set_shopping_tooltip(slot) - local index1, index2 = inventory_index(slot) - local tooltips = T.temp-T.acquire() - if index1 then - local tooltip = tooltip('inventory', 'player', index1) - if getn(tooltip) > 0 then - tinsert(tooltips, tooltip) - end - end - if index2 then - local tooltip = tooltip('inventory', 'player', index2) - if getn(tooltip) > 0 then - tinsert(tooltips, tooltip) - end - end - - if tooltips[1] then - tinsert(tooltips[1], 1, T.temp-T.map('left_text', 'Currently Equipped', 'left_color', T.temp-T.list(.5, .5, .5))) - ShoppingTooltip1:SetOwner(GameTooltip, 'ANCHOR_NONE') - ShoppingTooltip1:SetPoint('TOPLEFT', GameTooltip, 'TOPRIGHT', 0, -10) - load_tooltip(ShoppingTooltip1, tooltips[1]) - end - - if tooltips[2] then - tinsert(tooltips[2], 1, T.temp-T.map('left_text', 'Currently Equipped', 'left_color', T.temp-T.list(.5, .5, .5))) - ShoppingTooltip2:SetOwner(ShoppingTooltip1, 'ANCHOR_NONE') - ShoppingTooltip2:SetPoint('TOPLEFT', ShoppingTooltip1, 'TOPRIGHT') - load_tooltip(ShoppingTooltip2, tooltips[2]) - end -end - -function M.tooltip_match(entry, tooltip) - return aux.any(tooltip, function(line) - local left_match = line.left_text and strupper(line.left_text) == strupper(entry) - local right_match = line.right_text and strupper(line.right_text) == strupper(entry) - return left_match or right_match - end) -end - -function M.tooltip_find(pattern, tooltip) - local count = 0 - for _, line in tooltip do - if line.left_text and strfind(line.left_text, pattern) then - count = count + 1 - end - if line.right_text and strfind(line.right_text, pattern) then - count = count + 1 - end - end - return count -end - -function M.load_tooltip(frame, tooltip) - frame:ClearLines() - for _, line in ipairs(tooltip) do - if line.right_text then - frame:AddDoubleLine(line.left_text, line.right_text, line.left_color[1], line.left_color[2], line.left_color[3], line.right_color[1], line.right_color[2], line.right_color[3]) - else - frame:AddLine(line.left_text, line.left_color[1], line.left_color[2], line.left_color[3], true) - end - end - for i = 1, getn(tooltip) do -- TODO why is this needed? - _G[frame:GetName() .. 'TextLeft' .. i]:SetJustifyH('LEFT') - _G[frame:GetName() .. 'TextRight' .. i]:SetJustifyH('LEFT') - end - frame:Show() -end - -function M.display_name(item_id, no_brackets, no_color) - local item_info = item(item_id) - if item_info then - local name = item_info.name - if not no_brackets then - name = '[' .. name .. ']' - end - if not no_color then - name = aux.select(4, GetItemQualityColor(item_info.quality)) .. name .. FONT_COLOR_CODE_CLOSE - end - return name - end -end - -function M.auctionable(tooltip, quality, strict) - local status = tooltip[2] and tooltip[2].left_text - local durability, max_durability = durability(tooltip) - return (not quality or quality < 6) - and status ~= ITEM_BIND_ON_PICKUP - and status ~= ITEM_BIND_QUEST - and status ~= ITEM_SOULBOUND - and (not tooltip_match(ITEM_CONJURED, tooltip) or tooltip_find(ITEM_MIN_LEVEL, tooltip) > 1) - and not (strict and durability and durability < max_durability) -end - -function M.tooltip(setter, arg1, arg2) - AuxTooltip:SetOwner(UIParent, 'ANCHOR_NONE') - AuxTooltip.money = 0 - if setter == 'auction' then - AuxTooltip:SetAuctionItem(arg1, arg2) - elseif setter == 'bag' then - AuxTooltip:SetBagItem(arg1, arg2) - elseif setter == 'inventory' then - AuxTooltip:SetInventoryItem(arg1, arg2) - elseif setter == 'link' then - AuxTooltip:SetHyperlink(arg1) - end - local tooltip = T.acquire() - for i = 1, AuxTooltip:NumLines() do - tinsert(tooltip, T.map( - 'left_text', _G['AuxTooltipTextLeft' .. i]:GetText(), - 'left_color', T.list(_G['AuxTooltipTextLeft' .. i]:GetTextColor()), - 'right_text', _G['AuxTooltipTextRight' .. i]:IsVisible() and _G['AuxTooltipTextRight' .. i]:GetText(), - 'right_color', T.list(_G['AuxTooltipTextRight' .. i]:GetTextColor()) - )) - end - return tooltip, AuxTooltip.money -end - -do - local pattern = '^' .. gsub(gsub(ITEM_SPELL_CHARGES_P1, '%%d', '(%%d+)'), '%%%d+%$d', '(%%d+)') .. '$' - function item_charges(tooltip) - for _, line in tooltip do - local _, _, left_charges_string = strfind(line.left_text or '', pattern) - local _, _, right_charges_string = strfind(line.right_text or '', pattern) - - local charges = tonumber(left_charges_string) or tonumber(right_charges_string) - if charges then - return max(1, charges) -- TODO kronos bug? should never be 0 - end - end - return 1 - end -end - -do - local data = { - -- wizard oil - [20744] = 5, - [20746] = 5, - [20750] = 5, - [20749] = 5, - - -- mana oil - [20745] = 5, - [20747] = 5, - [20748] = 5, - - -- discombobulator - [4388] = 5, - - -- recombobulator - [4381] = 10, - [18637] = 10, - - -- deflector - [4376] = 5, - [4386] = 5, - - -- ... TODO - } - function M.max_item_charges(item_id) - return data[item_id] - end -end - -do - local pattern = '^' .. gsub(gsub(DURABILITY_TEMPLATE, '%%d', '(%%d+)'), '%%%d+%$d', '(%%d+)') .. '$' - function M.durability(tooltip) - for _, line in tooltip do - local _, _, left_durability_string, left_max_durability_string = strfind(line.left_text or '', pattern) - local _, _, right_durability_string, right_max_durability_string = strfind(line.right_text or '', pattern) - local durability = tonumber(left_durability_string) or tonumber(right_durability_string) - local max_durability = tonumber(left_max_durability_string) or tonumber(right_max_durability_string) - if durability then - return durability, max_durability - end - end - end -end - -function M.item_key(link) - local item_id, suffix_id = parse_link(link) - return item_id .. ':' .. suffix_id -end - -function M.parse_link(link) - local _, _, item_id, enchant_id, suffix_id, unique_id, name = strfind(link, '|c%x%x%x%x%x%x%x%x|Hitem:(%d*):(%d*):(%d*):(%d*)[:0-9]*|h%[(.-)%]|h|r') - return tonumber(item_id) or 0, tonumber(suffix_id) or 0, tonumber(unique_id) or 0, tonumber(enchant_id) or 0, name -end - -function M.itemstring(item_id, suffix_id, unique_id, enchant_id) - return 'item:' .. (item_id or 0) .. ':' .. (enchant_id or 0) .. ':' .. (suffix_id or 0) .. ':' .. (unique_id or 0) -end - -function M.item(item_id, suffix_id) - local itemstring = 'item:' .. (item_id or 0) .. ':0:' .. (suffix_id or 0) .. ':0' - local name, itemstring, quality, level, class, subclass, max_stack, slot, texture = GetItemInfo(itemstring) - return name and T.map( - 'name', name, - 'itemstring', itemstring, - 'quality', quality, - 'level', level, - 'class', class, - 'subclass', subclass, - 'slot', slot, - 'max_stack', max_stack, - 'texture', texture - ) or item_info(item_id) -end - -function M.item_class_index(item_class) - for i, class in T.temp-T.list(GetAuctionItemClasses()) do - if strupper(class) == strupper(item_class) then - return i, class - end - end -end - -function M.item_subclass_index(class_index, item_subclass) - for i, subclass in T.temp-T.list(GetAuctionItemSubClasses(class_index)) do - if strupper(subclass) == strupper(item_subclass) then - return i, subclass - end - end -end - -function M.item_slot_index(class_index, subclass_index, slot_name) - for i, slot in T.temp-T.list(GetAuctionInvTypes(class_index, subclass_index)) do - if strupper(_G[slot]) == strupper(slot_name) then - return i, _G[slot] - end - end -end - -function M.item_quality_index(item_quality) - for i = 0, 4 do - local quality = _G['ITEM_QUALITY' .. i .. '_DESC'] - if strupper(item_quality) == strupper(quality) then - return i, quality - end - end -end - -function M.inventory() - local bag, slot = 0, 0 - return function() - if not GetBagName(bag) or slot >= GetContainerNumSlots(bag) then - repeat bag = bag + 1 until GetBagName(bag) or bag > 4 - slot = 1 - else - slot = slot + 1 - end - if bag <= 4 then return T.list(bag, slot), bag_type(bag) end - end -end - -function M.bag_type(bag) - if bag == 0 then return 1 end - local link = GetInventoryItemLink('player', ContainerIDToInventoryID(bag)) - if link then - local item_id = parse_link(link) - local item_info = item(item_id) - return item_subclass_index(3, item_info.subclass) - end -end \ No newline at end of file diff --git a/aux-addon/util/money.lua b/aux-addon/util/money.lua deleted file mode 100644 index 734c4af..0000000 --- a/aux-addon/util/money.lua +++ /dev/null @@ -1,131 +0,0 @@ -module 'aux.util.money' - -local T = require 'T' -local aux = require 'aux' - -M.GOLD_TEXT = '|cffffd70ag|r' -M.SILVER_TEXT = '|cffc7c7cfs|r' -M.COPPER_TEXT = '|cffeda55fc|r' - -local COPPER_PER_GOLD = 10000 -local COPPER_PER_SILVER = 100 - -function M.to_gsc(money) - local gold = floor(money / COPPER_PER_GOLD) - local silver = floor(mod(money, COPPER_PER_GOLD) / COPPER_PER_SILVER) - local copper = mod(money, COPPER_PER_SILVER) - return gold, silver, copper -end - -function M.from_gsc(gold, silver, copper) - return gold * COPPER_PER_GOLD + silver * COPPER_PER_SILVER + copper -end - -function M.to_string2(money, exact, color) - color = color or FONT_COLOR_CODE_CLOSE - - local TEXT_NONE = '0' - - local GOLD = 'ffd100' - local SILVER = 'e6e6e6' - local COPPER = 'c8602c' - local START = '|cff%s%d' .. FONT_COLOR_CODE_CLOSE - local PART = color .. '.|cff%s%02d' .. FONT_COLOR_CODE_CLOSE - local NONE = '|cffa0a0a0' .. TEXT_NONE .. FONT_COLOR_CODE_CLOSE - - if not exact and money >= COPPER_PER_GOLD then - money = aux.round(money / COPPER_PER_SILVER) * COPPER_PER_SILVER - end - local g, s, c = to_gsc(money) - - local str = '' - - local fmt = START - if g > 0 then - str = str .. format(fmt, GOLD, g) - fmt = PART - end - if s > 0 or c > 0 then - str = str .. format(fmt, SILVER, s) - fmt = PART - end - if c > 0 then - str = str .. format(fmt, COPPER, c) - end - if str == '' then - str = NONE - end - return str -end - -function M.to_string(money, pad, trim, color, no_color) - local is_negative = money < 0 - money = abs(money) - local gold, silver, copper = to_gsc(money) - - local gold_text, silver_text, copper_text - if no_color then - gold_text, silver_text, copper_text = 'g', 's', 'c' - else - gold_text, silver_text, copper_text = GOLD_TEXT, SILVER_TEXT, COPPER_TEXT - end - - local text - if trim then - local parts = T.temp-T.acquire() - if gold > 0 then - tinsert(parts, format_number(gold, false, color) .. gold_text) - end - if silver > 0 then - tinsert(parts, format_number(silver, pad, color) .. silver_text) - end - if copper > 0 or gold == 0 and silver == 0 then - tinsert(parts, format_number(copper, pad, color) .. copper_text) - end - text = aux.join(parts, ' ') - else - if gold > 0 then - text = format_number(gold, false, color) .. gold_text .. ' ' .. format_number(silver, pad, color) .. silver_text .. ' ' .. format_number(copper, pad, color) .. copper_text - elseif silver > 0 then - text = format_number(silver, false, color) .. silver_text .. ' ' .. format_number(copper, pad, color) .. copper_text - else - text = format_number(copper, false, color) .. copper_text - end - end - - if is_negative then - text = (color and color'-' or '-') .. text - end - - return text -end - -function M.from_string(value) - local number = tonumber(value) - if number and number >= 0 then - return number * COPPER_PER_GOLD - end - - value = gsub(gsub(strlower(value), '|c%x%x%x%x%x%x%x%x', ''), FONT_COLOR_CODE_CLOSE, '') - - local gold = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)g'))) - local silver = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)s'))) - local copper = tonumber(aux.select(3, strfind(value, '(%d*%.?%d+)c'))) - if not gold and not silver and not copper then return end - - value = gsub(value, '%d*%.?%d+g', '', 1) - value = gsub(value, '%d*%.?%d+s', '', 1) - value = gsub(value, '%d*%.?%d+c', '', 1) - if strfind(value, '%S') then return end - - return from_gsc(gold or 0, silver or 0, copper or 0) -end - -function M.format_number(num, pad, color) - num = format('%0' .. (pad and 2 or 0) .. 'd', num) - if color then - return color .. num .. FONT_COLOR_CODE_CLOSE - else - return num - end -end \ No newline at end of file diff --git a/aux-addon/util/persistence.lua b/aux-addon/util/persistence.lua deleted file mode 100644 index 8481547..0000000 --- a/aux-addon/util/persistence.lua +++ /dev/null @@ -1,75 +0,0 @@ -module 'aux.util.persistence' - -local T = require 'T' -local aux = require 'aux' - -function M.read(schema, str) - if schema == 'string' then - return str - elseif schema == 'boolean' then - return str == '1' - elseif schema == 'number' then - return tonumber(str) - elseif type(schema) == 'table' and schema[1] == 'list' then - return T.temp-read_list(schema, str) - elseif type(schema) == 'table' and schema[1] == 'tuple' then - return T.temp-read_tuple(schema, str) - else - error('Invalid schema.', 2) - end -end - -function M.write(schema, obj) - if schema == 'string' then - return obj or '' - elseif schema == 'boolean' then - return obj and '1' or '0' - elseif schema == 'number' then - return obj and tostring(obj) or '' - elseif type(schema) == 'table' and schema[1] == 'list' then - return write_list(schema, obj) - elseif type(schema) == 'table' and schema[1] == 'tuple' then - return write_tuple(schema, obj) - else - error('Invalid schema.', 2) - end -end - -function read_list(schema, str) - if str == '' then return T.acquire() end - local separator = schema[2] - local element_type = schema[3] - return aux.map(aux.split(str, separator), function(part) - return read(element_type, part) - end) -end - -function write_list(schema, list) - local separator = schema[2] - local element_type = schema[3] - local parts = aux.map(T.temp-aux.copy(list), function(element) - return write(element_type, element) - end) - return aux.join(parts, separator) -end - -function read_tuple(schema, str) - local separator = schema[2] - local tuple = T.acquire() - local parts = T.temp-aux.split(str, separator) - for i = 3, getn(schema) do - local key, type = next(schema[i]) - tuple[key] = read(type, parts[i - 2]) - end - return tuple -end - -function write_tuple(schema, tuple) - local separator = schema[2] - local parts = T.temp-T.acquire() - for i = 3, getn(schema) do - local key, type = next(schema[i]) - tinsert(parts, write(type, tuple[key])) - end - return aux.join(parts, separator) -end \ No newline at end of file diff --git a/aux-addon/util/scan.lua b/aux-addon/util/scan.lua deleted file mode 100644 index 68b3c8e..0000000 --- a/aux-addon/util/scan.lua +++ /dev/null @@ -1,82 +0,0 @@ -module 'aux.util.scan' - -local T = require 'T' -local aux = require 'aux' -local info = require 'aux.util.info' -local filter_util = require 'aux.util.filter' -local scan = require 'aux.core.scan' - -function M.test(record, index) - local auction_record = T.temp-info.auction(index, record.query_type) - return auction_record and auction_record.search_signature == record.search_signature -end - -function M.find(auction_record, status_bar, on_abort, on_failure, on_success) - - local queries = T.list(T.acquire()) - - if auction_record.blizzard_query then - local blizzard_query1 = aux.copy(auction_record.blizzard_query) - blizzard_query1.first_page = auction_record.page - blizzard_query1.last_page = auction_record.page - tinsert(queries, T.map('blizzard_query', blizzard_query1)) - - if auction_record.page > 0 then - local blizzard_query2 = aux.copy(auction_record.blizzard_query) - blizzard_query2.first_page = auction_record.page - 1 - blizzard_query2.last_page = auction_record.page - 1 - tinsert(queries, T.map('blizzard_query', blizzard_query2)) - end - - local item_query = item_query(auction_record.item_id, 0, 0) - if not aux.eq(auction_record.blizzard_query, item_query.blizzard_query) then - tinsert(queries, item_query) - end - end - - local found - return scan.start{ - type = auction_record.query_type, - queries = queries, - on_scan_start = function() - status_bar:update_status(0, 0) - status_bar:set_text('Searching auction...') - end, - on_start_query = function(query_index) - status_bar:update_status((query_index - 1) / getn(queries), 0) - end, - on_auction = function(record) - if test(auction_record, record.index) then - found = true - scan.stop() - status_bar:update_status(1, 1) - status_bar:set_text('Auction found') - return on_success(record.index) - end - end, - on_abort = function() - if not found then - status_bar:update_status(1, 1) - status_bar:set_text('Auction not found') - return on_abort() - end - end, - on_complete = function() - if not found then - status_bar:update_status(1, 1) - status_bar:set_text('Auction not found') - return on_failure() - end - end, - } -end - -function M.item_query(item_id, first_page, last_page) - local item_info = info.item(item_id) - if item_info then - local query = filter_util.query(item_info.name .. '/exact') - query.blizzard_query.first_page = first_page - query.blizzard_query.last_page = last_page - return T.map('validator', query.validator, 'blizzard_query', query.blizzard_query) - end -end \ No newline at end of file diff --git a/aux-addon/util/sort.lua b/aux-addon/util/sort.lua deleted file mode 100644 index fd8871c..0000000 --- a/aux-addon/util/sort.lua +++ /dev/null @@ -1,30 +0,0 @@ -module 'aux.util.sort' - -local T = require 'T' - -M.EQ = 0 -M.LT = 1 -M.GT = 2 - -function M.compare(a, b, desc) - if a < b then - return desc and GT or LT - elseif a > b then - return desc and LT or GT - else - return EQ - end -end - -M.multi_lt = T.vararg-function(arg) - for i = 1, getn(arg), 2 do - if arg[i] and arg[i + 1] and arg[i] ~= arg[i + 1] then - return arg[i] < arg[i + 1] - elseif not arg[i] and arg[i + 1] then - return true - elseif not arg[i + 1] then - return false - end - end - return false -end \ No newline at end of file From 3dc0f22908da1ffcb0e57ccfb448f1b2290a5ffe Mon Sep 17 00:00:00 2001 From: geojak Date: Sat, 2 Apr 2022 14:54:03 +0200 Subject: [PATCH 5/6] Add files via upload reupload in right folder --- Bindings.xml | 5 +++ README_Geojak.md | 10 +++++ aux-addon.lua | 2 +- aux-addon.toc | 3 ++ core/crafting.lua | 5 ++- core/disenchant.lua | 22 +++++++--- core/post.lua | 93 ++++++++++++++++++++++++++++++++++++++++- core/scan.lua | 8 +++- core/tooltip.lua | 11 ++++- localization.lua | 7 ++++ tabs/post/core.lua | 63 ++++++++++++++++++++++++++-- tabs/search/filter.lua | 2 +- tabs/search/frame.lua | 2 +- tabs/search/results.lua | 3 ++ tabs/search/saved.lua | 36 +++++++++++++++- util/filter.lua | 49 ++++++++++++++++++++-- 16 files changed, 298 insertions(+), 23 deletions(-) create mode 100644 Bindings.xml create mode 100644 README_Geojak.md create mode 100644 localization.lua diff --git a/Bindings.xml b/Bindings.xml new file mode 100644 index 0000000..6d7d97e --- /dev/null +++ b/Bindings.xml @@ -0,0 +1,5 @@ + + + post_auctions_bind() + + \ No newline at end of file diff --git a/README_Geojak.md b/README_Geojak.md new file mode 100644 index 0000000..064b4ac --- /dev/null +++ b/README_Geojak.md @@ -0,0 +1,10 @@ +1. Added auto bid feature, doubled the existing auto buy code and slighly editted +2. Added auto pricing heuristic, these are activate when posting an item with bid price 0, the buyout price works as a minimum +3. Added a keybind for the post button, this significantly speeds up mass posting of items +4. Changed the default post stack size to rand(1,max) instead of always max +5. FIXED TWILLIGHT CULTIST SET NOT DISENCHATABLE +6. use SHagu tweaks vendor values if aux values not avaialbe +7. dont auto bid on own items +8. new filter disenchant-percent and bid-disencahnt-percent +9. enchanting created wands are not encahntabke, same with twillight stuff at 5. reworked implementation for easier addition +10. disenchant values are calcualted from min(value,value_today), this protects users from low enchanting mat prices \ No newline at end of file diff --git a/aux-addon.lua b/aux-addon.lua index e4c3790..b86e066 100644 --- a/aux-addon.lua +++ b/aux-addon.lua @@ -58,7 +58,7 @@ function handle.LOAD() ignore_owner = true, crafting_cost = true, post_bid = false, - post_duration = post.DURATION_8, + post_duration = post.DURATION_24, items = {}, item_ids = {}, auctionable_items = {}, diff --git a/aux-addon.toc b/aux-addon.toc index 82b078a..919f718 100644 --- a/aux-addon.toc +++ b/aux-addon.toc @@ -1,5 +1,6 @@ ## Interface: 11200 ## Author: shirsig +## OptionalDeps: ShaguTweaks ## Title: aux ## Notes: Auction House replacement ## SavedVariables: aux @@ -51,4 +52,6 @@ tabs\bids\frame.lua core\crafting.lua +localization.lua + diff --git a/core/crafting.lua b/core/crafting.lua index b00df11..6124f6e 100644 --- a/core/crafting.lua +++ b/core/crafting.lua @@ -89,7 +89,10 @@ do total_cost = total_cost + value * count end end - TradeSkillReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) + TradeSkillReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) + if ATSWReagentLabel then + ATSWReagentLabel:SetText(SPELL_REAGENTS .. ' ' .. cost_label(total_cost)) + end return unpack(ret) end) for i = 1, 8 do diff --git a/core/disenchant.lua b/core/disenchant.lua index 3d61dab..b6e5637 100644 --- a/core/disenchant.lua +++ b/core/disenchant.lua @@ -33,22 +33,32 @@ local WEAPON = T.set( 'INVTYPE_RANGEDRIGHT' ) -function M.value(slot, quality, level) +function M.value(slot, quality, level, item_id) local expectation - for _, event in distribution(slot, quality, level) do + for _, event in distribution(slot, quality, level, item_id) do local value = history.value(event.item_id .. ':' .. 0) - if not value then + if not value then return - else - expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value end + local market_value = history.market_value(event.item_id .. ':' .. 0) + if market_value then + value=min(value,market_value) + end + expectation = (expectation or 0) + event.probability * (event.min_quantity + event.max_quantity) / 2 * value end return expectation end -function M.distribution(slot, quality, level) +function M.distribution(slot, quality, level, item_id) if not ARMOR[slot] and not WEAPON[slot] or level == 0 then return T.acquire() + elseif --special items not following general rules + --twillight cultist items are not disenchantable + item_id==20408 or item_id==20407 or item_id==20406 + --enchanting created wands are not disenchantable + or item_id==11288 or item_id==11290 or item_id==11287 or item_id==11289 + then + return T.acquire() end local function p(probability_armor, probability_weapon) diff --git a/core/post.lua b/core/post.lua index b34c05b..0e8415f 100644 --- a/core/post.lua +++ b/core/post.lua @@ -3,6 +3,8 @@ module 'aux.core.post' local aux = require 'aux' local info = require 'aux.util.info' local stack = require 'aux.core.stack' +local history = require 'aux.core.history' +local disenchant = require 'aux.core.disenchant' local state @@ -41,8 +43,97 @@ function post_auction(slot, k) PickupContainerItem(unpack(slot)) ClickAuctionSellItemButton() ClearCursor() + + local start_price = state.unit_start_price + local buyout_price = state.unit_buyout_price + + --use autoprice heuristic if start bid 0 is set + if start_price == 0 then + + local kz_daily = 0 + local kz_pricing = 0 + local kz_warn = 0 + + if buyout_price > 0 then + kz_pricing=1 + start_price = buyout_price + end + + if history.market_value(state.item_key) ~= nil then + local tmp = tonumber(history.market_value(state.item_key)) + tmp = max(0.99*tmp,tmp-50) + buyout_price = max(buyout_price,tmp) + kz_daily = 1 + end + + if history.value(state.item_key) ~= nil then + if kz_pricing == 1 and kz_daily == 1 then + buyout_price = max(buyout_price,1.0*tonumber(history.value(state.item_key))) + elseif kz_daily == 1 then + start_price = max(start_price,0.91*tonumber(history.value(state.item_key))) + else + buyout_price = max(buyout_price,0.99* tonumber(history.value(state.item_key))) + end + else + kz_warn = kz_warn + 1 + end + + if disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id) ~= nil then + start_price = max(start_price,0.85* tonumber(disenchant.value(item_info.slot, item_info.quality, item_info.level, item_info.item_id))) + end + + if aux.account_data.merchant_buy[item_info.item_id] ~= nil then + local tmp = tostring(aux.account_data.merchant_buy[item_info.item_id]) + tmp = strsub(tmp,1,-3) + start_price = max(start_price,tonumber(tmp) * 1.1) + buyout_price = max(buyout_price,tonumber(tmp) * 1.15) + else + local vendor_price = 0 + if aux.account_data.merchant_sell[item_info.item_id] ~= nil then + vendor_price = tonumber(aux.account_data.merchant_sell[item_info.item_id]) + elseif ShaguTweaks.SellValueDB[item_info.item_id] ~= nil then + local charges = 1 + if info.max_item_charges(item_info.item_id) ~= nil then + charges=info.max_item_charges(item_info.item_id) + end + vendor_price = ShaguTweaks.SellValueDB[item_info.item_id] / charges + end + if vendor_price > 0 then + if kz_daily == 0 then + start_price = max(start_price, vendor_price * (1.35+3.65*math.exp(-(1/4000)* vendor_price))) + else + start_price = max(start_price, vendor_price * 1.35) + end + else + kz_warn = kz_warn + 1 + end + end + + start_price = max(start_price,0.91 * buyout_price) + buyout_price = max(start_price, buyout_price) + + if kz_warn == 2 and kz_pricing == 0 then + print("WARNING, insufficient data for autopricing!") + if start_price == 0 then + return stop() + end + end + + local gold = floor(start_price / COPPER_PER_GOLD) + local silver = floor(mod(start_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) + local copper = aux.round(mod(start_price, COPPER_PER_SILVER)) + + print("bid_price: "..gold.."g "..silver.."s "..copper.."c") + + gold = floor(buyout_price / COPPER_PER_GOLD) + silver = floor(mod(buyout_price, COPPER_PER_GOLD) / COPPER_PER_SILVER) + copper = aux.round(mod(buyout_price, COPPER_PER_SILVER)) + + print("buyout_price: "..gold.."g "..silver.."s "..copper.."c") - StartAuction(max(1, aux.round(state.unit_start_price * item_info.aux_quantity)), aux.round(state.unit_buyout_price * item_info.aux_quantity), state.duration) + end + + StartAuction(max(1, aux.round(start_price * item_info.aux_quantity)), aux.round(buyout_price * item_info.aux_quantity), state.duration) local send_signal, signal_received = aux.signal() aux.when(signal_received, function() diff --git a/core/scan.lua b/core/scan.lua index bab9f1a..7da6a61 100644 --- a/core/scan.lua +++ b/core/scan.lua @@ -146,8 +146,12 @@ function scan_page(i) auction_info.query_type = get_state().params.type history.process_auction(auction_info) - - if (get_state().params.auto_buy_validator or pass)(auction_info) then + + if (get_state().params.auto_bid_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then + local send_signal, signal_received = aux.signal() + aux.when(signal_received, scan_page, i) + return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.bid_price, send_signal) + elseif (get_state().params.auto_buy_validator or pass)(auction_info) and auction_info.owner ~= UnitName("player") then local send_signal, signal_received = aux.signal() aux.when(signal_received, scan_page, i) return aux.place_bid(auction_info.query_type, auction_info.index, auction_info.buyout_price, send_signal) diff --git a/core/tooltip.lua b/core/tooltip.lua index c43365b..353e893 100644 --- a/core/tooltip.lua +++ b/core/tooltip.lua @@ -54,7 +54,7 @@ function M.extend_tooltip(tooltip, link, quantity) quantity = IsShiftKeyDown() and quantity or 1 local item_info = T.temp-info.item(item_id) if item_info then - local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level) + local distribution = disenchant.distribution(item_info.slot, item_info.quality, item_info.level, item_id) if getn(distribution) > 0 then if settings.disenchant_distribution then tooltip:AddLine('Disenchants into:', aux.color.tooltip.disenchant.distribution()) @@ -64,7 +64,7 @@ function M.extend_tooltip(tooltip, link, quantity) end end if settings.disenchant_value then - local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level) + local disenchant_value = disenchant.value(item_info.slot, item_info.quality, item_info.level, item_id) tooltip:AddLine('Disenchant: ' .. (disenchant_value and money.to_string2(disenchant_value) or UNKNOWN), aux.color.tooltip.disenchant.value()) end end @@ -77,6 +77,13 @@ function M.extend_tooltip(tooltip, link, quantity) end if settings.merchant_sell then local price = info.merchant_info(item_id) + if price == nil and ShaguTweaks.SellValueDB[item_id] ~= nil then + local charges = 1 + if info.max_item_charges(item_id) ~= nil then + charges=info.max_item_charges(item_id) + end + price = ShaguTweaks.SellValueDB[item_id] / charges + end if price ~= 0 then tooltip:AddLine('Vendor: ' .. (price and money.to_string2(price * quantity) or UNKNOWN), aux.color.tooltip.merchant()) end diff --git a/localization.lua b/localization.lua new file mode 100644 index 0000000..37e3b3a --- /dev/null +++ b/localization.lua @@ -0,0 +1,7 @@ +BINDING_HEADER_AUX = "aux"; + +local post = require 'aux.tabs.post' + +function post_auctions_bind() + post.post_auctions_bind() +end \ No newline at end of file diff --git a/tabs/post/core.lua b/tabs/post/core.lua index d749c5b..08a3581 100644 --- a/tabs/post/core.lua +++ b/tabs/post/core.lua @@ -248,6 +248,58 @@ function post_auctions() end end +function M.post_auctions_bind() + if selected_item then + local unit_start_price = get_unit_start_price() + local unit_buyout_price = get_unit_buyout_price() + local stack_size = stack_size_slider:GetValue() + local stack_count + stack_count = stack_count_slider:GetValue() + local duration = UIDropDownMenu_GetSelectedValue(duration_dropdown) + local key = selected_item.key + + local duration_code + if duration == DURATION_2 then + duration_code = 2 + elseif duration == DURATION_8 then + duration_code = 3 + elseif duration == DURATION_24 then + duration_code = 4 + end + + post.start( + key, + stack_size, + duration, + unit_start_price, + unit_buyout_price, + stack_count, + function(posted) + if not frame:IsShown() then + return + end + for i = 1, posted do + record_auction(key, stack_size, unit_start_price * stack_size, unit_buyout_price, duration_code, UnitName'player') + end + update_inventory_records() + local same + for _, record in inventory_records do + if record.key == key then + same = record + break + end + end + if same then + update_item(same) + else + selected_item = nil + end + refresh = true + end + ) + end +end + function validate_parameters() if not selected_item then post_button:Disable() @@ -258,7 +310,7 @@ function validate_parameters() return end if get_unit_start_price() == 0 then - post_button:Disable() + post_button:Enable() return end if stack_count_slider:GetValue() == 0 then @@ -379,18 +431,23 @@ function update_item(item) UIDropDownMenu_SetSelectedValue(duration_dropdown, settings.duration) hide_checkbox:SetChecked(settings.hidden) - + local ii = 1 if selected_item.max_charges then for i = selected_item.max_charges, 1, -1 do if selected_item.availability[i] > 0 then stack_size_slider:SetMinMaxValues(1, i) + ii=i break end end else + ii = min(selected_item.max_stack, selected_item.aux_quantity) stack_size_slider:SetMinMaxValues(1, min(selected_item.max_stack, selected_item.aux_quantity)) end - stack_size_slider:SetValue(aux.huge) + + stack_size_slider:SetValue(math.random(1,ii)) + --stack_size_slider:SetValue(aux.huge) + --stack_size_slider:SetValue(1) quantity_update(true) unit_start_price_input:SetText(money.to_string(settings.start_price, true, nil, nil, true)) diff --git a/tabs/search/filter.lua b/tabs/search/filter.lua index 44618bf..eaac271 100644 --- a/tabs/search/filter.lua +++ b/tabs/search/filter.lua @@ -323,7 +323,7 @@ function set_filter_display_offset(x_offset, y_offset) end function initialize_filter_dropdown() - for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do + for _, filter in ipairs(T.temp-T.list('and', 'or', 'not', 'price', 'profit', 'vendor-profit', 'disenchant-profit', 'percent', 'disenchant-percent', 'bid-price', 'bid-profit', 'bid-vendor-profit', 'bid-disenchant-profit', 'bid-percent', 'bid-disenchant-percent', 'item', 'tooltip', 'min-level', 'max-level', 'rarity', 'left', 'utilizable', 'seller')) do UIDropDownMenu_AddButton(T.map( 'text', filter, 'value', filter, diff --git a/tabs/search/frame.lua b/tabs/search/frame.lua index d319b09..24a5d3a 100644 --- a/tabs/search/frame.lua +++ b/tabs/search/frame.lua @@ -547,7 +547,7 @@ for _ = 1, 5 do end favorite_searches_listing = listing.new(frame.saved.favorite) -favorite_searches_listing:SetColInfo{{name='Auto Buy', width=.07, align='CENTER'}, {name='Favorite Searches', width=.93}} +favorite_searches_listing:SetColInfo{{name='Auto', width=.07, align='CENTER'}, {name='Favorite Searches', width=.93}} recent_searches_listing = listing.new(frame.saved.recent) recent_searches_listing:SetColInfo{{name='Recent Searches', width=1}} diff --git a/tabs/search/results.lua b/tabs/search/results.lua index 48e85f2..6f9e863 100644 --- a/tabs/search/results.lua +++ b/tabs/search/results.lua @@ -151,6 +151,7 @@ function start_real_time_scan(query, search, continuation) type = 'list', queries = {query}, auto_buy_validator = search.auto_buy_validator, + auto_bid_validator = search.auto_bid_validator, on_scan_start = function() search.status_bar:update_status(.9999, .9999) search.status_bar:set_text('Scanning last page ...') @@ -227,6 +228,7 @@ function start_search(queries, continuation) type = 'list', queries = queries, auto_buy_validator = search.auto_buy_validator, + auto_bid_validator = search.auto_bid_validator, on_scan_start = function() search.status_bar:update_status(0, 0) if continuation then @@ -336,6 +338,7 @@ function M.execute(resume, real_time) search.last_page = last_page search.real_time = real_time search.auto_buy_validator = get_auto_buy_validator() + search.auto_bid_validator = get_auto_bid_validator() end local continuation = resume and current_search().continuation diff --git a/tabs/search/saved.lua b/tabs/search/saved.lua index b2043a0..8089e79 100644 --- a/tabs/search/saved.lua +++ b/tabs/search/saved.lua @@ -15,7 +15,7 @@ function update_search_listings() local search = favorite_searches[i] local name = strsub(search.prettified, 1, 250) tinsert(favorite_search_rows, T.map( - 'cols', T.list(T.map('value', search.auto_buy and aux.color.red'X' or ''), T.map('value', name)), + 'cols', T.list(T.map('value', search.auto_buy and aux.color.red'X' or search.auto_bid and aux.color.red'Y' or ''), T.map('value', name)), 'search', search, 'index', i )) @@ -68,8 +68,10 @@ handlers = { u(d) elseif st == favorite_searches_listing then local auto_buy = data.search.auto_buy + local auto_bid = data.search.auto_bid gui.menu( (auto_buy and 'Disable' or 'Enable') .. ' Auto Buy', function() if auto_buy then data.search.auto_buy = nil else enable_auto_buy(data.search) end u() end, + (auto_bid and 'Disable' or 'Enable') .. ' Auto Bid', function() if auto_bid then data.search.auto_bid = nil else enable_auto_bid(data.search) end u() end, 'Move Up', function() move_up(favorite_searches, data.index); u() end, 'Move Down', function() move_down(favorite_searches, data.index); u() end, 'Delete', function() tremove(favorite_searches, data.index); u() end @@ -106,6 +108,23 @@ function get_auto_buy_validator() end end +function get_auto_bid_validator() + local validators = T.acquire() + for _, search in favorite_searches do + if search.auto_bid then + local queries, error = filter_util.queries(search.filter_string) + if queries then + tinsert(validators, queries[1].validator) + else + aux.print('Invalid auto bid filter:', error) + end + end + end + return function(record) + return aux.any(validators, function(validator) return validator(record) end) + end +end + function add_favorite(filter_string) local queries, error = filter_util.queries(filter_string) if queries then @@ -134,6 +153,21 @@ function enable_auto_buy(search) end end +function enable_auto_bid(search) + local queries, error = filter_util.queries(search.filter_string) + if queries then + if getn(queries) > 1 then + aux.print('Error: Auto Bid does not support multi-queries') + elseif aux.size(queries[1].blizzard_query) > 0 and not filter_util.parse_filter_string(search.filter_string).blizzard.exact then + aux.print('Error: Auto Bid does not support Blizzard filters') + else + search.auto_bid = true + end + else + aux.print('Invalid filter:', error) + end +end + function move_up(list, index) if list[index - 1] then list[index], list[index - 1] = list[index - 1], list[index] diff --git a/util/filter.lua b/util/filter.lua index bb7092e..e997192 100644 --- a/util/filter.lua +++ b/util/filter.lua @@ -110,6 +110,16 @@ M.filters = { end end }, + + ['bid-disenchant-percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + and auction_record.unit_bid_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct + end + end + }, ['percent'] = { input_type = 'number', @@ -121,6 +131,17 @@ M.filters = { end end }, + + ['disenchant-percent'] = { + input_type = 'number', + validator = function(pct) + return function(auction_record) + return auction_record.unit_buyout_price > 0 + and disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + and auction_record.unit_buyout_price / disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) * 100 <= pct + end + end + }, ['bid-profit'] = { input_type = 'money', @@ -144,8 +165,8 @@ M.filters = { input_type = 'money', validator = function(amount) return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return disenchant_value and disenchant_value - auction_record.bid_price >= amount + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + return disenchant_value and disenchant_value - auction_record.bid_price >= amount end end }, @@ -154,8 +175,8 @@ M.filters = { input_type = 'money', validator = function(amount) return function(auction_record) - local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level) - return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount + local disenchant_value = disenchant.value(auction_record.slot, auction_record.quality, auction_record.level, auction_record.item_id) + return auction_record.buyout_price > 0 and disenchant_value and disenchant_value - auction_record.buyout_price >= amount end end }, @@ -165,6 +186,16 @@ M.filters = { validator = function(amount) return function(auction_record) local vendor_price = info.merchant_info(auction_record.item_id) + if not vendor_price then + vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] + if vendor_price then + local charges = 1 + if info.max_item_charges(auction_record.item_id) ~= nil then + info.charges=info.max_item_charges(auction_record.item_id) + end + vendor_price= vendor_price/ charges + end + end return vendor_price and vendor_price * auction_record.aux_quantity - auction_record.bid_price >= amount end end @@ -175,6 +206,16 @@ M.filters = { validator = function(amount) return function(auction_record) local vendor_price = info.merchant_info(auction_record.item_id) + if not vendor_price then + vendor_price = ShaguTweaks.SellValueDB[auction_record.item_id] + if vendor_price then + local charges = 1 + if info.max_item_charges(auction_record.item_id) ~= nil then + info.charges=info.max_item_charges(auction_record.item_id) + end + vendor_price= vendor_price/ charges + end + end return auction_record.buyout_price > 0 and vendor_price and vendor_price * auction_record.aux_quantity - auction_record.buyout_price >= amount end end From 3f2e93e7c0edbf1c40de4ecf2a3b59a9e34a1115 Mon Sep 17 00:00:00 2001 From: geojak Date: Sat, 2 Apr 2022 14:58:28 +0200 Subject: [PATCH 6/6] Add files via upload updated readme_geojak with some forgotten undocumented changes --- README_Geojak.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README_Geojak.md b/README_Geojak.md index 064b4ac..4b24ebf 100644 --- a/README_Geojak.md +++ b/README_Geojak.md @@ -7,4 +7,6 @@ 7. dont auto bid on own items 8. new filter disenchant-percent and bid-disencahnt-percent 9. enchanting created wands are not encahntabke, same with twillight stuff at 5. reworked implementation for easier addition -10. disenchant values are calcualted from min(value,value_today), this protects users from low enchanting mat prices \ No newline at end of file +10. disenchant values are calcualted from min(value,value_today), this protects users from low enchanting mat prices +11. advanced trade skill menu compabillity to display aux crafting cost +12. changed default post duration from 8 to 24, since turtle wow got no fees anyway \ No newline at end of file