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
+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