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
This commit is contained in:
geojak
2022-02-20 23:51:19 +01:00
committed by GitHub
parent 9771521c78
commit 02cfc420dd
42 changed files with 8791 additions and 0 deletions
+156
View File
@@ -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)<br/>
**/aux scale _factor_** (Scales the aux GUI by _factor_)<br/>
**/aux ignore owner** (Disables waiting for owner names when scanning. Recommended)<br/>
**/aux post bid** (Adds a bid price listing to the post tab)<br/>
**/aux crafting cost** (Toggles the crafting price information)<br/>
**/aux post duration _hours_** (Sets the default auction duration to _2_/_8_/_24_ hours)<br/>
### Tooltip
**/aux tooltip value**<br/>
**/aux tooltip daily**<br/>
**/aux tooltip disenchant value**<br/>
**/aux tooltip disenchant distribution**<br/>
**/aux tooltip vendor buy**<br/>
**/aux tooltip vendor sell**<br/>
## 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**<br/>
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**<br/>
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**<br/>
This will scan for usable recipes and exclude those with "libram" in the tooltip (i.e., librams)
**armor/cloth/50/intellect/stamina**<br/>
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.
Binary file not shown.
+243
View File
@@ -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 .. '<aux> ' .. 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
+54
View File
@@ -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
+51
View File
@@ -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),
}
+121
View File
@@ -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
+211
View File
@@ -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
+99
View File
@@ -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
+163
View File
@@ -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
+109
View File
@@ -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
+165
View File
@@ -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
+236
View File
@@ -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
+31
View File
@@ -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
+74
View File
@@ -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
+130
View File
@@ -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
+216
View File
@@ -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
+51
View File
@@ -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
File diff suppressed because it is too large Load Diff
+569
View File
@@ -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
+129
View File
@@ -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
+300
View File
@@ -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
+149
View File
@@ -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)
+37
View File
@@ -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
+101
View File
@@ -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
+62
View File
@@ -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
+125
View File
@@ -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
+71
View File
@@ -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
+560
View File
@@ -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
+326
View File
@@ -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
+85
View File
@@ -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
+395
View File
@@ -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 .. '</p><p>'
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 '<html><body><p>' .. data_link(0, 'Post Filter:') .. '</p><p>' .. str .. '</p></body></html>'
end
function data_link(id, str)
return format('<a href="%s">%s</a>', 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, '<p>(.-)</p>') 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
+559
View File
@@ -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
+443
View File
@@ -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
+181
View File
@@ -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
+188
View File
@@ -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
+45
View File
@@ -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
+549
View File
@@ -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
+443
View File
@@ -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
+131
View File
@@ -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
+75
View File
@@ -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
+82
View File
@@ -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
+30
View File
@@ -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